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