1 /* $NetBSD: v_paragraph.c,v 1.6 2017/01/22 05:11:22 rin Exp $ */
2 /*-
3 * Copyright (c) 1992, 1993, 1994
4 * The Regents of the University of California. All rights reserved.
5 * Copyright (c) 1992, 1993, 1994, 1995, 1996
6 * Keith Bostic. All rights reserved.
7 *
8 * See the LICENSE file for redistribution information.
9 */
10
11 #include "config.h"
12
13 #include <sys/cdefs.h>
14 #if 0
15 #ifndef lint
16 static const char sccsid[] = "Id: v_paragraph.c,v 10.10 2001/06/25 15:19:32 skimo Exp (Berkeley) Date: 2001/06/25 15:19:32 ";
17 #endif /* not lint */
18 #else
19 __RCSID("$NetBSD: v_paragraph.c,v 1.6 2017/01/22 05:11:22 rin Exp $");
20 #endif
21
22 #include <sys/types.h>
23 #include <sys/queue.h>
24 #include <sys/time.h>
25
26 #include <bitstring.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "../common/common.h"
34 #include "vi.h"
35
36 #define INTEXT_CHECK { \
37 if (len == 0 || v_isempty(p, len)) { \
38 if (!--cnt) \
39 goto found; \
40 pstate = P_INBLANK; \
41 } \
42 /* \
43 * !!! \
44 * Historic documentation (USD:15-11, 4.2) said that formfeed \
45 * characters (^L) in the first column delimited paragraphs. \
46 * The historic vi code mentions formfeed characters, but never \
47 * implements them. It seems reasonable, do it. \
48 */ \
49 if (p[0] == '\014') { \
50 if (!--cnt) \
51 goto found; \
52 continue; \
53 } \
54 if (p[0] != '.' || len < 2) \
55 continue; \
56 for (lp = VIP(sp)->ps; *lp != '\0'; lp += 2) \
57 if (lp[0] == p[1] && \
58 ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) && \
59 !--cnt) \
60 goto found; \
61 }
62
63 /*
64 * v_paragraphf -- [count]}
65 * Move forward count paragraphs.
66 *
67 * Paragraphs are empty lines after text, formfeed characters, or values
68 * from the paragraph or section options.
69 *
70 * PUBLIC: int v_paragraphf __P((SCR *, VICMD *));
71 */
72 int
v_paragraphf(SCR * sp,VICMD * vp)73 v_paragraphf(SCR *sp, VICMD *vp)
74 {
75 enum { P_INTEXT, P_INBLANK } pstate;
76 size_t lastcno, cno, prevlen, len;
77 db_recno_t cnt, lastlno, prevlno, lno;
78 int isempty;
79 CHAR_T *p;
80 char *lp;
81
82 /*
83 * !!!
84 * If the starting cursor position is at or before any non-blank
85 * characters in the line, i.e. the movement is cutting all of the
86 * line's text, the buffer is in line mode. It's a lot easier to
87 * check here, because we know that the end is going to be the start
88 * or end of a line.
89 *
90 * This was historical practice in vi, with a single exception. If
91 * the paragraph movement was from the start of the last line to EOF,
92 * then all the characters were deleted from the last line, but the
93 * line itself remained. If somebody complains, don't pause, don't
94 * hesitate, just hit them.
95 */
96 if (db_last(sp, &lastlno))
97 return (1);
98 lno = vp->m_start.lno;
99 if (ISMOTION(vp) && lno != lastlno) {
100 if ((cno = vp->m_start.cno) == 0)
101 F_SET(vp, VM_LMODE);
102 else {
103 if (nonblank(sp, lno, &len))
104 return (1);
105 if (cno <= len)
106 F_SET(vp, VM_LMODE);
107 }
108 }
109
110 /*
111 * Figure out what state we're currently in. It also historically
112 * worked on empty files, so we have to make it okay.
113 */
114 if (db_eget(sp, lno, &p, &len, &isempty)) {
115 if (isempty) {
116 vp->m_stop = vp->m_final = vp->m_start;
117 return (0);
118 } else
119 return (1);
120 }
121
122 /*
123 * If we start in text, we want to switch states
124 * (2 * N - 1) times, in non-text, (2 * N) times.
125 */
126 cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
127 cnt *= 2;
128 if (len == 0 || v_isempty(p, len))
129 pstate = P_INBLANK;
130 else {
131 --cnt;
132 pstate = P_INTEXT;
133 }
134
135 for (;;) {
136 prevlno = lno;
137 prevlen = len;
138 if (++lno > lastlno)
139 goto eof;
140 if (db_get(sp, lno, 0, &p, &len))
141 return (1);
142 switch (pstate) {
143 case P_INTEXT:
144 INTEXT_CHECK;
145 break;
146 case P_INBLANK:
147 if (len == 0 || v_isempty(p, len))
148 break;
149 if (--cnt) {
150 pstate = P_INTEXT;
151 break;
152 }
153 /*
154 * !!!
155 * Non-motion commands move to the end of the range,
156 * delete and yank stay at the start. Ignore others.
157 * Adjust the end of the range for motion commands;
158 * historically, a motion component was to the end of
159 * the previous line, whereas the movement command was
160 * to the start of the new "paragraph".
161 */
162 found: if (ISMOTION(vp)) {
163 vp->m_stop.lno = prevlno;
164 vp->m_stop.cno = prevlen ? prevlen - 1 : 0;
165 vp->m_final = vp->m_start;
166 } else {
167 vp->m_stop.lno = lno;
168 vp->m_stop.cno = 0;
169 vp->m_final = vp->m_stop;
170 }
171 return (0);
172 default:
173 abort();
174 }
175 }
176
177 /*
178 * !!!
179 * Adjust end of the range for motion commands; EOF is a movement
180 * sink. The } command historically moved to the end of the last
181 * line, not the beginning, from any position before the end of the
182 * last line.
183 */
184 eof: lastcno = len ? len - 1 : 0;
185 if (vp->m_start.lno == lastlno && vp->m_start.cno == lastcno) {
186 v_eof(sp, NULL);
187 return (1);
188 }
189 /*
190 * !!!
191 * Non-motion commands move to the end of the range, delete
192 * and yank stay at the start. Ignore others.
193 *
194 * If deleting the line (which happens if deleting to EOF), then
195 * cursor movement is to the first nonblank.
196 */
197 if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) {
198 F_CLR(vp, VM_RCM_MASK);
199 F_SET(vp, VM_RCM_SETFNB);
200 }
201 vp->m_stop.lno = lastlno;
202 vp->m_stop.cno = lastcno;
203 vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
204 return (0);
205 }
206
207 /*
208 * v_paragraphb -- [count]{
209 * Move backward count paragraphs.
210 *
211 * PUBLIC: int v_paragraphb __P((SCR *, VICMD *));
212 */
213 int
v_paragraphb(SCR * sp,VICMD * vp)214 v_paragraphb(SCR *sp, VICMD *vp)
215 {
216 enum { P_INTEXT, P_INBLANK } pstate;
217 size_t len;
218 db_recno_t cnt, lno;
219 CHAR_T *p;
220 char *lp;
221
222 /*
223 * !!!
224 * Check for SOF. The historic vi didn't complain if users hit SOF
225 * repeatedly, unless it was part of a motion command. There is no
226 * question but that Emerson's editor of choice was vi.
227 *
228 * The { command historically moved to the beginning of the first
229 * line if invoked on the first line.
230 *
231 * !!!
232 * If the starting cursor position is in the first column (backward
233 * paragraph movements did NOT historically pay attention to non-blank
234 * characters) i.e. the movement is cutting the entire line, the buffer
235 * is in line mode. Cuts from the beginning of the line also did not
236 * cut the current line, but started at the previous EOL.
237 *
238 * Correct for a left motion component while we're thinking about it.
239 */
240 lno = vp->m_start.lno;
241
242 if (ISMOTION(vp)) {
243 if (vp->m_start.cno == 0) {
244 if (vp->m_start.lno == 1) {
245 v_sof(sp, &vp->m_start);
246 return (1);
247 } else
248 --vp->m_start.lno;
249 F_SET(vp, VM_LMODE);
250 } else
251 --vp->m_start.cno;
252 }
253
254 if (vp->m_start.lno <= 1)
255 goto sof;
256
257 /* Figure out what state we're currently in. */
258 if (db_get(sp, lno, 0, &p, &len))
259 goto sof;
260
261 /*
262 * If we start in text, we want to switch states
263 * (2 * N - 1) times, in non-text, (2 * N) times.
264 */
265 cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
266 cnt *= 2;
267 if (len == 0 || v_isempty(p, len))
268 pstate = P_INBLANK;
269 else {
270 --cnt;
271 pstate = P_INTEXT;
272
273 /*
274 * !!!
275 * If the starting cursor is past the first column,
276 * the current line is checked for a paragraph.
277 */
278 if (vp->m_start.cno > 0)
279 ++lno;
280 }
281
282 for (;;) {
283 if (db_get(sp, --lno, 0, &p, &len))
284 goto sof;
285 switch (pstate) {
286 case P_INTEXT:
287 INTEXT_CHECK;
288 break;
289 case P_INBLANK:
290 if (len != 0 && !v_isempty(p, len)) {
291 if (!--cnt)
292 goto found;
293 pstate = P_INTEXT;
294 }
295 break;
296 default:
297 abort();
298 }
299 }
300
301 /* SOF is a movement sink. */
302 sof: lno = 1;
303
304 found: vp->m_stop.lno = lno;
305 vp->m_stop.cno = 0;
306
307 /*
308 * All commands move to the end of the range. (We already
309 * adjusted the start of the range for motion commands).
310 */
311 vp->m_final = vp->m_stop;
312 return (0);
313 }
314
315 /*
316 * v_buildps --
317 * Build the paragraph command search pattern.
318 *
319 * PUBLIC: int v_buildps __P((SCR *, const char *, const char *));
320 */
321 int
v_buildps(SCR * sp,const char * p_p,const char * s_p)322 v_buildps(SCR *sp, const char *p_p, const char *s_p)
323 {
324 VI_PRIVATE *vip;
325 size_t p_len, s_len;
326 char *p;
327
328 /*
329 * The vi paragraph command searches for either a paragraph or
330 * section option macro.
331 */
332 p_len = p_p == NULL ? 0 : strlen(p_p);
333 s_len = s_p == NULL ? 0 : strlen(s_p);
334
335 if (p_len == 0 && s_len == 0)
336 return (0);
337
338 MALLOC_RET(sp, p, char *, p_len + s_len + 1);
339
340 vip = VIP(sp);
341 if (vip->ps != NULL)
342 free(vip->ps);
343
344 if (p_p != NULL)
345 memmove(p, p_p, p_len + 1);
346 if (s_p != NULL)
347 memmove(p + p_len, s_p, s_len + 1);
348 vip->ps = p;
349 return (0);
350 }
351