1 /*	$OpenBSD: match.c,v 1.22 2021/03/01 10:51:14 lum 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	 inwindow;
140 	char	 buf[NLINE];
141 
142 	/*
143 	 * Figure out if matching char is in current window by
144 	 * searching from the top of the window to dot.
145 	 */
146 	inwindow = FALSE;
147 	for (tlp = curwp->w_linep; tlp != lforw(curwp->w_dotp);
148 	    tlp = lforw(tlp))
149 		if (tlp == clp)
150 			inwindow = TRUE;
151 
152 	if (inwindow == TRUE) {
153 		tlp = curwp->w_dotp;	/* save current position */
154 		tbo = curwp->w_doto;
155 
156 		curwp->w_dotp = clp;	/* move to new position */
157 		curwp->w_doto = cbo;
158 		curwp->w_rflag |= WFMOVE;
159 
160 		update(CMODE);		/* show match */
161 		ttwait(1000);		/* wait for key or 1 second */
162 
163 		curwp->w_dotp = tlp;	/* return to old position */
164 		curwp->w_doto = tbo;
165 		curwp->w_rflag |= WFMOVE;
166 		update(CMODE);
167 	} else {
168 		/* match is not in this window, so display line in echo area */
169 		bufo = 0;
170 		for (cp = 0; cp < llength(clp); cp++) {
171 			c = lgetc(clp, cp);
172 			if (c != '\t'
173 #ifdef NOTAB
174 			    || (curbp->b_flag & BFNOTAB)
175 #endif
176 				)
177 				if (ISCTRL(c)) {
178 					buf[bufo++] = '^';
179 					buf[bufo++] = CCHR(c);
180 				} else
181 					buf[bufo++] = c;
182 			else
183 				do {
184 					buf[bufo++] = ' ';
185 				} while (bufo & 7);
186 		}
187 		buf[bufo++] = '\0';
188 		ewprintf("Matches %s", buf);
189 	}
190 }
191