1 /* $OpenBSD: paragraph.c,v 1.39 2015/09/24 07:20:12 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 /* 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 } 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 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 begining 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 ((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 /* ARGSUSED */ 265 int 266 killpara(int f, int n) 267 { 268 int lineno, status; 269 270 if (findpara() == FALSE) 271 return (TRUE); 272 273 /* go to the beginning of the paragraph */ 274 (void)gotobop(FFRAND, 1); 275 276 /* take a note of the line number for after deletions and set mark */ 277 lineno = curwp->w_dotline; 278 curwp->w_markp = curwp->w_dotp; 279 curwp->w_marko = curwp->w_doto; 280 281 (void)gotoeop(FFRAND, n); 282 283 if ((status = killregion(FFRAND, 1)) != TRUE) 284 return (status); 285 286 curwp->w_dotline = lineno; 287 return (TRUE); 288 } 289 290 /* 291 * Mark n paragraphs starting with the n'th and working our way backwards. 292 * This leaves the cursor at the beginning of the paragraph where markpara() 293 * was invoked. 294 */ 295 /* ARGSUSED */ 296 int 297 markpara(int f, int n) 298 { 299 int i = 0; 300 301 clearmark(FFARG, 0); 302 303 if (findpara() == FALSE) 304 return (TRUE); 305 306 (void)do_gotoeop(FFRAND, n, &i); 307 308 /* set the mark here */ 309 curwp->w_markp = curwp->w_dotp; 310 curwp->w_marko = curwp->w_doto; 311 312 (void)gotobop(FFRAND, i); 313 314 return (TRUE); 315 } 316 317 /* 318 * Go down the buffer until we find a line with non-space characters. 319 */ 320 int 321 findpara(void) 322 { 323 int col, nospace = 0; 324 325 /* we move forward to find a para to mark */ 326 do { 327 curwp->w_doto = 0; 328 col = 0; 329 330 /* check if we are on a blank line */ 331 while (col < llength(curwp->w_dotp)) { 332 if (!isspace(lgetc(curwp->w_dotp, col))) 333 nospace = 1; 334 col++; 335 } 336 if (nospace) 337 break; 338 339 if (lforw(curwp->w_dotp) == curbp->b_headp) 340 return (FALSE); 341 342 curwp->w_dotp = lforw(curwp->w_dotp); 343 curwp->w_dotline++; 344 } while (1); 345 346 return (TRUE); 347 } 348 349 /* 350 * Insert char with work wrap. Check to see if we're past fillcol, and if so, 351 * justify this line. As a last step, justify the line. 352 */ 353 /* ARGSUSED */ 354 int 355 fillword(int f, int n) 356 { 357 char c; 358 int col, i, nce; 359 360 for (i = col = 0; col <= fillcol; ++i, ++col) { 361 if (i == curwp->w_doto) 362 return selfinsert(f, n); 363 c = lgetc(curwp->w_dotp, i); 364 if (c == '\t' 365 #ifdef NOTAB 366 && !(curbp->b_flag & BFNOTAB) 367 #endif 368 ) 369 col |= 0x07; 370 else if (ISCTRL(c) != FALSE) 371 ++col; 372 } 373 if (curwp->w_doto != llength(curwp->w_dotp)) { 374 (void)selfinsert(f, n); 375 nce = llength(curwp->w_dotp) - curwp->w_doto; 376 } else 377 nce = 0; 378 curwp->w_doto = i; 379 380 if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t') 381 do { 382 (void)backchar(FFRAND, 1); 383 } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && 384 c != '\t' && curwp->w_doto > 0); 385 386 if (curwp->w_doto == 0) 387 do { 388 (void)forwchar(FFRAND, 1); 389 } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && 390 c != '\t' && curwp->w_doto < llength(curwp->w_dotp)); 391 392 (void)delwhite(FFRAND, 1); 393 (void)lnewline(); 394 i = llength(curwp->w_dotp) - nce; 395 curwp->w_doto = i > 0 ? i : 0; 396 curwp->w_rflag |= WFMOVE; 397 if (nce == 0 && curwp->w_doto != 0) 398 return (fillword(f, n)); 399 return (TRUE); 400 } 401 402 /* 403 * Set fill column to n for justify. 404 */ 405 int 406 setfillcol(int f, int n) 407 { 408 char buf[32], *rep; 409 const char *es; 410 int nfill; 411 412 if ((f & FFARG) != 0) { 413 fillcol = n; 414 } else { 415 if ((rep = eread("Set fill-column: ", buf, sizeof(buf), 416 EFNEW | EFCR)) == NULL) 417 return (ABORT); 418 else if (rep[0] == '\0') 419 return (FALSE); 420 nfill = strtonum(rep, 0, INT_MAX, &es); 421 if (es != NULL) { 422 dobeep(); 423 ewprintf("Invalid fill column: %s", rep); 424 return (FALSE); 425 } 426 fillcol = nfill; 427 ewprintf("Fill column set to %d", fillcol); 428 } 429 return (TRUE); 430 } 431