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