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