xref: /openbsd/usr.bin/mg/paragraph.c (revision fb3e194e)
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