xref: /openbsd-src/usr.bin/mg/paragraph.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: paragraph.c,v 1.45 2016/09/06 16:25:47 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 	/* find a paragraph, set mark, then goto the end */
342 	gotobop(FFRAND, 1);
343 	curwp->w_markp = curwp->w_dotp;
344 	curwp->w_marko = curwp->w_doto;
345 	(void)gotoeop(FFRAND, 1);
346 
347 	/* take a note of buffer flags - we may need them */
348 	flg = curbp->b_flag;
349 
350 	/* clean out kill buffer then kill region */
351 	kdelete();
352 	if ((status = killregion(FFRAND, 1)) != TRUE)
353 		return (status);
354 
355 	/*
356 	 * Now step through n paragraphs. If we reach the end of buffer,
357 	 * stop and paste the killed region back, then display a message.
358 	 */
359 	if (do_gotoeop(FFRAND, n, &i) == FALSE) {
360 		ewprintf("Cannot transpose paragraph, end of buffer reached.");
361 		(void)gotobop(FFRAND, i);
362 		(void)yank(FFRAND, 1);
363 		curbp->b_flag = flg;
364 		return (FALSE);
365 	}
366 	(void)yank(FFRAND, 1);
367 
368 	return (TRUE);
369 }
370 
371 /*
372  * Go down the buffer until we find a line with non-space characters.
373  */
374 int
375 findpara(void)
376 {
377 	int	col, nospace = 0;
378 
379 	/* we move forward to find a para to mark */
380 	do {
381 		curwp->w_doto = 0;
382 		col = 0;
383 
384 		/* check if we are on a blank line */
385 		while (col < llength(curwp->w_dotp)) {
386 			if (!isspace(lgetc(curwp->w_dotp, col)))
387 				nospace = 1;
388 			col++;
389 		}
390 		if (nospace)
391 			break;
392 
393 		if (lforw(curwp->w_dotp) == curbp->b_headp)
394 			return (FALSE);
395 
396 		curwp->w_dotp = lforw(curwp->w_dotp);
397 		curwp->w_dotline++;
398 	} while (1);
399 
400 	return (TRUE);
401 }
402 
403 /*
404  * Insert char with work wrap.  Check to see if we're past fillcol, and if so,
405  * justify this line.  As a last step, justify the line.
406  */
407 /* ARGSUSED */
408 int
409 fillword(int f, int n)
410 {
411 	char	c;
412 	int	col, i, nce;
413 
414 	for (i = col = 0; col <= fillcol; ++i, ++col) {
415 		if (i == curwp->w_doto)
416 			return selfinsert(f, n);
417 		c = lgetc(curwp->w_dotp, i);
418 		if (c == '\t'
419 #ifdef NOTAB
420 		    && !(curbp->b_flag & BFNOTAB)
421 #endif
422 			)
423 			col |= 0x07;
424 		else if (ISCTRL(c) != FALSE)
425 			++col;
426 	}
427 	if (curwp->w_doto != llength(curwp->w_dotp)) {
428 		(void)selfinsert(f, n);
429 		nce = llength(curwp->w_dotp) - curwp->w_doto;
430 	} else
431 		nce = 0;
432 	curwp->w_doto = i;
433 
434 	if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t')
435 		do {
436 			(void)backchar(FFRAND, 1);
437 		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
438 		    c != '\t' && curwp->w_doto > 0);
439 
440 	if (curwp->w_doto == 0)
441 		do {
442 			(void)forwchar(FFRAND, 1);
443 		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
444 		    c != '\t' && curwp->w_doto < llength(curwp->w_dotp));
445 
446 	(void)delwhite(FFRAND, 1);
447 	(void)lnewline();
448 	i = llength(curwp->w_dotp) - nce;
449 	curwp->w_doto = i > 0 ? i : 0;
450 	curwp->w_rflag |= WFMOVE;
451 	if (nce == 0 && curwp->w_doto != 0)
452 		return (fillword(f, n));
453 	return (TRUE);
454 }
455 
456 /*
457  * Set fill column to n for justify.
458  */
459 int
460 setfillcol(int f, int n)
461 {
462 	char buf[32], *rep;
463 	const char *es;
464 	int nfill;
465 
466 	if ((f & FFARG) != 0) {
467 		fillcol = n;
468 	} else {
469 		if ((rep = eread("Set fill-column: ", buf, sizeof(buf),
470 		    EFNEW | EFCR)) == NULL)
471 			return (ABORT);
472 		else if (rep[0] == '\0')
473 			return (FALSE);
474 		nfill = strtonum(rep, 0, INT_MAX, &es);
475 		if (es != NULL) {
476 			dobeep();
477 			ewprintf("Invalid fill column: %s", rep);
478 			return (FALSE);
479 		}
480 		fillcol = nfill;
481 		ewprintf("Fill column set to %d", fillcol);
482 	}
483 	return (TRUE);
484 }
485 
486 int
487 sentencespace(int f, int n)
488 {
489 	if (f & FFARG)
490 		dblspace = n > 1;
491 	else
492 		dblspace = !dblspace;
493 
494 	return (TRUE);
495 }
496