xref: /openbsd/usr.bin/mg/paragraph.c (revision 8529ddd3)
1 /*	$OpenBSD: paragraph.c,v 1.36 2015/03/19 21:22:15 bcallah 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 /*
24  * Move to start of paragraph.
25  * Move backwards by line, checking from the 1st character forwards for the
26  * existence a non-space. If a non-space character is found, move to the
27  * preceding line. Keep doing this until a line with only spaces is found or
28  * the start of buffer.
29  */
30 /* ARGSUSED */
31 int
32 gotobop(int f, int n)
33 {
34 	int col, nospace;
35 
36 	/* the other way... */
37 	if (n < 0)
38 		return (gotoeop(f, -n));
39 
40 	while (n-- > 0) {
41 		nospace = 0;
42 		while (lback(curwp->w_dotp) != curbp->b_headp) {
43 			curwp->w_doto = 0;
44 			col = 0;
45 
46 			while (col < llength(curwp->w_dotp) &&
47 			    (isspace(lgetc(curwp->w_dotp, col))))
48 				col++;
49 
50 			if (col >= llength(curwp->w_dotp)) {
51 				if (nospace)
52 					break;
53 			} else
54 				nospace = 1;
55 
56 			curwp->w_dotline--;
57 			curwp->w_dotp = lback(curwp->w_dotp);
58 		}
59 	}
60 	/* force screen update */
61 	curwp->w_rflag |= WFMOVE;
62 	return (TRUE);
63 }
64 
65 /*
66  * Move to end of paragraph.
67  * See comments for gotobop(). Same, but moving forwards.
68  */
69 /* ARGSUSED */
70 int
71 gotoeop(int f, int n)
72 {
73 	int col, nospace;
74 
75 	/* the other way... */
76 	if (n < 0)
77 		return (gotobop(f, -n));
78 
79 	/* for each one asked for */
80 	while (n-- > 0) {
81 		nospace = 0;
82 		while (lforw(curwp->w_dotp) != curbp->b_headp) {
83 			col = 0;
84 			curwp->w_doto = 0;
85 
86 			while (col < llength(curwp->w_dotp) &&
87 			    (isspace(lgetc(curwp->w_dotp, col))))
88 				col++;
89 
90 			if (col >= llength(curwp->w_dotp)) {
91 				if (nospace)
92 					break;
93 			} else
94 				nospace = 1;
95 
96 			curwp->w_dotp = lforw(curwp->w_dotp);
97 			curwp->w_dotline++;
98 
99 			/* do not continue after end of buffer */
100 			if (lforw(curwp->w_dotp) == curbp->b_headp) {
101 				gotoeol(FFRAND, 1);
102 				curwp->w_rflag |= WFMOVE;
103 				return (FALSE);
104 			}
105 		}
106 	}
107 
108 	/* force screen update */
109 	curwp->w_rflag |= WFMOVE;
110 	return (TRUE);
111 }
112 
113 /*
114  * Justify a paragraph.  Fill the current paragraph according to the current
115  * fill column.
116  */
117 /* ARGSUSED */
118 int
119 fillpara(int f, int n)
120 {
121 	int	 c;		/* current char during scan		*/
122 	int	 wordlen;	/* length of current word		*/
123 	int	 clength;	/* position on line during fill		*/
124 	int	 i;		/* index during word copy		*/
125 	int	 eopflag;	/* Are we at the End-Of-Paragraph?	*/
126 	int	 firstflag;	/* first word? (needs no space)		*/
127 	int	 newlength;	/* tentative new line length		*/
128 	int	 eolflag;	/* was at end of line			*/
129 	int	 retval;	/* return value				*/
130 	struct line	*eopline;	/* pointer to line just past EOP	*/
131 	char	 wbuf[MAXWORD];	/* buffer for current word		*/
132 
133 	undo_boundary_enable(FFRAND, 0);
134 
135 	/* record the pointer to the line just past the EOP */
136 	(void)gotoeop(FFRAND, 1);
137 	if (curwp->w_doto != 0) {
138 		/* paragraph ends at end of buffer */
139 		(void)lnewline();
140 		eopline = lforw(curwp->w_dotp);
141 	} else
142 		eopline = curwp->w_dotp;
143 
144 	/* and back top the begining of the paragraph */
145 	(void)gotobop(FFRAND, 1);
146 
147 	/* initialize various info */
148 	while (inword() == 0 && forwchar(FFRAND, 1));
149 
150 	clength = curwp->w_doto;
151 	wordlen = 0;
152 
153 	/* scan through lines, filling words */
154 	firstflag = TRUE;
155 	eopflag = FALSE;
156 	while (!eopflag) {
157 
158 		/* get the next character in the paragraph */
159 		if ((eolflag = (curwp->w_doto == llength(curwp->w_dotp)))) {
160 			c = ' ';
161 			if (lforw(curwp->w_dotp) == eopline)
162 				eopflag = TRUE;
163 		} else
164 			c = lgetc(curwp->w_dotp, curwp->w_doto);
165 
166 		/* and then delete it */
167 		if (ldelete((RSIZE) 1, KNONE) == FALSE && !eopflag) {
168 			retval = FALSE;
169 			goto cleanup;
170 		}
171 
172 		/* if not a separator, just add it in */
173 		if (c != ' ' && c != '\t') {
174 			if (wordlen < MAXWORD - 1)
175 				wbuf[wordlen++] = c;
176 			else {
177 				/*
178 				 * You lose chars beyond MAXWORD if the word
179 				 * is too long. I'm too lazy to fix it now; it
180 				 * just silently truncated the word before,
181 				 * so I get to feel smug.
182 				 */
183 				ewprintf("Word too long!");
184 			}
185 		} else if (wordlen) {
186 
187 			/* calculate tentative new length with word added */
188 			newlength = clength + 1 + wordlen;
189 
190 			/*
191 			 * if at end of line or at doublespace and previous
192 			 * character was one of '.','?','!' doublespace here.
193 			 * behave the same way if a ')' is preceded by a
194 			 * [.?!] and followed by a doublespace.
195 			 */
196 			if ((eolflag ||
197 			    curwp->w_doto == llength(curwp->w_dotp) ||
198 			    (c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' '
199 			    || c == '\t') && (ISEOSP(wbuf[wordlen - 1]) ||
200 			    (wbuf[wordlen - 1] == ')' && wordlen >= 2 &&
201 			    ISEOSP(wbuf[wordlen - 2]))) &&
202 			    wordlen < MAXWORD - 1)
203 				wbuf[wordlen++] = ' ';
204 
205 			/* at a word break with a word waiting */
206 			if (newlength <= fillcol) {
207 				/* add word to current line */
208 				if (!firstflag) {
209 					(void)linsert(1, ' ');
210 					++clength;
211 				}
212 				firstflag = FALSE;
213 			} else {
214 				if (curwp->w_doto > 0 &&
215 				    lgetc(curwp->w_dotp, curwp->w_doto - 1) == ' ') {
216 					curwp->w_doto -= 1;
217 					(void)ldelete((RSIZE) 1, KNONE);
218 				}
219 				/* start a new line */
220 				(void)lnewline();
221 				clength = 0;
222 			}
223 
224 			/* and add the word in in either case */
225 			for (i = 0; i < wordlen; i++) {
226 				(void)linsert(1, wbuf[i]);
227 				++clength;
228 			}
229 			wordlen = 0;
230 		}
231 	}
232 	/* and add a last newline for the end of our new paragraph */
233 	(void)lnewline();
234 
235 	/*
236 	 * We really should wind up where we started, (which is hard to keep
237 	 * track of) but I think the end of the last line is better than the
238 	 * beginning of the blank line.
239 	 */
240 	(void)backchar(FFRAND, 1);
241 	retval = TRUE;
242 cleanup:
243 	undo_boundary_enable(FFRAND, 1);
244 	return (retval);
245 }
246 
247 /*
248  * Delete a paragraph.  Delete n paragraphs starting with the current one.
249  */
250 /* ARGSUSED */
251 int
252 killpara(int f, int n)
253 {
254 	int	status, end = FALSE;	/* returned status of functions */
255 
256 	/* for each paragraph to delete */
257 	while (n--) {
258 
259 		/* mark out the end and beginning of the para to delete */
260 		if (!gotoeop(FFRAND, 1))
261 			end = TRUE;
262 
263 		/* set the mark here */
264 		curwp->w_markp = curwp->w_dotp;
265 		curwp->w_marko = curwp->w_doto;
266 
267 		/* go to the beginning of the paragraph */
268 		(void)gotobop(FFRAND, 1);
269 
270 		/* force us to the beginning of line */
271 		curwp->w_doto = 0;
272 
273 		/* and delete it */
274 		if ((status = killregion(FFRAND, 1)) != TRUE)
275 			return (status);
276 
277 		if (end)
278 			return (TRUE);
279 	}
280 	return (TRUE);
281 }
282 
283 /*
284  * Insert char with work wrap.  Check to see if we're past fillcol, and if so,
285  * justify this line.  As a last step, justify the line.
286  */
287 /* ARGSUSED */
288 int
289 fillword(int f, int n)
290 {
291 	char	c;
292 	int	col, i, nce;
293 
294 	for (i = col = 0; col <= fillcol; ++i, ++col) {
295 		if (i == curwp->w_doto)
296 			return selfinsert(f, n);
297 		c = lgetc(curwp->w_dotp, i);
298 		if (c == '\t'
299 #ifdef NOTAB
300 		    && !(curbp->b_flag & BFNOTAB)
301 #endif
302 			)
303 			col |= 0x07;
304 		else if (ISCTRL(c) != FALSE)
305 			++col;
306 	}
307 	if (curwp->w_doto != llength(curwp->w_dotp)) {
308 		(void)selfinsert(f, n);
309 		nce = llength(curwp->w_dotp) - curwp->w_doto;
310 	} else
311 		nce = 0;
312 	curwp->w_doto = i;
313 
314 	if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t')
315 		do {
316 			(void)backchar(FFRAND, 1);
317 		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
318 		    c != '\t' && curwp->w_doto > 0);
319 
320 	if (curwp->w_doto == 0)
321 		do {
322 			(void)forwchar(FFRAND, 1);
323 		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
324 		    c != '\t' && curwp->w_doto < llength(curwp->w_dotp));
325 
326 	(void)delwhite(FFRAND, 1);
327 	(void)lnewline();
328 	i = llength(curwp->w_dotp) - nce;
329 	curwp->w_doto = i > 0 ? i : 0;
330 	curwp->w_rflag |= WFMOVE;
331 	if (nce == 0 && curwp->w_doto != 0)
332 		return (fillword(f, n));
333 	return (TRUE);
334 }
335 
336 /*
337  * Set fill column to n for justify.
338  */
339 int
340 setfillcol(int f, int n)
341 {
342 	char buf[32], *rep;
343 	const char *es;
344 	int nfill;
345 
346 	if ((f & FFARG) != 0) {
347 		fillcol = n;
348 	} else {
349 		if ((rep = eread("Set fill-column: ", buf, sizeof(buf),
350 		    EFNEW | EFCR)) == NULL)
351 			return (ABORT);
352 		else if (rep[0] == '\0')
353 			return (FALSE);
354 		nfill = strtonum(rep, 0, INT_MAX, &es);
355 		if (es != NULL) {
356 			dobeep();
357 			ewprintf("Invalid fill column: %s", rep);
358 			return (FALSE);
359 		}
360 		fillcol = nfill;
361 		ewprintf("Fill column set to %d", fillcol);
362 	}
363 	return (TRUE);
364 }
365