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