1 |
|
|
/* $OpenBSD: cmode.c,v 1.16 2015/09/26 21:51:58 jasper Exp $ */ |
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 |
|
|
|
13 |
|
|
#include <sys/queue.h> |
14 |
|
|
#include <ctype.h> |
15 |
|
|
#include <signal.h> |
16 |
|
|
#include <stdio.h> |
17 |
|
|
|
18 |
|
|
#include "def.h" |
19 |
|
|
#include "funmap.h" |
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 *); |
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 |
|
|
|
37 |
|
|
void cmode_init(void); |
38 |
|
|
int cc_comment(int, int); |
39 |
|
|
|
40 |
|
|
/* Keymaps */ |
41 |
|
|
|
42 |
|
|
static PF cmode_brace[] = { |
43 |
|
|
cc_brace, /* } */ |
44 |
|
|
}; |
45 |
|
|
|
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 */ |
58 |
|
|
cc_tab, /* ^I */ |
59 |
|
|
rescan, /* ^J */ |
60 |
|
|
rescan, /* ^K */ |
61 |
|
|
rescan, /* ^L */ |
62 |
|
|
cc_lfindent, /* ^M */ |
63 |
|
|
}; |
64 |
|
|
|
65 |
|
|
static PF cmode_spec[] = { |
66 |
|
|
cc_char, /* : */ |
67 |
|
|
}; |
68 |
|
|
|
69 |
|
|
static struct KEYMAPE (1) cmode_cmap = { |
70 |
|
|
1, |
71 |
|
|
1, |
72 |
|
|
rescan, |
73 |
|
|
{ |
74 |
|
|
{ 'P', 'P', cmode_cCP, NULL } |
75 |
|
|
} |
76 |
|
|
}; |
77 |
|
|
|
78 |
|
|
static struct KEYMAPE (3) cmodemap = { |
79 |
|
|
3, |
80 |
|
|
3, |
81 |
|
|
rescan, |
82 |
|
|
{ |
83 |
|
|
{ CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap }, |
84 |
|
|
{ ':', ':', cmode_spec, NULL }, |
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"); |
96 |
|
|
funmap_add(cc_brace, "c-handle-special-brace"); |
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"); |
100 |
|
|
maps_add((KEYMAP *)&cmodemap, "c"); |
101 |
|
|
} |
102 |
|
|
|
103 |
|
|
/* |
104 |
|
|
* Enable/toggle c-mode |
105 |
|
|
*/ |
106 |
|
|
int |
107 |
|
|
cmode(int f, int n) |
108 |
|
|
{ |
109 |
|
|
return(changemode(f, n, "c")); |
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 |
|
|
} |
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 |
|
|
|
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? */ |
148 |
|
|
|
149 |
|
|
inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp)); |
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 |
|
|
{ |
164 |
|
|
int pi, mi; /* Previous indents (mi is ignored) */ |
165 |
|
|
int ci; /* current indent */ |
166 |
|
|
struct line *lp; |
167 |
|
|
int ret; |
168 |
|
|
|
169 |
|
|
if (n < 0) |
170 |
|
|
return (FALSE); |
171 |
|
|
|
172 |
|
|
undo_boundary_enable(FFRAND, 0); |
173 |
|
|
if (cc_strip_trailp) |
174 |
|
|
deltrailwhite(FFRAND, 1); |
175 |
|
|
|
176 |
|
|
/* |
177 |
|
|
* Search backwards for a non-blank, non-preprocessor, |
178 |
|
|
* non-comment line |
179 |
|
|
*/ |
180 |
|
|
|
181 |
|
|
lp = findnonblank(curwp->w_dotp); |
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 */ |
188 |
|
|
(void)getindent(curwp->w_dotp, &ci); |
189 |
|
|
|
190 |
|
|
if (pi + ci < 0) |
191 |
|
|
ret = indent(FFOTHARG, 0); |
192 |
|
|
else |
193 |
|
|
ret = indent(FFOTHARG, pi + ci); |
194 |
|
|
|
195 |
|
|
undo_boundary_enable(FFRAND, 1); |
196 |
|
|
|
197 |
|
|
return (ret); |
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); |
208 |
|
|
if (enewline(FFRAND, 1) == FALSE) |
209 |
|
|
return (FALSE); |
210 |
|
|
return (cc_indent(FFRAND, n)); |
211 |
|
|
} |
212 |
|
|
|
213 |
|
|
/* |
214 |
|
|
* Get the level of indention after line lp is processed |
215 |
|
|
* Note getindent has two returns: |
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 */ |
232 |
|
|
int firstnwsp = FALSE; /* First nonspace encountered? */ |
233 |
|
|
int colonp = FALSE; /* Did we see a colon? */ |
234 |
|
|
int questionp = FALSE; /* Did we see a question mark? */ |
235 |
|
|
int slashp = FALSE; /* Slash? */ |
236 |
|
|
int astp = FALSE; /* Asterisk? */ |
237 |
|
|
int cpos = -1; /* comment position */ |
238 |
|
|
int cppp = FALSE; /* Preprocessor command? */ |
239 |
|
|
|
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 |
248 |
|
|
&& !(curbp->b_flag & BFNOTAB) |
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 */ |
265 |
|
|
if (!firstnwsp && !isspace(c)) { |
266 |
|
|
if (c == '#') |
267 |
|
|
cppp = TRUE; |
268 |
|
|
firstnwsp = TRUE; |
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++; |
287 |
|
|
firstnwsp = FALSE; |
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; |
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; |
313 |
|
|
} |
314 |
|
|
|
315 |
|
|
/* Reset matches that apply to next character only */ |
316 |
|
|
if (c != '\\') |
317 |
|
|
escp = FALSE; |
318 |
|
|
if (c != '*') |
319 |
|
|
astp = FALSE; |
320 |
|
|
if (c != '/') |
321 |
|
|
slashp = FALSE; |
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; |
339 |
|
|
|
340 |
|
|
*curi += nicol; |
341 |
|
|
|
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 |
|
|
|
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); |
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); |
522 |
|
|
} |