xref: /openbsd-src/usr.bin/mg/cmode.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /* $OpenBSD: cmode.c,v 1.8 2012/05/18 02:13:44 lum Exp $ */
2 /*
3  * This file is in the public domain.
4  *
5  * Author: Kjell Wooding <kjell@openbsd.org>
6  */
7 
8 /*
9  * Implement an non-irritating KNF-compliant mode for editing
10  * C code.
11  */
12 #include <ctype.h>
13 
14 #include "def.h"
15 #include "kbd.h"
16 #include "funmap.h"
17 
18 /* Pull in from modes.c */
19 extern int changemode(int, int, char *);
20 
21 static int cc_strip_trailp = TRUE;	/* Delete Trailing space? */
22 static int cc_basic_indent = 8;		/* Basic Indent multiple */
23 static int cc_cont_indent = 4;		/* Continued line indent */
24 static int cc_colon_indent = -8;	/* Label / case indent */
25 
26 static int getmatch(int, int);
27 static int getindent(const struct line *, int *);
28 static int in_whitespace(struct line *, int);
29 static int findcolpos(const struct buffer *, const struct line *, int);
30 static struct line *findnonblank(struct line *);
31 static int isnonblank(const struct line *, int);
32 
33 void cmode_init(void);
34 int cc_comment(int, int);
35 
36 /* Keymaps */
37 
38 static PF cmode_brace[] = {
39 	cc_brace,	/* } */
40 };
41 
42 static PF cmode_cCP[] = {
43 	compile,		/* C-c P */
44 };
45 
46 
47 static PF cmode_cc[] = {
48 	NULL,		/* ^C */
49 	rescan,		/* ^D */
50 	rescan,		/* ^E */
51 	rescan,		/* ^F */
52 	rescan,		/* ^G */
53 	rescan,		/* ^H */
54 	cc_tab,		/* ^I */
55 	rescan,		/* ^J */
56 	rescan,		/* ^K */
57 	rescan,		/* ^L */
58 	cc_lfindent,	/* ^M */
59 };
60 
61 static PF cmode_spec[] = {
62 	cc_char,	/* : */
63 };
64 
65 static struct KEYMAPE (1 + IMAPEXT) cmode_cmap = {
66 	1,
67 	1 + IMAPEXT,
68 	rescan,
69 	{
70 		{ 'P', 'P', cmode_cCP, NULL }
71 	}
72 };
73 
74 static struct KEYMAPE (3 + IMAPEXT) cmodemap = {
75 	3,
76 	3 + IMAPEXT,
77 	rescan,
78 	{
79 		{ CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap },
80 		{ ':', ':', cmode_spec, NULL },
81 		{ '}', '}', cmode_brace, NULL }
82 	}
83 };
84 
85 /* Funtion, Mode hooks */
86 
87 void
88 cmode_init(void)
89 {
90 	funmap_add(cmode, "c-mode");
91 	funmap_add(cc_char, "c-handle-special-char");
92 	funmap_add(cc_brace, "c-handle-special-brace");
93 	funmap_add(cc_tab, "c-tab-or-indent");
94 	funmap_add(cc_indent, "c-indent");
95 	funmap_add(cc_lfindent, "c-indent-and-newline");
96 	maps_add((KEYMAP *)&cmodemap, "c");
97 }
98 
99 /*
100  * Enable/toggle c-mode
101  */
102 int
103 cmode(int f, int n)
104 {
105 	return(changemode(f, n, "c"));
106 }
107 
108 /*
109  * Handle special C character - selfinsert then indent.
110  */
111 int
112 cc_char(int f, int n)
113 {
114 	if (n < 0)
115 		return (FALSE);
116 	if (selfinsert(FFRAND, n) == FALSE)
117 		return (FALSE);
118 	return (cc_indent(FFRAND, n));
119 }
120 
121 /*
122  * Handle special C character - selfinsert then indent.
123  */
124 int
125 cc_brace(int f, int n)
126 {
127 	if (n < 0)
128 		return (FALSE);
129 	if (showmatch(FFRAND, 1) == FALSE)
130 		return (FALSE);
131 	return (cc_indent(FFRAND, n));
132 }
133 
134 
135 /*
136  * If we are in the whitespace at the beginning of the line,
137  * simply act as a regular tab. If we are not, indent
138  * current line according to whitespace rules.
139  */
140 int
141 cc_tab(int f, int n)
142 {
143 	int inwhitep = FALSE;	/* In leading whitespace? */
144 
145 	inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp));
146 
147 	/* If empty line, or in whitespace */
148 	if (llength(curwp->w_dotp) == 0 || inwhitep)
149 		return (selfinsert(f, n));
150 
151 	return (cc_indent(FFRAND, 1));
152 }
153 
154 /*
155  * Attempt to indent current line according to KNF rules.
156  */
157 int
158 cc_indent(int f, int n)
159 {
160 	int pi, mi;			/* Previous indents */
161 	int ci, dci;			/* current indent, don't care */
162 	struct line *lp;
163 	int ret;
164 
165 	if (n < 0)
166 		return (FALSE);
167 
168 	undo_boundary_enable(FFRAND, 0);
169 	if (cc_strip_trailp)
170 		deltrailwhite(FFRAND, 1);
171 
172 	/*
173 	 * Search backwards for a non-blank, non-preprocessor,
174 	 * non-comment line
175 	 */
176 
177 	lp = findnonblank(curwp->w_dotp);
178 
179 	pi = getindent(lp, &mi);
180 
181 	/* Strip leading space on current line */
182 	delleadwhite(FFRAND, 1);
183 	/* current indent is computed only to current position */
184 	dci = getindent(curwp->w_dotp, &ci);
185 
186 	if (pi + ci < 0)
187 		ret = indent(FFOTHARG, 0);
188 	else
189 		ret = indent(FFOTHARG, pi + ci);
190 
191 	undo_boundary_enable(FFRAND, 1);
192 
193 	return (ret);
194 }
195 
196 /*
197  * Indent-and-newline (technically, newline then indent)
198  */
199 int
200 cc_lfindent(int f, int n)
201 {
202 	if (n < 0)
203 		return (FALSE);
204 	if (newline(FFRAND, 1) == FALSE)
205 		return (FALSE);
206 	return (cc_indent(FFRAND, n));
207 }
208 
209 /*
210  * Get the level of indention after line lp is processed
211  * Note getindent has two returns:
212  * curi = value if indenting current line.
213  * return value = value affecting subsequent lines.
214  */
215 static int
216 getindent(const struct line *lp, int *curi)
217 {
218 	int lo, co;		/* leading space,  current offset*/
219 	int nicol = 0;		/* position count */
220 	int ccol = 0;		/* current column */
221 	int c = '\0';		/* current char */
222 	int newind = 0;		/* new index value */
223 	int stringp = FALSE;	/* in string? */
224 	int escp = FALSE;	/* Escape char? */
225 	int lastc = '\0';	/* Last matched string delimeter */
226 	int nparen = 0;		/* paren count */
227 	int obrace = 0;		/* open brace count */
228 	int cbrace = 0;		/* close brace count */
229 	int contp = FALSE;	/* Continue? */
230 	int firstnwsp = FALSE;	/* First nonspace encountered? */
231 	int colonp = FALSE;	/* Did we see a colon? */
232 	int questionp = FALSE;	/* Did we see a question mark? */
233 	int slashp = FALSE;	/* Slash? */
234 	int astp = FALSE;	/* Asterisk? */
235 	int cpos = -1;		/* comment position */
236 	int cppp  = FALSE;	/* Preprocessor command? */
237 
238 	*curi = 0;
239 
240 	/* Compute leading space */
241 	for (lo = 0; lo < llength(lp); lo++) {
242 		if (!isspace(c = lgetc(lp, lo)))
243 			break;
244 		if (c == '\t'
245 #ifdef NOTAB
246 		    && !(curbp->b_flag & BFNOTAB)
247 #endif /* NOTAB */
248 		    ) {
249 			nicol |= 0x07;
250 		}
251 		nicol++;
252 	}
253 
254 	/* If last line was blank, choose 0 */
255 	if (lo == llength(lp))
256 		nicol = 0;
257 
258 	newind = 0;
259 	ccol = nicol;			/* current column */
260 	/* Compute modifiers */
261 	for (co = lo; co < llength(lp); co++) {
262 		c = lgetc(lp, co);
263 		/* We have a non-whitespace char */
264 		if (!firstnwsp && !isspace(c)) {
265 			contp = TRUE;
266 			if (c == '#')
267 				cppp = TRUE;
268 			firstnwsp = TRUE;
269 		}
270 		if (c == '\\')
271 			escp = !escp;
272 		else if (stringp) {
273 			if (!escp && (c == '"' || c == '\'')) {
274 				/* unescaped string char */
275 				if (getmatch(c, lastc))
276 					stringp = FALSE;
277 			}
278 		} else if (c == '"' || c == '\'') {
279 			stringp = TRUE;
280 			lastc = c;
281 		} else if (c == '(') {
282 			nparen++;
283 		} else if (c == ')') {
284 			nparen--;
285 		} else if (c == '{') {
286 			obrace++;
287 			firstnwsp = FALSE;
288 			contp = FALSE;
289 		} else if (c == '}') {
290 			cbrace++;
291 		} else if (c == '?') {
292 			questionp = TRUE;
293 		} else if (c == ':') {
294 			/* ignore (foo ? bar : baz) construct */
295 			if (!questionp)
296 				colonp = TRUE;
297 		} else if (c == ';') {
298 			if (nparen > 0)
299 				contp = FALSE;
300 		} else if (c == '/') {
301 			/* first nonwhitespace? -> indent */
302 			if (firstnwsp) {
303 				/* If previous char asterisk -> close */
304 				if (astp)
305 					cpos = -1;
306 				else
307 					slashp = TRUE;
308 			}
309 		} else if (c == '*') {
310 			/* If previous char slash -> open */
311 			if (slashp)
312 				cpos = co;
313 			else
314 				astp = TRUE;
315 		} else if (firstnwsp) {
316 			firstnwsp = FALSE;
317 		}
318 
319 		/* Reset matches that apply to next character only */
320 		if (c != '\\')
321 			escp = FALSE;
322 		if (c != '*')
323 			astp = FALSE;
324 		if (c != '/')
325 			slashp = FALSE;
326 	}
327 	/*
328 	 * If not terminated with a semicolon, and brace or paren open.
329 	 * we continue
330 	 */
331 	if (colonp) {
332 		*curi += cc_colon_indent;
333 		newind -= cc_colon_indent;
334 	}
335 
336 	*curi -= (cbrace) * cc_basic_indent;
337 	newind += obrace * cc_basic_indent;
338 
339 	if (nparen < 0)
340 		newind -= cc_cont_indent;
341 	else if (nparen > 0)
342 		newind += cc_cont_indent;
343 
344 	*curi += nicol;
345 
346 	/* Ignore preprocessor. Otherwise, add current column */
347 	if (cppp) {
348 		newind = nicol;
349 		*curi = 0;
350 	} else {
351 		newind += nicol;
352 	}
353 
354 	if (cpos != -1)
355 		newind = findcolpos(curbp, lp, cpos);
356 
357 	return (newind);
358 }
359 
360 /*
361  * Given a delimeter and its purported mate, tell us if they
362  * match.
363  */
364 static int
365 getmatch(int c, int mc)
366 {
367 	int match = FALSE;
368 
369 	switch (c) {
370 	case '"':
371 		match = (mc == '"');
372 		break;
373 	case '\'':
374 		match = (mc == '\'');
375 		break;
376 	case '(':
377 		match = (mc == ')');
378 		break;
379 	case '[':
380 		match = (mc == ']');
381 		break;
382 	case '{':
383 		match = (mc == '}');
384 		break;
385 	}
386 
387 	return (match);
388 }
389 
390 static int
391 in_whitespace(struct line *lp, int len)
392 {
393 	int lo;
394 	int inwhitep = FALSE;
395 
396 	for (lo = 0; lo < len; lo++) {
397 		if (!isspace(lgetc(lp, lo)))
398 			break;
399 		if (lo == len - 1)
400 			inwhitep = TRUE;
401 	}
402 
403 	return (inwhitep);
404 }
405 
406 
407 /* convert a line/offset pair to a column position (for indenting) */
408 static int
409 findcolpos(const struct buffer *bp, const struct line *lp, int lo)
410 {
411 	int	col, i, c;
412 	char tmp[5];
413 
414 	/* determine column */
415 	col = 0;
416 
417 	for (i = 0; i < lo; ++i) {
418 		c = lgetc(lp, i);
419 		if (c == '\t'
420 #ifdef NOTAB
421 		    && !(bp->b_flag & BFNOTAB)
422 #endif /* NOTAB */
423 			) {
424 			col |= 0x07;
425 			col++;
426 		} else if (ISCTRL(c) != FALSE)
427 			col += 2;
428 		else if (isprint(c)) {
429 			col++;
430 		} else {
431 			col += snprintf(tmp, sizeof(tmp), "\\%o", c);
432 		}
433 
434 	}
435 	return (col);
436 }
437 
438 /*
439  * Find a non-blank line, searching backwards from the supplied line pointer.
440  * For C, nonblank is non-preprocessor, non C++, and accounts
441  * for complete C-style comments.
442  */
443 static struct line *
444 findnonblank(struct line *lp)
445 {
446 	int lo;
447 	int nonblankp = FALSE;
448 	int commentp = FALSE;
449 	int slashp;
450 	int astp;
451 	int c;
452 
453 	while (lback(lp) != curbp->b_headp && (commentp || !nonblankp)) {
454 		lp = lback(lp);
455 		slashp = FALSE;
456 		astp = FALSE;
457 
458 		/* Potential nonblank? */
459 		nonblankp = isnonblank(lp, llength(lp));
460 
461 		/*
462 		 * Search from end, removing complete C-style
463 		 * comments. If one is found, ignore it and
464 		 * test for nonblankness from where it starts.
465 		 */
466 		slashp = FALSE;
467 		/* Scan backwards from end to find C-style comment */
468 		for (lo = llength(lp) - 1; lo >= 0; lo--) {
469 			if (!isspace(c = lgetc(lp, lo))) {
470 				if (commentp) { /* find comment "open" */
471 					if (c == '*')
472 						astp = TRUE;
473 					else if (astp && c == '/') {
474 						commentp = FALSE;
475 						/* whitespace to here? */
476 						nonblankp = isnonblank(lp, lo);
477 					}
478 				} else { /* find comment "close" */
479 					if (c == '/')
480 						slashp = TRUE;
481 					else if (slashp && c == '*')
482 						/* found a comment */
483 						commentp = TRUE;
484 				}
485 			}
486 		}
487 	}
488 
489 	/* Rewound to start of file? */
490 	if (lback(lp) == curbp->b_headp && !nonblankp)
491 		return (curbp->b_headp);
492 
493 	return (lp);
494 }
495 
496 /*
497  * Given a line, scan forward to 'omax' and determine if we
498  * are all C whitespace.
499  * Note that preprocessor directives and C++-style comments
500  * count as whitespace. C-style comments do not, and must
501  * be handled elsewhere.
502  */
503 static int
504 isnonblank(const struct line *lp, int omax)
505 {
506 	int nonblankp = FALSE;		/* Return value */
507 	int slashp = FALSE;		/* Encountered slash */
508 	int lo;				/* Loop index */
509 	int c;				/* char being read */
510 
511 	/* Scan from front for preprocessor, C++ comments */
512 	for (lo = 0; lo < omax; lo++) {
513 		if (!isspace(c = lgetc(lp, lo))) {
514 			/* Possible nonblank line */
515 			nonblankp = TRUE;
516 			/* skip // and # starts */
517 			if (c == '#' || (slashp && c == '/')) {
518 				nonblankp = FALSE;
519 				break;
520 			} else if (!slashp && c == '/') {
521 				slashp = TRUE;
522 				continue;
523 			}
524 		}
525 		slashp = FALSE;
526 	}
527 	return (nonblankp);
528 }
529