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
gotobop(int f,int n)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
gotoeop(int f,int n)72 gotoeop(int f, int n)
73 {
74 int i;
75
76 return(do_gotoeop(f, n, &i));
77 }
78
79 int
do_gotoeop(int f,int n,int * i)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
fillpara(int f,int n)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
killpara(int f,int n)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
markpara(int f,int n)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
transposepara(int f,int n)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
findpara(void)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
fillword(int f,int n)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
setfillcol(int f,int n)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
sentencespace(int f,int n)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