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