nvi: Upgrade from version 1.79 to 2.1.1 (multibyte support)
[dragonfly.git] / contrib / nvi / vi / v_sentence.c
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[] = "$Id: v_sentence.c,v 10.9 2001/06/25 15:19:35 skimo Exp $";
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(SCR *sp, VICMD *vp)
56 {
57         enum { BLANK, NONE, PERIOD } state;
58         VCS cs;
59         size_t len;
60         u_long cnt;
61
62         cs.cs_lno = vp->m_start.lno;
63         cs.cs_cno = vp->m_start.cno;
64         if (cs_init(sp, &cs))
65                 return (1);
66
67         cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
68
69         /*
70          * !!!
71          * If in white-space, the next start of sentence counts as one.
72          * This may not handle "  .  " correctly, but it's real unclear
73          * what correctly means in that case.
74          */
75         if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && isblank(cs.cs_ch))) {
76                 if (cs_fblank(sp, &cs))
77                         return (1);
78                 if (--cnt == 0) {
79                         if (vp->m_start.lno != cs.cs_lno ||
80                             vp->m_start.cno != cs.cs_cno)
81                                 goto okret;
82                         return (1);
83                 }
84         }
85
86         for (state = NONE;;) {
87                 if (cs_next(sp, &cs))
88                         return (1);
89                 if (cs.cs_flags == CS_EOF)
90                         break;
91                 if (cs.cs_flags == CS_EOL) {
92                         if ((state == PERIOD || state == BLANK) && --cnt == 0) {
93                                 if (cs_next(sp, &cs))
94                                         return (1);
95                                 if (cs.cs_flags == 0 &&
96                                     isblank(cs.cs_ch) && cs_fblank(sp, &cs))
97                                         return (1);
98                                 goto okret;
99                         }
100                         state = NONE;
101                         continue;
102                 }
103                 if (cs.cs_flags == CS_EMP) {    /* An EMP is two sentences. */
104                         if (--cnt == 0)
105                                 goto okret;
106                         if (cs_fblank(sp, &cs))
107                                 return (1);
108                         if (--cnt == 0)
109                                 goto okret;
110                         state = NONE;
111                         continue;
112                 }
113                 switch (cs.cs_ch) {
114                 case '.':
115                 case '?':
116                 case '!':
117                         state = PERIOD;
118                         break;
119                 case ')':
120                 case ']':
121                 case '"':
122                 case '\'':
123                         if (state != PERIOD)
124                                 state = NONE;
125                         break;
126                 case '\t':
127                         if (state == PERIOD)
128                                 state = BLANK;
129                         /* FALLTHROUGH */
130                 case ' ':
131                         if (state == PERIOD) {
132                                 state = BLANK;
133                                 break;
134                         }
135                         if (state == BLANK && --cnt == 0) {
136                                 if (cs_fblank(sp, &cs))
137                                         return (1);
138                                 goto okret;
139                         }
140                         /* FALLTHROUGH */
141                 default:
142                         state = NONE;
143                         break;
144                 }
145         }
146
147         /* EOF is a movement sink, but it's an error not to have moved. */
148         if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) {
149                 v_eof(sp, NULL);
150                 return (1);
151         }
152
153 okret:  vp->m_stop.lno = cs.cs_lno;
154         vp->m_stop.cno = cs.cs_cno;
155
156         /*
157          * !!!
158          * Historic, uh, features, yeah, that's right, call 'em features.
159          * If the starting and ending cursor positions are at the first
160          * column in their lines, i.e. the movement is cutting entire lines,
161          * the buffer is in line mode, and the ending position is the last
162          * character of the previous line.  Note check to make sure that
163          * it's not within a single line.
164          *
165          * Non-motion commands move to the end of the range.  Delete and
166          * yank stay at the start.  Ignore others.  Adjust the end of the
167          * range for motion commands.
168          */
169         if (ISMOTION(vp)) {
170                 if (vp->m_start.cno == 0 &&
171                     (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {
172                         if (vp->m_start.lno < vp->m_stop.lno) {
173                                 if (db_get(sp,
174                                     --vp->m_stop.lno, DBG_FATAL, NULL, &len))
175                                         return (1);
176                                 vp->m_stop.cno = len ? len - 1 : 0;
177                         }
178                         F_SET(vp, VM_LMODE);
179                 } else
180                         --vp->m_stop.cno;
181                 vp->m_final = vp->m_start;
182         } else
183                 vp->m_final = vp->m_stop;
184         return (0);
185 }
186
187 /*
188  * v_sentenceb -- [count](
189  *      Move backward count sentences.
190  *
191  * PUBLIC: int v_sentenceb __P((SCR *, VICMD *));
192  */
193 int
194 v_sentenceb(SCR *sp, VICMD *vp)
195 {
196         VCS cs;
197         recno_t slno;
198         size_t len, scno;
199         u_long cnt;
200         int last;
201
202         /*
203          * !!!
204          * Historic vi permitted the user to hit SOF repeatedly.
205          */
206         if (vp->m_start.lno == 1 && vp->m_start.cno == 0)
207                 return (0);
208
209         cs.cs_lno = vp->m_start.lno;
210         cs.cs_cno = vp->m_start.cno;
211         if (cs_init(sp, &cs))
212                 return (1);
213
214         cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
215
216         /*
217          * !!!
218          * In empty lines, skip to the previous non-white-space character.
219          * If in text, skip to the prevous white-space character.  Believe
220          * it or not, in the paragraph:
221          *      ab cd.
222          *      AB CD.
223          * if the cursor is on the 'A' or 'B', ( moves to the 'a'.  If it
224          * is on the ' ', 'C' or 'D', it moves to the 'A'.  Yes, Virginia,
225          * Berkeley was once a major center of drug activity.
226          */
227         if (cs.cs_flags == CS_EMP) {
228                 if (cs_bblank(sp, &cs))
229                         return (1);
230                 for (;;) {
231                         if (cs_prev(sp, &cs))
232                                 return (1);
233                         if (cs.cs_flags != CS_EOL)
234                                 break;
235                 }
236         } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch))
237                 for (;;) {
238                         if (cs_prev(sp, &cs))
239                                 return (1);
240                         if (cs.cs_flags != 0 || isblank(cs.cs_ch))
241                                 break;
242                 }
243
244         for (last = 0;;) {
245                 if (cs_prev(sp, &cs))
246                         return (1);
247                 if (cs.cs_flags == CS_SOF)      /* SOF is a movement sink. */
248                         break;
249                 if (cs.cs_flags == CS_EOL) {
250                         last = 1;
251                         continue;
252                 }
253                 if (cs.cs_flags == CS_EMP) {
254                         if (--cnt == 0)
255                                 goto ret;
256                         if (cs_bblank(sp, &cs))
257                                 return (1);
258                         last = 0;
259                         continue;
260                 }
261                 switch (cs.cs_ch) {
262                 case '.':
263                 case '?':
264                 case '!':
265                         if (!last || --cnt != 0) {
266                                 last = 0;
267                                 continue;
268                         }
269
270 ret:                    slno = cs.cs_lno;
271                         scno = cs.cs_cno;
272
273                         /*
274                          * Move to the start of the sentence, skipping blanks
275                          * and special characters.
276                          */
277                         do {
278                                 if (cs_next(sp, &cs))
279                                         return (1);
280                         } while (!cs.cs_flags &&
281                             (cs.cs_ch == ')' || cs.cs_ch == ']' ||
282                             cs.cs_ch == '"' || cs.cs_ch == '\''));
283                         if ((cs.cs_flags || isblank(cs.cs_ch)) &&
284                             cs_fblank(sp, &cs))
285                                 return (1);
286
287                         /*
288                          * If it was ".  xyz", with the cursor on the 'x', or
289                          * "end.  ", with the cursor in the spaces, or the
290                          * beginning of a sentence preceded by an empty line,
291                          * we can end up where we started.  Fix it.
292                          */
293                         if (vp->m_start.lno != cs.cs_lno ||
294                             vp->m_start.cno != cs.cs_cno)
295                                 goto okret;
296
297                         /*
298                          * Well, if an empty line preceded possible blanks
299                          * and the sentence, it could be a real sentence.
300                          */
301                         for (;;) {
302                                 if (cs_prev(sp, &cs))
303                                         return (1);
304                                 if (cs.cs_flags == CS_EOL)
305                                         continue;
306                                 if (cs.cs_flags == 0 && isblank(cs.cs_ch))
307                                         continue;
308                                 break;
309                         }
310                         if (cs.cs_flags == CS_EMP)
311                                 goto okret;
312
313                         /* But it wasn't; try again. */
314                         ++cnt;
315                         cs.cs_lno = slno;
316                         cs.cs_cno = scno;
317                         last = 0;
318                         break;
319                 case '\t':
320                         last = 1;
321                         break;
322                 default:
323                         last =
324                             cs.cs_flags == CS_EOL || isblank(cs.cs_ch) ||
325                             cs.cs_ch == ')' || cs.cs_ch == ']' ||
326                             cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0;
327                 }
328         }
329
330 okret:  vp->m_stop.lno = cs.cs_lno;
331         vp->m_stop.cno = cs.cs_cno;
332
333         /*
334          * !!!
335          * If the starting and stopping cursor positions are at the first
336          * columns in the line, i.e. the movement is cutting an entire line,
337          * the buffer is in line mode, and the starting position is the last
338          * character of the previous line.
339          *
340          * All commands move to the end of the range.  Adjust the start of
341          * the range for motion commands.
342          */
343         if (ISMOTION(vp))
344                 if (vp->m_start.cno == 0 &&
345                     (cs.cs_flags != 0 || vp->m_stop.cno == 0)) {
346                         if (db_get(sp,
347                             --vp->m_start.lno, DBG_FATAL, NULL, &len))
348                                 return (1);
349                         vp->m_start.cno = len ? len - 1 : 0;
350                         F_SET(vp, VM_LMODE);
351                 } else
352                         --vp->m_start.cno;
353         vp->m_final = vp->m_stop;
354         return (0);
355 }