xref: /openbsd/usr.bin/mg/paragraph.c (revision cca36db2)
1 /*	$OpenBSD: paragraph.c,v 1.22 2011/11/29 05:59:54 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 "def.h"
11 
12 static int	fillcol = 70;
13 
14 #define MAXWORD 256
15 
16 /*
17  * Move to start of paragraph.  Go back to the beginning of the current
18  * paragraph here we look for a <NL><NL> or <NL><TAB> or <NL><SPACE>
19  * combination to delimit the beginning of a paragraph.
20  */
21 /* ARGSUSED */
22 int
23 gotobop(int f, int n)
24 {
25 	/* the other way... */
26 	if (n < 0)
27 		return (gotoeop(f, -n));
28 
29 	while (n-- > 0) {
30 		/* first scan back until we are in a word */
31 		while (backchar(FFRAND, 1) && inword() == 0);
32 
33 		/* and go to the B-O-Line */
34 		curwp->w_doto = 0;
35 
36 		/*
37 		 * and scan back until we hit a <NL><SP> <NL><TAB> or
38 		 * <NL><NL>
39 		 */
40 		while (lback(curwp->w_dotp) != curbp->b_headp)
41 			if (llength(lback(curwp->w_dotp)) &&
42 			    lgetc(curwp->w_dotp, 0) != ' ' &&
43 			    lgetc(curwp->w_dotp, 0) != '.' &&
44 			    lgetc(curwp->w_dotp, 0) != '\t')
45 				curwp->w_dotp = lback(curwp->w_dotp);
46 			else {
47 				if (llength(lback(curwp->w_dotp)) &&
48 				    lgetc(curwp->w_dotp, 0) == '.') {
49 					curwp->w_dotp = lforw(curwp->w_dotp);
50 					if (curwp->w_dotp == curbp->b_headp) {
51 						/*
52 						 * beyond end of buffer,
53 						 * cleanup time
54 						 */
55 						curwp->w_dotp =
56 						    lback(curwp->w_dotp);
57 						curwp->w_doto =
58 						    llength(curwp->w_dotp);
59 					}
60 				}
61 				break;
62 			}
63 	}
64 	/* force screen update */
65 	curwp->w_rflag |= WFMOVE;
66 	return (TRUE);
67 }
68 
69 /*
70  * Move to end of paragraph.  Go forward to the end of the current paragraph
71  * here we look for a <NL><NL> or <NL><TAB> or <NL><SPACE> combination to
72  * delimit the beginning of a paragraph.
73  */
74 /* ARGSUSED */
75 int
76 gotoeop(int f, int n)
77 {
78 	/* the other way... */
79 	if (n < 0)
80 		return (gotobop(f, -n));
81 
82 	/* for each one asked for */
83 	while (n-- > 0) {
84 		/* Find the first word on/after the current line */
85 		curwp->w_doto = 0;
86 		while (forwchar(FFRAND, 1) && inword() == 0);
87 
88 		curwp->w_doto = 0;
89 		curwp->w_dotp = lforw(curwp->w_dotp);
90 
91 		/* and scan forword until we hit a <NL><SP> or ... */
92 		while (curwp->w_dotp != curbp->b_headp) {
93 			if (llength(curwp->w_dotp) &&
94 			    lgetc(curwp->w_dotp, 0) != ' ' &&
95 			    lgetc(curwp->w_dotp, 0) != '.' &&
96 			    lgetc(curwp->w_dotp, 0) != '\t')
97 				curwp->w_dotp = lforw(curwp->w_dotp);
98 			else
99 				break;
100 		}
101 		if (curwp->w_dotp == curbp->b_headp) {
102 			/* beyond end of buffer, cleanup time */
103 			curwp->w_dotp = lback(curwp->w_dotp);
104 			curwp->w_doto = llength(curwp->w_dotp);
105 			break;
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;		/* 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 		(void)gotoeop(FFRAND, 1);
261 
262 		/* set the mark here */
263 		curwp->w_markp = curwp->w_dotp;
264 		curwp->w_marko = curwp->w_doto;
265 
266 		/* go to the beginning of the paragraph */
267 		(void)gotobop(FFRAND, 1);
268 
269 		/* force us to the beginning of line */
270 		curwp->w_doto = 0;
271 
272 		/* and delete it */
273 		if ((status = killregion(FFRAND, 1)) != TRUE)
274 			return (status);
275 
276 		/* and clean up the 2 extra lines */
277 		(void)ldelete((RSIZE) 1, KFORW);
278 	}
279 	return (TRUE);
280 }
281 
282 /*
283  * Insert char with work wrap.  Check to see if we're past fillcol, and if so,
284  * justify this line.  As a last step, justify the line.
285  */
286 /* ARGSUSED */
287 int
288 fillword(int f, int n)
289 {
290 	char	c;
291 	int	col, i, nce;
292 
293 	for (i = col = 0; col <= fillcol; ++i, ++col) {
294 		if (i == curwp->w_doto)
295 			return selfinsert(f, n);
296 		c = lgetc(curwp->w_dotp, i);
297 		if (c == '\t'
298 #ifdef NOTAB
299 		    && !(curbp->b_flag & BFNOTAB)
300 #endif
301 			)
302 			col |= 0x07;
303 		else if (ISCTRL(c) != FALSE)
304 			++col;
305 	}
306 	if (curwp->w_doto != llength(curwp->w_dotp)) {
307 		(void)selfinsert(f, n);
308 		nce = llength(curwp->w_dotp) - curwp->w_doto;
309 	} else
310 		nce = 0;
311 	curwp->w_doto = i;
312 
313 	if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t')
314 		do {
315 			(void)backchar(FFRAND, 1);
316 		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
317 		    c != '\t' && curwp->w_doto > 0);
318 
319 	if (curwp->w_doto == 0)
320 		do {
321 			(void)forwchar(FFRAND, 1);
322 		} while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
323 		    c != '\t' && curwp->w_doto < llength(curwp->w_dotp));
324 
325 	(void)delwhite(FFRAND, 1);
326 	(void)lnewline();
327 	i = llength(curwp->w_dotp) - nce;
328 	curwp->w_doto = i > 0 ? i : 0;
329 	curwp->w_rflag |= WFMOVE;
330 	if (nce == 0 && curwp->w_doto != 0)
331 		return (fillword(f, n));
332 	return (TRUE);
333 }
334 
335 /*
336  * Set fill column to n for justify.
337  */
338 int
339 setfillcol(int f, int n)
340 {
341 	char buf[32], *rep;
342 	const char *es;
343 	int nfill;
344 
345 	if ((f & FFARG) != 0) {
346 		fillcol = n;
347 	} else {
348 		if ((rep = eread("Set fill-column: ", buf, sizeof(buf),
349 		    EFNEW | EFCR)) == NULL)
350 			return (ABORT);
351 		else if (rep[0] == '\0')
352 			return (FALSE);
353 		nfill = strtonum(rep, 0, INT_MAX, &es);
354 		if (es != NULL) {
355 			ewprintf("Invalid fill column: %s", rep);
356 			return (FALSE);
357 		}
358 		fillcol = nfill;
359 		ewprintf("Fill column set to %d", fillcol);
360 	}
361 	return (TRUE);
362 }
363