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