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