1 /* $OpenBSD: paragraph.c,v 1.46 2018/11/17 09:52:34 lum Exp $ */ 2 3 /* This file is in the public domain. */ 4 5 /* 6 * Code for dealing with paragraphs and filling. Adapted from MicroEMACS 3.6 7 * and GNU-ified by mwm@ucbvax. Several bug fixes by blarson@usc-oberon. 8 */ 9 10 #include <sys/queue.h> 11 #include <ctype.h> 12 #include <limits.h> 13 #include <signal.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 17 #include "def.h" 18 19 static int fillcol = 70; 20 21 #define MAXWORD 256 22 23 static int findpara(void); 24 static int do_gotoeop(int, int, int *); 25 26 /* 27 * Move to start of paragraph. 28 * Move backwards by line, checking from the 1st character forwards for the 29 * existence a non-space. If a non-space character is found, move to the 30 * preceding line. Keep doing this until a line with only spaces is found or 31 * the start of buffer. 32 */ 33 /* ARGSUSED */ 34 int 35 gotobop(int f, int n) 36 { 37 int col, nospace; 38 39 /* the other way... */ 40 if (n < 0) 41 return (gotoeop(f, -n)); 42 43 while (n-- > 0) { 44 nospace = 0; 45 while (lback(curwp->w_dotp) != curbp->b_headp) { 46 curwp->w_doto = 0; 47 col = 0; 48 49 while (col < llength(curwp->w_dotp) && 50 (isspace(lgetc(curwp->w_dotp, col)))) 51 col++; 52 53 if (col >= llength(curwp->w_dotp)) { 54 if (nospace) 55 break; 56 } else 57 nospace = 1; 58 59 curwp->w_dotline--; 60 curwp->w_dotp = lback(curwp->w_dotp); 61 } 62 } 63 /* force screen update */ 64 curwp->w_rflag |= WFMOVE; 65 return (TRUE); 66 } 67 68 /* 69 * Move to end of paragraph. 70 * See comments for gotobop(). Same, but moving forwards. 71 */ 72 /* ARGSUSED */ 73 int 74 gotoeop(int f, int n) 75 { 76 int i; 77 78 return(do_gotoeop(f, n, &i)); 79 } 80 81 int 82 do_gotoeop(int f, int n, int *i) 83 { 84 int col, nospace, j = 0; 85 86 /* the other way... */ 87 if (n < 0) 88 return (gotobop(f, -n)); 89 90 /* for each one asked for */ 91 while (n-- > 0) { 92 *i = ++j; 93 nospace = 0; 94 while (lforw(curwp->w_dotp) != curbp->b_headp) { 95 col = 0; 96 curwp->w_doto = 0; 97 98 while (col < llength(curwp->w_dotp) && 99 (isspace(lgetc(curwp->w_dotp, col)))) 100 col++; 101 102 if (col >= llength(curwp->w_dotp)) { 103 if (nospace) 104 break; 105 } else 106 nospace = 1; 107 108 curwp->w_dotp = lforw(curwp->w_dotp); 109 curwp->w_dotline++; 110 111 } 112 } 113 /* do not continue after end of buffer */ 114 if (lforw(curwp->w_dotp) == curbp->b_headp) { 115 gotoeol(FFRAND, 1); 116 curwp->w_rflag |= WFMOVE; 117 return (FALSE); 118 } 119 120 /* force screen update */ 121 curwp->w_rflag |= WFMOVE; 122 return (TRUE); 123 } 124 125 /* 126 * Justify a paragraph. Fill the current paragraph according to the current 127 * fill column. 128 */ 129 /* ARGSUSED */ 130 int 131 fillpara(int f, int n) 132 { 133 int c; /* current char during scan */ 134 int wordlen; /* length of current word */ 135 int clength; /* position on line during fill */ 136 int i; /* index during word copy */ 137 int eopflag; /* Are we at the End-Of-Paragraph? */ 138 int firstflag; /* first word? (needs no space) */ 139 int newlength; /* tentative new line length */ 140 int eolflag; /* was at end of line */ 141 int retval; /* return value */ 142 struct line *eopline; /* pointer to line just past EOP */ 143 char wbuf[MAXWORD]; /* buffer for current word */ 144 145 if (n == 0) 146 return (TRUE); 147 148 undo_boundary_enable(FFRAND, 0); 149 150 /* record the pointer to the line just past the EOP */ 151 (void)gotoeop(FFRAND, 1); 152 if (curwp->w_doto != 0) { 153 /* paragraph ends at end of buffer */ 154 (void)lnewline(); 155 eopline = lforw(curwp->w_dotp); 156 } else 157 eopline = curwp->w_dotp; 158 159 /* and back top the beginning of the paragraph */ 160 (void)gotobop(FFRAND, 1); 161 162 /* initialize various info */ 163 while (inword() == 0 && forwchar(FFRAND, 1)); 164 165 clength = curwp->w_doto; 166 wordlen = 0; 167 168 /* scan through lines, filling words */ 169 firstflag = TRUE; 170 eopflag = FALSE; 171 while (!eopflag) { 172 173 /* get the next character in the paragraph */ 174 if ((eolflag = (curwp->w_doto == llength(curwp->w_dotp)))) { 175 c = ' '; 176 if (lforw(curwp->w_dotp) == eopline) 177 eopflag = TRUE; 178 } else 179 c = lgetc(curwp->w_dotp, curwp->w_doto); 180 181 /* and then delete it */ 182 if (ldelete((RSIZE) 1, KNONE) == FALSE && !eopflag) { 183 retval = FALSE; 184 goto cleanup; 185 } 186 187 /* if not a separator, just add it in */ 188 if (c != ' ' && c != '\t') { 189 if (wordlen < MAXWORD - 1) 190 wbuf[wordlen++] = c; 191 else { 192 /* 193 * You lose chars beyond MAXWORD if the word 194 * is too long. I'm too lazy to fix it now; it 195 * just silently truncated the word before, 196 * so I get to feel smug. 197 */ 198 ewprintf("Word too long!"); 199 } 200 } else if (wordlen) { 201 202 /* calculate tentative new length with word added */ 203 newlength = clength + 1 + wordlen; 204 205 /* 206 * if at end of line or at doublespace and previous 207 * character was one of '.','?','!' doublespace here. 208 * behave the same way if a ')' is preceded by a 209 * [.?!] and followed by a doublespace. 210 */ 211 if (dblspace && (!eopflag && ((eolflag || 212 curwp->w_doto == llength(curwp->w_dotp) || 213 (c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' ' 214 || c == '\t') && (ISEOSP(wbuf[wordlen - 1]) || 215 (wbuf[wordlen - 1] == ')' && wordlen >= 2 && 216 ISEOSP(wbuf[wordlen - 2])))) && 217 wordlen < MAXWORD - 1)) 218 wbuf[wordlen++] = ' '; 219 220 /* at a word break with a word waiting */ 221 if (newlength <= fillcol) { 222 /* add word to current line */ 223 if (!firstflag) { 224 (void)linsert(1, ' '); 225 ++clength; 226 } 227 firstflag = FALSE; 228 } else { 229 if (curwp->w_doto > 0 && 230 lgetc(curwp->w_dotp, curwp->w_doto - 1) == ' ') { 231 curwp->w_doto -= 1; 232 (void)ldelete((RSIZE) 1, KNONE); 233 } 234 /* start a new line */ 235 (void)lnewline(); 236 clength = 0; 237 } 238 239 /* and add the word in in either case */ 240 for (i = 0; i < wordlen; i++) { 241 (void)linsert(1, wbuf[i]); 242 ++clength; 243 } 244 wordlen = 0; 245 } 246 } 247 /* and add a last newline for the end of our new paragraph */ 248 (void)lnewline(); 249 250 /* 251 * We really should wind up where we started, (which is hard to keep 252 * track of) but I think the end of the last line is better than the 253 * beginning of the blank line. 254 */ 255 (void)backchar(FFRAND, 1); 256 retval = TRUE; 257 cleanup: 258 undo_boundary_enable(FFRAND, 1); 259 return (retval); 260 } 261 262 /* 263 * Delete n paragraphs. Move to the beginning of the current paragraph, or if 264 * the cursor is on an empty line, move down the buffer to the first line with 265 * non-space characters. Then mark n paragraphs and delete. 266 */ 267 /* ARGSUSED */ 268 int 269 killpara(int f, int n) 270 { 271 int lineno, status; 272 273 if (n == 0) 274 return (TRUE); 275 276 if (findpara() == FALSE) 277 return (TRUE); 278 279 /* go to the beginning of the paragraph */ 280 (void)gotobop(FFRAND, 1); 281 282 /* take a note of the line number for after deletions and set mark */ 283 lineno = curwp->w_dotline; 284 curwp->w_markp = curwp->w_dotp; 285 curwp->w_marko = curwp->w_doto; 286 287 (void)gotoeop(FFRAND, n); 288 289 if ((status = killregion(FFRAND, 1)) != TRUE) 290 return (status); 291 292 curwp->w_dotline = lineno; 293 return (TRUE); 294 } 295 296 /* 297 * Mark n paragraphs starting with the n'th and working our way backwards. 298 * This leaves the cursor at the beginning of the paragraph where markpara() 299 * was invoked. 300 */ 301 /* ARGSUSED */ 302 int 303 markpara(int f, int n) 304 { 305 int i = 0; 306 307 if (n == 0) 308 return (TRUE); 309 310 clearmark(FFARG, 0); 311 312 if (findpara() == FALSE) 313 return (TRUE); 314 315 (void)do_gotoeop(FFRAND, n, &i); 316 317 /* set the mark here */ 318 curwp->w_markp = curwp->w_dotp; 319 curwp->w_marko = curwp->w_doto; 320 321 (void)gotobop(FFRAND, i); 322 323 return (TRUE); 324 } 325 326 /* 327 * Transpose the current paragraph with the following paragraph. If invoked 328 * multiple times, transpose to the n'th paragraph. If invoked between 329 * paragraphs, move to the previous paragraph, then continue. 330 */ 331 /* ARGSUSED */ 332 int 333 transposepara(int f, int n) 334 { 335 int i = 0, status; 336 char flg; 337 338 if (n == 0) 339 return (TRUE); 340 341 undo_boundary_enable(FFRAND, 0); 342 343 /* find a paragraph, set mark, then goto the end */ 344 gotobop(FFRAND, 1); 345 curwp->w_markp = curwp->w_dotp; 346 curwp->w_marko = curwp->w_doto; 347 (void)gotoeop(FFRAND, 1); 348 349 /* take a note of buffer flags - we may need them */ 350 flg = curbp->b_flag; 351 352 /* clean out kill buffer then kill region */ 353 kdelete(); 354 if ((status = killregion(FFRAND, 1)) != TRUE) 355 return (status); 356 357 /* 358 * Now step through n paragraphs. If we reach the end of buffer, 359 * stop and paste the killed region back, then display a message. 360 */ 361 if (do_gotoeop(FFRAND, n, &i) == FALSE) { 362 ewprintf("Cannot transpose paragraph, end of buffer reached."); 363 (void)gotobop(FFRAND, i); 364 (void)yank(FFRAND, 1); 365 curbp->b_flag = flg; 366 return (FALSE); 367 } 368 (void)yank(FFRAND, 1); 369 370 undo_boundary_enable(FFRAND, 1); 371 372 return (TRUE); 373 } 374 375 /* 376 * Go down the buffer until we find a line with non-space characters. 377 */ 378 int 379 findpara(void) 380 { 381 int col, nospace = 0; 382 383 /* we move forward to find a para to mark */ 384 do { 385 curwp->w_doto = 0; 386 col = 0; 387 388 /* check if we are on a blank line */ 389 while (col < llength(curwp->w_dotp)) { 390 if (!isspace(lgetc(curwp->w_dotp, col))) 391 nospace = 1; 392 col++; 393 } 394 if (nospace) 395 break; 396 397 if (lforw(curwp->w_dotp) == curbp->b_headp) 398 return (FALSE); 399 400 curwp->w_dotp = lforw(curwp->w_dotp); 401 curwp->w_dotline++; 402 } while (1); 403 404 return (TRUE); 405 } 406 407 /* 408 * Insert char with work wrap. Check to see if we're past fillcol, and if so, 409 * justify this line. As a last step, justify the line. 410 */ 411 /* ARGSUSED */ 412 int 413 fillword(int f, int n) 414 { 415 char c; 416 int col, i, nce; 417 418 for (i = col = 0; col <= fillcol; ++i, ++col) { 419 if (i == curwp->w_doto) 420 return selfinsert(f, n); 421 c = lgetc(curwp->w_dotp, i); 422 if (c == '\t' 423 #ifdef NOTAB 424 && !(curbp->b_flag & BFNOTAB) 425 #endif 426 ) 427 col |= 0x07; 428 else if (ISCTRL(c) != FALSE) 429 ++col; 430 } 431 if (curwp->w_doto != llength(curwp->w_dotp)) { 432 (void)selfinsert(f, n); 433 nce = llength(curwp->w_dotp) - curwp->w_doto; 434 } else 435 nce = 0; 436 curwp->w_doto = i; 437 438 if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t') 439 do { 440 (void)backchar(FFRAND, 1); 441 } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && 442 c != '\t' && curwp->w_doto > 0); 443 444 if (curwp->w_doto == 0) 445 do { 446 (void)forwchar(FFRAND, 1); 447 } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && 448 c != '\t' && curwp->w_doto < llength(curwp->w_dotp)); 449 450 (void)delwhite(FFRAND, 1); 451 (void)lnewline(); 452 i = llength(curwp->w_dotp) - nce; 453 curwp->w_doto = i > 0 ? i : 0; 454 curwp->w_rflag |= WFMOVE; 455 if (nce == 0 && curwp->w_doto != 0) 456 return (fillword(f, n)); 457 return (TRUE); 458 } 459 460 /* 461 * Set fill column to n for justify. 462 */ 463 int 464 setfillcol(int f, int n) 465 { 466 char buf[32], *rep; 467 const char *es; 468 int nfill; 469 470 if ((f & FFARG) != 0) { 471 fillcol = n; 472 } else { 473 if ((rep = eread("Set fill-column: ", buf, sizeof(buf), 474 EFNEW | EFCR)) == NULL) 475 return (ABORT); 476 else if (rep[0] == '\0') 477 return (FALSE); 478 nfill = strtonum(rep, 0, INT_MAX, &es); 479 if (es != NULL) { 480 dobeep(); 481 ewprintf("Invalid fill column: %s", rep); 482 return (FALSE); 483 } 484 fillcol = nfill; 485 ewprintf("Fill column set to %d", fillcol); 486 } 487 return (TRUE); 488 } 489 490 int 491 sentencespace(int f, int n) 492 { 493 if (f & FFARG) 494 dblspace = n > 1; 495 else 496 dblspace = !dblspace; 497 498 return (TRUE); 499 } 500