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