Annotation of src/usr.bin/mg/cmode.c, Revision 1.12
1.12 ! bcallah 1: /* $OpenBSD: cmode.c,v 1.11 2015/01/02 11:43:15 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: {
1.10 guenther 160: int pi, mi; /* Previous indents (mi is ignored) */
161: int ci; /* current indent */
1.1 kjell 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 */
1.10 guenther 184: (void)getindent(curwp->w_dotp, &ci);
1.1 kjell 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 */
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 */
1.2 kjell 228: int firstnwsp = FALSE; /* First nonspace encountered? */
1.1 kjell 229: int colonp = FALSE; /* Did we see a colon? */
230: int questionp = FALSE; /* Did we see a question mark? */
1.2 kjell 231: int slashp = FALSE; /* Slash? */
232: int astp = FALSE; /* Asterisk? */
233: int cpos = -1; /* comment position */
234: int cppp = FALSE; /* Preprocessor command? */
1.1 kjell 235:
236: *curi = 0;
237:
238: /* Compute leading space */
239: for (lo = 0; lo < llength(lp); lo++) {
240: if (!isspace(c = lgetc(lp, lo)))
241: break;
242: if (c == '\t'
243: #ifdef NOTAB
1.8 lum 244: && !(curbp->b_flag & BFNOTAB)
1.1 kjell 245: #endif /* NOTAB */
246: ) {
247: nicol |= 0x07;
248: }
249: nicol++;
250: }
251:
252: /* If last line was blank, choose 0 */
253: if (lo == llength(lp))
254: nicol = 0;
255:
256: newind = 0;
257: /* Compute modifiers */
258: for (co = lo; co < llength(lp); co++) {
259: c = lgetc(lp, co);
260: /* We have a non-whitespace char */
1.2 kjell 261: if (!firstnwsp && !isspace(c)) {
262: if (c == '#')
263: cppp = TRUE;
264: firstnwsp = TRUE;
1.1 kjell 265: }
266: if (c == '\\')
267: escp = !escp;
268: else if (stringp) {
269: if (!escp && (c == '"' || c == '\'')) {
270: /* unescaped string char */
271: if (getmatch(c, lastc))
272: stringp = FALSE;
273: }
274: } else if (c == '"' || c == '\'') {
275: stringp = TRUE;
276: lastc = c;
277: } else if (c == '(') {
278: nparen++;
279: } else if (c == ')') {
280: nparen--;
281: } else if (c == '{') {
282: obrace++;
1.2 kjell 283: firstnwsp = FALSE;
1.1 kjell 284: } else if (c == '}') {
285: cbrace++;
286: } else if (c == '?') {
287: questionp = TRUE;
288: } else if (c == ':') {
289: /* ignore (foo ? bar : baz) construct */
290: if (!questionp)
291: colonp = TRUE;
1.2 kjell 292: } else if (c == '/') {
293: /* first nonwhitespace? -> indent */
294: if (firstnwsp) {
295: /* If previous char asterisk -> close */
296: if (astp)
297: cpos = -1;
298: else
299: slashp = TRUE;
300: }
301: } else if (c == '*') {
302: /* If previous char slash -> open */
303: if (slashp)
304: cpos = co;
305: else
306: astp = TRUE;
307: } else if (firstnwsp) {
308: firstnwsp = FALSE;
1.1 kjell 309: }
310:
1.2 kjell 311: /* Reset matches that apply to next character only */
1.1 kjell 312: if (c != '\\')
313: escp = FALSE;
1.2 kjell 314: if (c != '*')
315: astp = FALSE;
316: if (c != '/')
317: slashp = FALSE;
1.1 kjell 318: }
319: /*
320: * If not terminated with a semicolon, and brace or paren open.
321: * we continue
322: */
323: if (colonp) {
324: *curi += cc_colon_indent;
325: newind -= cc_colon_indent;
326: }
327:
328: *curi -= (cbrace) * cc_basic_indent;
329: newind += obrace * cc_basic_indent;
330:
331: if (nparen < 0)
332: newind -= cc_cont_indent;
333: else if (nparen > 0)
334: newind += cc_cont_indent;
1.2 kjell 335:
1.1 kjell 336: *curi += nicol;
337:
1.2 kjell 338: /* Ignore preprocessor. Otherwise, add current column */
339: if (cppp) {
340: newind = nicol;
341: *curi = 0;
342: } else {
343: newind += nicol;
344: }
345:
346: if (cpos != -1)
347: newind = findcolpos(curbp, lp, cpos);
348:
1.1 kjell 349: return (newind);
350: }
351:
352: /*
353: * Given a delimeter and its purported mate, tell us if they
354: * match.
355: */
356: static int
357: getmatch(int c, int mc)
358: {
359: int match = FALSE;
360:
361: switch (c) {
362: case '"':
363: match = (mc == '"');
364: break;
365: case '\'':
366: match = (mc == '\'');
367: break;
368: case '(':
369: match = (mc == ')');
370: break;
371: case '[':
372: match = (mc == ']');
373: break;
374: case '{':
375: match = (mc == '}');
376: break;
377: }
378:
379: return (match);
1.2 kjell 380: }
381:
382: static int
383: in_whitespace(struct line *lp, int len)
384: {
385: int lo;
386: int inwhitep = FALSE;
387:
388: for (lo = 0; lo < len; lo++) {
389: if (!isspace(lgetc(lp, lo)))
390: break;
391: if (lo == len - 1)
392: inwhitep = TRUE;
393: }
394:
395: return (inwhitep);
396: }
397:
398:
399: /* convert a line/offset pair to a column position (for indenting) */
400: static int
401: findcolpos(const struct buffer *bp, const struct line *lp, int lo)
402: {
403: int col, i, c;
404: char tmp[5];
405:
406: /* determine column */
407: col = 0;
408:
409: for (i = 0; i < lo; ++i) {
410: c = lgetc(lp, i);
411: if (c == '\t'
412: #ifdef NOTAB
413: && !(bp->b_flag & BFNOTAB)
414: #endif /* NOTAB */
415: ) {
416: col |= 0x07;
417: col++;
418: } else if (ISCTRL(c) != FALSE)
419: col += 2;
420: else if (isprint(c)) {
421: col++;
422: } else {
423: col += snprintf(tmp, sizeof(tmp), "\\%o", c);
424: }
425:
426: }
427: return (col);
428: }
429:
430: /*
431: * Find a non-blank line, searching backwards from the supplied line pointer.
432: * For C, nonblank is non-preprocessor, non C++, and accounts
433: * for complete C-style comments.
434: */
435: static struct line *
436: findnonblank(struct line *lp)
437: {
438: int lo;
439: int nonblankp = FALSE;
440: int commentp = FALSE;
441: int slashp;
442: int astp;
443: int c;
444:
445: while (lback(lp) != curbp->b_headp && (commentp || !nonblankp)) {
446: lp = lback(lp);
447: slashp = FALSE;
448: astp = FALSE;
449:
450: /* Potential nonblank? */
451: nonblankp = isnonblank(lp, llength(lp));
452:
453: /*
454: * Search from end, removing complete C-style
455: * comments. If one is found, ignore it and
456: * test for nonblankness from where it starts.
457: */
458: for (lo = llength(lp) - 1; lo >= 0; lo--) {
459: if (!isspace(c = lgetc(lp, lo))) {
460: if (commentp) { /* find comment "open" */
461: if (c == '*')
462: astp = TRUE;
463: else if (astp && c == '/') {
464: commentp = FALSE;
465: /* whitespace to here? */
466: nonblankp = isnonblank(lp, lo);
467: }
468: } else { /* find comment "close" */
469: if (c == '/')
470: slashp = TRUE;
471: else if (slashp && c == '*')
472: /* found a comment */
473: commentp = TRUE;
474: }
475: }
476: }
477: }
478:
479: /* Rewound to start of file? */
480: if (lback(lp) == curbp->b_headp && !nonblankp)
481: return (curbp->b_headp);
482:
483: return (lp);
484: }
485:
486: /*
487: * Given a line, scan forward to 'omax' and determine if we
488: * are all C whitespace.
489: * Note that preprocessor directives and C++-style comments
490: * count as whitespace. C-style comments do not, and must
491: * be handled elsewhere.
492: */
493: static int
494: isnonblank(const struct line *lp, int omax)
495: {
496: int nonblankp = FALSE; /* Return value */
497: int slashp = FALSE; /* Encountered slash */
498: int lo; /* Loop index */
499: int c; /* char being read */
500:
501: /* Scan from front for preprocessor, C++ comments */
502: for (lo = 0; lo < omax; lo++) {
503: if (!isspace(c = lgetc(lp, lo))) {
504: /* Possible nonblank line */
505: nonblankp = TRUE;
506: /* skip // and # starts */
507: if (c == '#' || (slashp && c == '/')) {
508: nonblankp = FALSE;
509: break;
510: } else if (!slashp && c == '/') {
511: slashp = TRUE;
512: continue;
513: }
514: }
515: slashp = FALSE;
516: }
517: return (nonblankp);
1.1 kjell 518: }