Annotation of src/usr.bin/mg/cmode.c, Revision 1.6
1.6 ! sobrado 1: /* $OpenBSD: cmode.c,v 1.5 2008/09/15 16:13:35 kjell Exp $ */
1.1 kjell 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 *);
1.2 kjell 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);
1.1 kjell 34:
35: /* Keymaps */
36:
37: static PF cmode_brace[] = {
1.3 kjell 38: cc_brace, /* } */
1.1 kjell 39: };
40:
1.4 kjell 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 */
1.1 kjell 53: cc_tab, /* ^I */
54: rescan, /* ^J */
55: rescan, /* ^K */
56: rescan, /* ^L */
57: cc_lfindent, /* ^M */
58: };
59:
1.2 kjell 60: static PF cmode_spec[] = {
1.1 kjell 61: cc_char, /* : */
62: };
63:
1.4 kjell 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:
1.1 kjell 73: static struct KEYMAPE (3 + IMAPEXT) cmodemap = {
74: 3,
75: 3 + IMAPEXT,
76: rescan,
77: {
1.4 kjell 78: { CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap },
1.2 kjell 79: { ':', ':', cmode_spec, NULL },
1.1 kjell 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");
1.3 kjell 91: funmap_add(cc_brace, "c-handle-special-brace");
1.1 kjell 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");
1.6 ! sobrado 95: maps_add((KEYMAP *)&cmodemap, "c");
1.1 kjell 96: }
97:
98: /*
99: * Enable/toggle c-mode
100: */
101: int
102: cmode(int f, int n)
103: {
1.6 ! sobrado 104: return(changemode(f, n, "c"));
1.1 kjell 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: }
1.3 kjell 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:
1.1 kjell 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:
1.2 kjell 144: inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp));
1.1 kjell 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;
1.4 kjell 162: int ret;
163:
1.1 kjell 164: if (n < 0)
165: return (FALSE);
166:
1.5 kjell 167: undo_boundary_enable(FFRAND, 0);
1.1 kjell 168: if (cc_strip_trailp)
169: deltrailwhite(FFRAND, 1);
170:
1.2 kjell 171: /*
172: * Search backwards for a non-blank, non-preprocessor,
173: * non-comment line
174: */
175:
176: lp = findnonblank(curwp->w_dotp);
1.1 kjell 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)
1.4 kjell 186: ret = indent(FFOTHARG, 0);
1.1 kjell 187: else
1.4 kjell 188: ret = indent(FFOTHARG, pi + ci);
189:
1.5 kjell 190: undo_boundary_enable(FFRAND, 1);
1.4 kjell 191:
192: return (ret);
1.1 kjell 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
1.2 kjell 210: * Note getindent has two returns:
1.1 kjell 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 */
1.2 kjell 219: int ccol = 0; /* current column */
1.1 kjell 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? */
1.2 kjell 229: int firstnwsp = FALSE; /* First nonspace encountered? */
1.1 kjell 230: int colonp = FALSE; /* Did we see a colon? */
231: int questionp = FALSE; /* Did we see a question mark? */
1.2 kjell 232: int slashp = FALSE; /* Slash? */
233: int astp = FALSE; /* Asterisk? */
234: int cpos = -1; /* comment position */
235: int cppp = FALSE; /* Preprocessor command? */
1.1 kjell 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;
1.2 kjell 258: ccol = nicol; /* current column */
1.1 kjell 259: /* Compute modifiers */
260: for (co = lo; co < llength(lp); co++) {
261: c = lgetc(lp, co);
262: /* We have a non-whitespace char */
1.2 kjell 263: if (!firstnwsp && !isspace(c)) {
1.1 kjell 264: contp = TRUE;
1.2 kjell 265: if (c == '#')
266: cppp = TRUE;
267: firstnwsp = TRUE;
1.1 kjell 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++;
1.2 kjell 286: firstnwsp = FALSE;
1.1 kjell 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;
1.2 kjell 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;
1.1 kjell 316: }
317:
1.2 kjell 318: /* Reset matches that apply to next character only */
1.1 kjell 319: if (c != '\\')
320: escp = FALSE;
1.2 kjell 321: if (c != '*')
322: astp = FALSE;
323: if (c != '/')
324: slashp = FALSE;
1.1 kjell 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;
1.2 kjell 342:
1.1 kjell 343: *curi += nicol;
344:
1.2 kjell 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:
1.1 kjell 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);
1.2 kjell 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);
1.1 kjell 527: }