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