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 } 529