xref: /freebsd/contrib/nvi/vi/v_sentence.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
1 /*-
2  * Copyright (c) 1992, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1992, 1993, 1994, 1995, 1996
5  *	Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9 
10 #include "config.h"
11 
12 #ifndef lint
13 static const char sccsid[] = "@(#)v_sentence.c	10.7 (Berkeley) 3/6/96";
14 #endif /* not lint */
15 
16 #include <sys/types.h>
17 #include <sys/queue.h>
18 #include <sys/time.h>
19 
20 #include <bitstring.h>
21 #include <ctype.h>
22 #include <limits.h>
23 #include <stdio.h>
24 
25 #include "../common/common.h"
26 #include "vi.h"
27 
28 /*
29  * !!!
30  * In historic vi, a sentence was delimited by a '.', '?' or '!' character
31  * followed by TWO spaces or a newline.  One or more empty lines was also
32  * treated as a separate sentence.  The Berkeley documentation for historical
33  * vi states that any number of ')', ']', '"' and '\'' characters can be
34  * between the delimiter character and the spaces or end of line, however,
35  * the historical implementation did not handle additional '"' characters.
36  * We follow the documentation here, not the implementation.
37  *
38  * Once again, historical vi didn't do sentence movements associated with
39  * counts consistently, mostly in the presence of lines containing only
40  * white-space characters.
41  *
42  * This implementation also permits a single tab to delimit sentences, and
43  * treats lines containing only white-space characters as empty lines.
44  * Finally, tabs are eaten (along with spaces) when skipping to the start
45  * of the text following a "sentence".
46  */
47 
48 /*
49  * v_sentencef -- [count])
50  *	Move forward count sentences.
51  *
52  * PUBLIC: int v_sentencef __P((SCR *, VICMD *));
53  */
54 int
55 v_sentencef(sp, vp)
56 	SCR *sp;
57 	VICMD *vp;
58 {
59 	enum { BLANK, NONE, PERIOD } state;
60 	VCS cs;
61 	size_t len;
62 	u_long cnt;
63 
64 	cs.cs_lno = vp->m_start.lno;
65 	cs.cs_cno = vp->m_start.cno;
66 	if (cs_init(sp, &cs))
67 		return (1);
68 
69 	cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
70 
71 	/*
72 	 * !!!
73 	 * If in white-space, the next start of sentence counts as one.
74 	 * This may not handle "  .  " correctly, but it's real unclear
75 	 * what correctly means in that case.
76 	 */
77 	if (cs.cs_flags == CS_EMP || cs.cs_flags == 0 && isblank(cs.cs_ch)) {
78 		if (cs_fblank(sp, &cs))
79 			return (1);
80 		if (--cnt == 0) {
81 			if (vp->m_start.lno != cs.cs_lno ||
82 			    vp->m_start.cno != cs.cs_cno)
83 				goto okret;
84 			return (1);
85 		}
86 	}
87 
88 	for (state = NONE;;) {
89 		if (cs_next(sp, &cs))
90 			return (1);
91 		if (cs.cs_flags == CS_EOF)
92 			break;
93 		if (cs.cs_flags == CS_EOL) {
94 			if ((state == PERIOD || state == BLANK) && --cnt == 0) {
95 				if (cs_next(sp, &cs))
96 					return (1);
97 				if (cs.cs_flags == 0 &&
98 				    isblank(cs.cs_ch) && cs_fblank(sp, &cs))
99 					return (1);
100 				goto okret;
101 			}
102 			state = NONE;
103 			continue;
104 		}
105 		if (cs.cs_flags == CS_EMP) {	/* An EMP is two sentences. */
106 			if (--cnt == 0)
107 				goto okret;
108 			if (cs_fblank(sp, &cs))
109 				return (1);
110 			if (--cnt == 0)
111 				goto okret;
112 			state = NONE;
113 			continue;
114 		}
115 		switch (cs.cs_ch) {
116 		case '.':
117 		case '?':
118 		case '!':
119 			state = PERIOD;
120 			break;
121 		case ')':
122 		case ']':
123 		case '"':
124 		case '\'':
125 			if (state != PERIOD)
126 				state = NONE;
127 			break;
128 		case '\t':
129 			if (state == PERIOD)
130 				state = BLANK;
131 			/* FALLTHROUGH */
132 		case ' ':
133 			if (state == PERIOD) {
134 				state = BLANK;
135 				break;
136 			}
137 			if (state == BLANK && --cnt == 0) {
138 				if (cs_fblank(sp, &cs))
139 					return (1);
140 				goto okret;
141 			}
142 			/* FALLTHROUGH */
143 		default:
144 			state = NONE;
145 			break;
146 		}
147 	}
148 
149 	/* EOF is a movement sink, but it's an error not to have moved. */
150 	if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) {
151 		v_eof(sp, NULL);
152 		return (1);
153 	}
154 
155 okret:	vp->m_stop.lno = cs.cs_lno;
156 	vp->m_stop.cno = cs.cs_cno;
157 
158 	/*
159 	 * !!!
160 	 * Historic, uh, features, yeah, that's right, call 'em features.
161 	 * If the starting and ending cursor positions are at the first
162 	 * column in their lines, i.e. the movement is cutting entire lines,
163 	 * the buffer is in line mode, and the ending position is the last
164 	 * character of the previous line.  Note check to make sure that
165 	 * it's not within a single line.
166 	 *
167 	 * Non-motion commands move to the end of the range.  Delete and
168 	 * yank stay at the start.  Ignore others.  Adjust the end of the
169 	 * range for motion commands.
170 	 */
171 	if (ISMOTION(vp)) {
172 		if (vp->m_start.cno == 0 &&
173 		    (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {
174 			if (vp->m_start.lno < vp->m_stop.lno) {
175 				if (db_get(sp,
176 				    --vp->m_stop.lno, DBG_FATAL, NULL, &len))
177 					return (1);
178 				vp->m_stop.cno = len ? len - 1 : 0;
179 			}
180 			F_SET(vp, VM_LMODE);
181 		} else
182 			--vp->m_stop.cno;
183 		vp->m_final = vp->m_start;
184 	} else
185 		vp->m_final = vp->m_stop;
186 	return (0);
187 }
188 
189 /*
190  * v_sentenceb -- [count](
191  *	Move backward count sentences.
192  *
193  * PUBLIC: int v_sentenceb __P((SCR *, VICMD *));
194  */
195 int
196 v_sentenceb(sp, vp)
197 	SCR *sp;
198 	VICMD *vp;
199 {
200 	VCS cs;
201 	recno_t slno;
202 	size_t len, scno;
203 	u_long cnt;
204 	int last;
205 
206 	/*
207 	 * !!!
208 	 * Historic vi permitted the user to hit SOF repeatedly.
209 	 */
210 	if (vp->m_start.lno == 1 && vp->m_start.cno == 0)
211 		return (0);
212 
213 	cs.cs_lno = vp->m_start.lno;
214 	cs.cs_cno = vp->m_start.cno;
215 	if (cs_init(sp, &cs))
216 		return (1);
217 
218 	cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
219 
220 	/*
221 	 * !!!
222 	 * In empty lines, skip to the previous non-white-space character.
223 	 * If in text, skip to the prevous white-space character.  Believe
224 	 * it or not, in the paragraph:
225 	 *	ab cd.
226 	 *	AB CD.
227 	 * if the cursor is on the 'A' or 'B', ( moves to the 'a'.  If it
228 	 * is on the ' ', 'C' or 'D', it moves to the 'A'.  Yes, Virginia,
229 	 * Berkeley was once a major center of drug activity.
230 	 */
231 	if (cs.cs_flags == CS_EMP) {
232 		if (cs_bblank(sp, &cs))
233 			return (1);
234 		for (;;) {
235 			if (cs_prev(sp, &cs))
236 				return (1);
237 			if (cs.cs_flags != CS_EOL)
238 				break;
239 		}
240 	} else if (cs.cs_flags == 0 && !isblank(cs.cs_ch))
241 		for (;;) {
242 			if (cs_prev(sp, &cs))
243 				return (1);
244 			if (cs.cs_flags != 0 || isblank(cs.cs_ch))
245 				break;
246 		}
247 
248 	for (last = 0;;) {
249 		if (cs_prev(sp, &cs))
250 			return (1);
251 		if (cs.cs_flags == CS_SOF)	/* SOF is a movement sink. */
252 			break;
253 		if (cs.cs_flags == CS_EOL) {
254 			last = 1;
255 			continue;
256 		}
257 		if (cs.cs_flags == CS_EMP) {
258 			if (--cnt == 0)
259 				goto ret;
260 			if (cs_bblank(sp, &cs))
261 				return (1);
262 			last = 0;
263 			continue;
264 		}
265 		switch (cs.cs_ch) {
266 		case '.':
267 		case '?':
268 		case '!':
269 			if (!last || --cnt != 0) {
270 				last = 0;
271 				continue;
272 			}
273 
274 ret:			slno = cs.cs_lno;
275 			scno = cs.cs_cno;
276 
277 			/*
278 			 * Move to the start of the sentence, skipping blanks
279 			 * and special characters.
280 			 */
281 			do {
282 				if (cs_next(sp, &cs))
283 					return (1);
284 			} while (!cs.cs_flags &&
285 			    (cs.cs_ch == ')' || cs.cs_ch == ']' ||
286 			    cs.cs_ch == '"' || cs.cs_ch == '\''));
287 			if ((cs.cs_flags || isblank(cs.cs_ch)) &&
288 			    cs_fblank(sp, &cs))
289 				return (1);
290 
291 			/*
292 			 * If it was ".  xyz", with the cursor on the 'x', or
293 			 * "end.  ", with the cursor in the spaces, or the
294 			 * beginning of a sentence preceded by an empty line,
295 			 * we can end up where we started.  Fix it.
296 			 */
297 			if (vp->m_start.lno != cs.cs_lno ||
298 			    vp->m_start.cno != cs.cs_cno)
299 				goto okret;
300 
301 			/*
302 			 * Well, if an empty line preceded possible blanks
303 			 * and the sentence, it could be a real sentence.
304 			 */
305 			for (;;) {
306 				if (cs_prev(sp, &cs))
307 					return (1);
308 				if (cs.cs_flags == CS_EOL)
309 					continue;
310 				if (cs.cs_flags == 0 && isblank(cs.cs_ch))
311 					continue;
312 				break;
313 			}
314 			if (cs.cs_flags == CS_EMP)
315 				goto okret;
316 
317 			/* But it wasn't; try again. */
318 			++cnt;
319 			cs.cs_lno = slno;
320 			cs.cs_cno = scno;
321 			last = 0;
322 			break;
323 		case '\t':
324 			last = 1;
325 			break;
326 		default:
327 			last =
328 			    cs.cs_flags == CS_EOL || isblank(cs.cs_ch) ||
329 			    cs.cs_ch == ')' || cs.cs_ch == ']' ||
330 			    cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0;
331 		}
332 	}
333 
334 okret:	vp->m_stop.lno = cs.cs_lno;
335 	vp->m_stop.cno = cs.cs_cno;
336 
337 	/*
338 	 * !!!
339 	 * If the starting and stopping cursor positions are at the first
340 	 * columns in the line, i.e. the movement is cutting an entire line,
341 	 * the buffer is in line mode, and the starting position is the last
342 	 * character of the previous line.
343 	 *
344 	 * All commands move to the end of the range.  Adjust the start of
345 	 * the range for motion commands.
346 	 */
347 	if (ISMOTION(vp))
348 		if (vp->m_start.cno == 0 &&
349 		    (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {
350 			if (db_get(sp,
351 			    --vp->m_start.lno, DBG_FATAL, NULL, &len))
352 				return (1);
353 			vp->m_start.cno = len ? len - 1 : 0;
354 			F_SET(vp, VM_LMODE);
355 		} else
356 			--vp->m_start.cno;
357 	vp->m_final = vp->m_stop;
358 	return (0);
359 }
360