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