1 /* $OpenBSD: match.c,v 1.25 2023/04/21 13:39:37 op Exp $ */
2
3 /* This file is in the public domain. */
4
5 /*
6 * Limited parenthesis matching routines
7 *
8 * The hacks in this file implement automatic matching * of (), [], {}, and
9 * other characters. It would be better to have a full-blown syntax table,
10 * but there's enough overhead in the editor as it is.
11 */
12
13 #include <sys/queue.h>
14 #include <signal.h>
15 #include <stdio.h>
16
17 #include "def.h"
18 #include "key.h"
19
20 static int balance(void);
21 static void displaymatch(struct line *, int);
22
23 /*
24 * Balance table. When balance() encounters a character that is to be
25 * matched, it first searches this table for a balancing left-side character.
26 * If the character is not in the table, the character is balanced by itself.
27 */
28 static struct balance {
29 char left, right;
30 } bal[] = {
31 { '(', ')' },
32 { '[', ']' },
33 { '{', '}' },
34 { '<', '>' },
35 { '\0', '\0' }
36 };
37
38 /*
39 * Hack to show matching paren. Self-insert character, then show matching
40 * character, if any. Bound to "blink-and-insert". Used in the mg startup
41 * file to amend the default cursor behaviour of a key press, e.g:
42 * global-set-key "%" blink-and-insert
43 */
44 int
showmatch(int f,int n)45 showmatch(int f, int n)
46 {
47 int i, s;
48
49 for (i = 0; i < n; i++) {
50 if ((s = selfinsert(FFRAND, 1)) != TRUE)
51 return (s);
52 /* unbalanced -- warn user */
53 if (balance() != TRUE)
54 dobeep();
55 }
56 return (TRUE);
57 }
58
59 /*
60 * Search for and display a matching character.
61 *
62 * This routine does the real work of searching backward
63 * for a balancing character. If such a balancing character
64 * is found, it uses displaymatch() to display the match.
65 */
66 static int
balance(void)67 balance(void)
68 {
69 struct line *clp;
70 int cbo;
71 int c, i, depth;
72 int rbal, lbal;
73
74 rbal = key.k_chars[key.k_count - 1];
75
76 /* See if there is a matching character -- default to the same */
77 lbal = rbal;
78 for (i = 0; bal[i].right != '\0'; i++)
79 if (bal[i].right == rbal) {
80 lbal = bal[i].left;
81 break;
82 }
83
84 /*
85 * Move behind the inserted character. We are always guaranteed
86 * that there is at least one character on the line.
87 */
88 clp = curwp->w_dotp;
89 cbo = curwp->w_doto - 1;
90
91 /* init nesting depth */
92 depth = 0;
93
94 for (;;) {
95 if (cbo == 0) {
96 clp = lback(clp); /* beginning of line */
97 if (clp == curbp->b_headp)
98 return (FALSE);
99 cbo = llength(clp) + 1;
100 }
101 if (--cbo == llength(clp))
102 c = *curbp->b_nlchr; /* end of line */
103 else
104 c = lgetc(clp, cbo); /* somewhere in middle */
105
106 /*
107 * Check for a matching character. If still in a nested
108 * level, pop out of it and continue search. This check
109 * is done before the nesting check so single-character
110 * matches will work too.
111 */
112 if (c == lbal) {
113 if (depth == 0) {
114 displaymatch(clp, cbo);
115 return (TRUE);
116 } else
117 depth--;
118 }
119 /* Check for another level of nesting. */
120 if (c == rbal)
121 depth++;
122 }
123 /* NOTREACHED */
124 }
125
126 /*
127 * Display matching character. Matching characters that are not in the
128 * current window are displayed in the echo line. If in the current window,
129 * move dot to the matching character, sit there a while, then move back.
130 */
131 static void
displaymatch(struct line * clp,int cbo)132 displaymatch(struct line *clp, int cbo)
133 {
134 struct line *tlp;
135 int tbo;
136 int cp;
137 int bufo;
138 int c;
139 int col;
140 int inwindow;
141 char buf[NLINE];
142
143 /*
144 * Figure out if matching char is in current window by
145 * searching from the top of the window to dot.
146 */
147 inwindow = FALSE;
148 for (tlp = curwp->w_linep; tlp != lforw(curwp->w_dotp);
149 tlp = lforw(tlp))
150 if (tlp == clp)
151 inwindow = TRUE;
152
153 if (inwindow == TRUE) {
154 tlp = curwp->w_dotp; /* save current position */
155 tbo = curwp->w_doto;
156
157 curwp->w_dotp = clp; /* move to new position */
158 curwp->w_doto = cbo;
159 curwp->w_rflag |= WFMOVE;
160
161 update(CMODE); /* show match */
162 ttwait(1000); /* wait for key or 1 second */
163
164 curwp->w_dotp = tlp; /* return to old position */
165 curwp->w_doto = tbo;
166 curwp->w_rflag |= WFMOVE;
167 update(CMODE);
168 } else {
169 /* match is not in this window, so display line in echo area */
170 bufo = 0;
171 for (cp = 0; cp < llength(clp); cp++) {
172 if (bufo >= sizeof(buf) - 1)
173 break;
174
175 c = lgetc(clp, cp);
176 if (c != '\t') {
177 if (ISCTRL(c)) {
178 if (bufo >= sizeof(buf) - 3)
179 break;
180 buf[bufo++] = '^';
181 buf[bufo++] = CCHR(c);
182 } else
183 buf[bufo++] = c;
184 } else {
185 col = ntabstop(bufo, curbp->b_tabw);
186 while (bufo < col && bufo < sizeof(buf) - 1)
187 buf[bufo++] = ' ';
188 }
189 }
190 buf[bufo++] = '\0';
191 ewprintf("Matches %s", buf);
192 }
193 }
194