xref: /netbsd-src/external/bsd/nvi/dist/vi/v_paragraph.c (revision 4696254e455a11275231881105e1b09bd54c6820)
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