]> pd.if.org Git - pd_readline/blob - mg/cmode.c
Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg...
[pd_readline] / mg / cmode.c
1 /* $OpenBSD: cmode.c,v 1.8 2012/05/18 02:13:44 lum 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 #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 *);
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 void cmode_init(void);
34 int cc_comment(int, int);
35
36 /* Keymaps */
37
38 static PF cmode_brace[] = {
39         cc_brace,       /* } */
40 };
41
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 */
54         cc_tab,         /* ^I */
55         rescan,         /* ^J */
56         rescan,         /* ^K */
57         rescan,         /* ^L */
58         cc_lfindent,    /* ^M */
59 };
60
61 static PF cmode_spec[] = {
62         cc_char,        /* : */
63 };
64
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
74 static struct KEYMAPE (3 + IMAPEXT) cmodemap = {
75         3,
76         3 + IMAPEXT,
77         rescan,
78         {
79                 { CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap },
80                 { ':', ':', cmode_spec, NULL },
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");
92         funmap_add(cc_brace, "c-handle-special-brace");
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");
96         maps_add((KEYMAP *)&cmodemap, "c");
97 }
98
99 /*
100  * Enable/toggle c-mode
101  */
102 int
103 cmode(int f, int n)
104 {
105         return(changemode(f, n, "c"));
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 }
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
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         
145         inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp));
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 {
160         int pi, mi;                     /* Previous indents */
161         int ci, dci;                    /* current indent, don't care */
162         struct line *lp;
163         int ret;
164         
165         if (n < 0)
166                 return (FALSE);
167
168         undo_boundary_enable(FFRAND, 0);
169         if (cc_strip_trailp)
170                 deltrailwhite(FFRAND, 1);
171
172         /*
173          * Search backwards for a non-blank, non-preprocessor,
174          * non-comment line
175          */
176
177         lp = findnonblank(curwp->w_dotp);
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 */
184         dci = getindent(curwp->w_dotp, &ci);
185         
186         if (pi + ci < 0)
187                 ret = indent(FFOTHARG, 0);
188         else
189                 ret = indent(FFOTHARG, pi + ci);
190         
191         undo_boundary_enable(FFRAND, 1);
192         
193         return (ret);
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
211  * Note getindent has two returns:
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 ccol = 0;           /* current column */
221         int c = '\0';           /* current char */
222         int newind = 0;         /* new index value */
223         int stringp = FALSE;    /* in string? */
224         int escp = FALSE;       /* Escape char? */
225         int lastc = '\0';       /* Last matched string delimeter */
226         int nparen = 0;         /* paren count */
227         int obrace = 0;         /* open brace count */
228         int cbrace = 0;         /* close brace count */
229         int contp = FALSE;      /* Continue? */
230         int firstnwsp = FALSE;  /* First nonspace encountered? */
231         int colonp = FALSE;     /* Did we see a colon? */
232         int questionp = FALSE;  /* Did we see a question mark? */
233         int slashp = FALSE;     /* Slash? */
234         int astp = FALSE;       /* Asterisk? */
235         int cpos = -1;          /* comment position */
236         int cppp  = FALSE;      /* Preprocessor command? */
237         
238         *curi = 0;
239
240         /* Compute leading space */
241         for (lo = 0; lo < llength(lp); lo++) {
242                 if (!isspace(c = lgetc(lp, lo)))
243                         break;
244                 if (c == '\t'
245 #ifdef NOTAB
246                     && !(curbp->b_flag & BFNOTAB)
247 #endif /* NOTAB */
248                     ) {
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         ccol = nicol;                   /* current column */
260         /* Compute modifiers */
261         for (co = lo; co < llength(lp); co++) {
262                 c = lgetc(lp, co);
263                 /* We have a non-whitespace char */
264                 if (!firstnwsp && !isspace(c)) {
265                         contp = TRUE;
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                         contp = FALSE;
289                 } else if (c == '}') {
290                         cbrace++;
291                 } else if (c == '?') {
292                         questionp = TRUE;
293                 } else if (c == ':') {
294                         /* ignore (foo ? bar : baz) construct */
295                         if (!questionp)
296                                 colonp = TRUE;
297                 } else if (c == ';') {
298                         if (nparen > 0)
299                                 contp = FALSE;
300                 } else if (c == '/') {
301                         /* first nonwhitespace? -> indent */
302                         if (firstnwsp) {
303                                 /* If previous char asterisk -> close */
304                                 if (astp)
305                                         cpos = -1;
306                                 else
307                                         slashp = TRUE;
308                         }
309                 } else if (c == '*') {
310                         /* If previous char slash -> open */
311                         if (slashp)
312                                 cpos = co;
313                         else
314                                 astp = TRUE;
315                 } else if (firstnwsp) {
316                         firstnwsp = FALSE;
317                 }
318
319                 /* Reset matches that apply to next character only */
320                 if (c != '\\')
321                         escp = FALSE;
322                 if (c != '*')
323                         astp = FALSE;
324                 if (c != '/')
325                         slashp = FALSE;
326         }
327         /*
328          * If not terminated with a semicolon, and brace or paren open.
329          * we continue
330          */
331         if (colonp) {
332                 *curi += cc_colon_indent;
333                 newind -= cc_colon_indent;
334         }
335
336         *curi -= (cbrace) * cc_basic_indent;
337         newind += obrace * cc_basic_indent;
338
339         if (nparen < 0)
340                 newind -= cc_cont_indent;
341         else if (nparen > 0)
342                 newind += cc_cont_indent;
343
344         *curi += nicol;
345
346         /* Ignore preprocessor. Otherwise, add current column */
347         if (cppp) {
348                 newind = nicol;
349                 *curi = 0;
350         } else {
351                 newind += nicol;
352         }
353
354         if (cpos != -1)
355                 newind = findcolpos(curbp, lp, cpos);
356
357         return (newind);
358 }
359
360 /*
361  * Given a delimeter and its purported mate, tell us if they
362  * match.
363  */
364 static int
365 getmatch(int c, int mc)
366 {
367         int match = FALSE;
368
369         switch (c) {
370         case '"':
371                 match = (mc == '"');
372                 break;
373         case '\'':
374                 match = (mc == '\'');
375                 break;
376         case '(':
377                 match = (mc == ')');
378                 break;
379         case '[':
380                 match = (mc == ']');
381                 break;
382         case '{':
383                 match = (mc == '}');
384                 break;
385         }
386
387         return (match);
388 }
389
390 static int
391 in_whitespace(struct line *lp, int len)
392 {
393         int lo;
394         int inwhitep = FALSE;
395
396         for (lo = 0; lo < len; lo++) {
397                 if (!isspace(lgetc(lp, lo)))
398                         break;
399                 if (lo == len - 1)
400                         inwhitep = TRUE;
401         }
402
403         return (inwhitep);
404 }
405
406
407 /* convert a line/offset pair to a column position (for indenting) */
408 static int
409 findcolpos(const struct buffer *bp, const struct line *lp, int lo)
410 {
411         int     col, i, c;
412         char tmp[5];
413
414         /* determine column */
415         col = 0;
416
417         for (i = 0; i < lo; ++i) {
418                 c = lgetc(lp, i);
419                 if (c == '\t'
420 #ifdef NOTAB
421                     && !(bp->b_flag & BFNOTAB)
422 #endif /* NOTAB */
423                         ) {
424                         col |= 0x07;
425                         col++;
426                 } else if (ISCTRL(c) != FALSE)
427                         col += 2;
428                 else if (isprint(c)) {
429                         col++;
430                 } else {
431                         col += snprintf(tmp, sizeof(tmp), "\\%o", c);
432                 }
433
434         }
435         return (col);
436 }
437
438 /*
439  * Find a non-blank line, searching backwards from the supplied line pointer.
440  * For C, nonblank is non-preprocessor, non C++, and accounts
441  * for complete C-style comments.
442  */
443 static struct line *
444 findnonblank(struct line *lp)
445 {
446         int lo;
447         int nonblankp = FALSE;
448         int commentp = FALSE;
449         int slashp;
450         int astp;
451         int c;
452
453         while (lback(lp) != curbp->b_headp && (commentp || !nonblankp)) {
454                 lp = lback(lp);
455                 slashp = FALSE;
456                 astp = FALSE;
457
458                 /* Potential nonblank? */
459                 nonblankp = isnonblank(lp, llength(lp));
460
461                 /*
462                  * Search from end, removing complete C-style
463                  * comments. If one is found, ignore it and
464                  * test for nonblankness from where it starts.
465                  */
466                 slashp = FALSE;
467                 /* Scan backwards from end to find C-style comment */
468                 for (lo = llength(lp) - 1; lo >= 0; lo--) {
469                         if (!isspace(c = lgetc(lp, lo))) {
470                                 if (commentp) { /* find comment "open" */
471                                         if (c == '*')
472                                                 astp = TRUE;
473                                         else if (astp && c == '/') {
474                                                 commentp = FALSE;
475                                                 /* whitespace to here? */
476                                                 nonblankp = isnonblank(lp, lo);
477                                         }
478                                 } else { /* find comment "close" */
479                                         if (c == '/')
480                                                 slashp = TRUE;
481                                         else if (slashp && c == '*')
482                                                 /* found a comment */
483                                                 commentp = TRUE;
484                                 }
485                         }
486                 }
487         }
488
489         /* Rewound to start of file? */
490         if (lback(lp) == curbp->b_headp && !nonblankp)
491                 return (curbp->b_headp);
492
493         return (lp);
494 }
495
496 /*
497  * Given a line, scan forward to 'omax' and determine if we
498  * are all C whitespace.
499  * Note that preprocessor directives and C++-style comments
500  * count as whitespace. C-style comments do not, and must
501  * be handled elsewhere.
502  */
503 static int
504 isnonblank(const struct line *lp, int omax)
505 {
506         int nonblankp = FALSE;          /* Return value */
507         int slashp = FALSE;             /* Encountered slash */
508         int lo;                         /* Loop index */
509         int c;                          /* char being read */
510
511         /* Scan from front for preprocessor, C++ comments */
512         for (lo = 0; lo < omax; lo++) {
513                 if (!isspace(c = lgetc(lp, lo))) {
514                         /* Possible nonblank line */
515                         nonblankp = TRUE;
516                         /* skip // and # starts */
517                         if (c == '#' || (slashp && c == '/')) {
518                                 nonblankp = FALSE;
519                                 break;
520                         } else if (!slashp && c == '/') {
521                                 slashp = TRUE;
522                                 continue;
523                         }
524                 }
525                 slashp = FALSE;
526         }
527         return (nonblankp);
528 }