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