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