1 /* $OpenBSD: cmode.c,v 1.17 2019/07/11 18:20:18 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 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", 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); 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 } 523