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
16static 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 */
72int
73v_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			 */
162found:			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	 */
184eof:	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 */
213int
214v_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. */
302sof:	lno = 1;
303
304found:	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 */
321int
322v_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