root/usr.bin/mg/match.c
/*      $OpenBSD: match.c,v 1.25 2023/04/21 13:39:37 op Exp $   */

/* This file is in the public domain. */

/*
 *      Limited parenthesis matching routines
 *
 * The hacks in this file implement automatic matching * of (), [], {}, and
 * other characters.  It would be better to have a full-blown syntax table,
 * but there's enough overhead in the editor as it is.
 */

#include <sys/queue.h>
#include <signal.h>
#include <stdio.h>

#include "def.h"
#include "key.h"

static int      balance(void);
static void     displaymatch(struct line *, int);

/*
 * Balance table. When balance() encounters a character that is to be
 * matched, it first searches this table for a balancing left-side character.
 * If the character is not in the table, the character is balanced by itself.
 */
static struct balance {
        char    left, right;
} bal[] = {
        { '(', ')' },
        { '[', ']' },
        { '{', '}' },
        { '<', '>' },
        { '\0', '\0' }
};

/*
 * Hack to show matching paren.  Self-insert character, then show matching
 * character, if any.  Bound to "blink-and-insert".  Used in the mg startup
 * file to amend the default cursor behaviour of a key press, e.g:
 *   global-set-key "%" blink-and-insert
 */
int
showmatch(int f, int n)
{
        int     i, s;

        for (i = 0; i < n; i++) {
                if ((s = selfinsert(FFRAND, 1)) != TRUE)
                        return (s);
                /* unbalanced -- warn user */
                if (balance() != TRUE)
                        dobeep();
        }
        return (TRUE);
}

/*
 * Search for and display a matching character.
 *
 * This routine does the real work of searching backward
 * for a balancing character.  If such a balancing character
 * is found, it uses displaymatch() to display the match.
 */
static int
balance(void)
{
        struct line     *clp;
        int      cbo;
        int      c, i, depth;
        int      rbal, lbal;

        rbal = key.k_chars[key.k_count - 1];

        /* See if there is a matching character -- default to the same */
        lbal = rbal;
        for (i = 0; bal[i].right != '\0'; i++)
                if (bal[i].right == rbal) {
                        lbal = bal[i].left;
                        break;
                }

        /*
         * Move behind the inserted character.  We are always guaranteed
         * that there is at least one character on the line.
         */
        clp = curwp->w_dotp;
        cbo = curwp->w_doto - 1;

        /* init nesting depth */
        depth = 0;

        for (;;) {
                if (cbo == 0) {
                        clp = lback(clp);       /* beginning of line    */
                        if (clp == curbp->b_headp)
                                return (FALSE);
                        cbo = llength(clp) + 1;
                }
                if (--cbo == llength(clp))
                        c = *curbp->b_nlchr;    /* end of line          */
                else
                        c = lgetc(clp, cbo);    /* somewhere in middle  */

                /*
                 * Check for a matching character.  If still in a nested
                 * level, pop out of it and continue search.  This check
                 * is done before the nesting check so single-character
                 * matches will work too.
                 */
                if (c == lbal) {
                        if (depth == 0) {
                                displaymatch(clp, cbo);
                                return (TRUE);
                        } else
                                depth--;
                }
                /* Check for another level of nesting.   */
                if (c == rbal)
                        depth++;
        }
        /* NOTREACHED */
}

/*
 * Display matching character.  Matching characters that are not in the
 * current window are displayed in the echo line. If in the current window,
 * move dot to the matching character, sit there a while, then move back.
 */
static void
displaymatch(struct line *clp, int cbo)
{
        struct line     *tlp;
        int      tbo;
        int      cp;
        int      bufo;
        int      c;
        int      col;
        int      inwindow;
        char     buf[NLINE];

        /*
         * Figure out if matching char is in current window by
         * searching from the top of the window to dot.
         */
        inwindow = FALSE;
        for (tlp = curwp->w_linep; tlp != lforw(curwp->w_dotp);
            tlp = lforw(tlp))
                if (tlp == clp)
                        inwindow = TRUE;

        if (inwindow == TRUE) {
                tlp = curwp->w_dotp;    /* save current position */
                tbo = curwp->w_doto;

                curwp->w_dotp = clp;    /* move to new position */
                curwp->w_doto = cbo;
                curwp->w_rflag |= WFMOVE;

                update(CMODE);          /* show match */
                ttwait(1000);           /* wait for key or 1 second */

                curwp->w_dotp = tlp;    /* return to old position */
                curwp->w_doto = tbo;
                curwp->w_rflag |= WFMOVE;
                update(CMODE);
        } else {
                /* match is not in this window, so display line in echo area */
                bufo = 0;
                for (cp = 0; cp < llength(clp); cp++) {
                        if (bufo >= sizeof(buf) - 1)
                                break;

                        c = lgetc(clp, cp);
                        if (c != '\t') {
                                if (ISCTRL(c)) {
                                        if (bufo >= sizeof(buf) - 3)
                                                break;
                                        buf[bufo++] = '^';
                                        buf[bufo++] = CCHR(c);
                                } else
                                        buf[bufo++] = c;
                        } else {
                                col = ntabstop(bufo, curbp->b_tabw);
                                while (bufo < col && bufo < sizeof(buf) - 1)
                                        buf[bufo++] = ' ';
                        }
                }
                buf[bufo++] = '\0';
                ewprintf("Matches %s", buf);
        }
}