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