ex_global.c revision 19304
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
13static const char sccsid[] = "@(#)ex_global.c	10.22 (Berkeley) 10/10/96";
14#endif /* not lint */
15
16#include <sys/types.h>
17#include <sys/queue.h>
18
19#include <bitstring.h>
20#include <ctype.h>
21#include <errno.h>
22#include <limits.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <unistd.h>
27
28#include "../common/common.h"
29
30enum which {GLOBAL, V};
31
32static int ex_g_setup __P((SCR *, EXCMD *, enum which));
33
34/*
35 * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands]
36 *	Exec on lines matching a pattern.
37 *
38 * PUBLIC: int ex_global __P((SCR *, EXCMD *));
39 */
40int
41ex_global(sp, cmdp)
42	SCR *sp;
43	EXCMD *cmdp;
44{
45	return (ex_g_setup(sp,
46	    cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL));
47}
48
49/*
50 * ex_v -- [line [,line]] v /pattern/ [commands]
51 *	Exec on lines not matching a pattern.
52 *
53 * PUBLIC: int ex_v __P((SCR *, EXCMD *));
54 */
55int
56ex_v(sp, cmdp)
57	SCR *sp;
58	EXCMD *cmdp;
59{
60	return (ex_g_setup(sp, cmdp, V));
61}
62
63/*
64 * ex_g_setup --
65 *	Ex global and v commands.
66 */
67static int
68ex_g_setup(sp, cmdp, cmd)
69	SCR *sp;
70	EXCMD *cmdp;
71	enum which cmd;
72{
73	CHAR_T *ptrn, *p, *t;
74	EXCMD *ecp;
75	MARK abs;
76	RANGE *rp;
77	busy_t btype;
78	recno_t start, end;
79	regex_t *re;
80	regmatch_t match[1];
81	size_t len;
82	int cnt, delim, eval;
83	char *dbp;
84
85	NEEDFILE(sp, cmdp);
86
87	if (F_ISSET(sp, SC_EX_GLOBAL)) {
88		msgq(sp, M_ERR,
89	"124|The %s command can't be used as part of a global or v command",
90		    cmdp->cmd->name);
91		return (1);
92	}
93
94	/*
95	 * Skip leading white space.  Historic vi allowed any non-alphanumeric
96	 * to serve as the global command delimiter.
97	 */
98	if (cmdp->argc == 0)
99		goto usage;
100	for (p = cmdp->argv[0]->bp; isblank(*p); ++p);
101	if (*p == '\0' || isalnum(*p) ||
102	    *p == '\\' || *p == '|' || *p == '\n') {
103usage:		ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
104		return (1);
105	}
106	delim = *p++;
107
108	/*
109	 * Get the pattern string, toss escaped characters.
110	 *
111	 * QUOTING NOTE:
112	 * Only toss an escaped character if it escapes a delimiter.
113	 */
114	for (ptrn = t = p;;) {
115		if (p[0] == '\0' || p[0] == delim) {
116			if (p[0] == delim)
117				++p;
118			/*
119			 * !!!
120			 * Nul terminate the pattern string -- it's passed
121			 * to regcomp which doesn't understand anything else.
122			 */
123			*t = '\0';
124			break;
125		}
126		if (p[0] == '\\')
127			if (p[1] == delim)
128				++p;
129			else if (p[1] == '\\')
130				*t++ = *p++;
131		*t++ = *p++;
132	}
133
134	/* If the pattern string is empty, use the last one. */
135	if (*ptrn == '\0') {
136		if (sp->re == NULL) {
137			ex_emsg(sp, NULL, EXM_NOPREVRE);
138			return (1);
139		}
140
141		/* Re-compile the RE if necessary. */
142		if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,
143		    sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH))
144			return (1);
145	} else {
146		/* Compile the RE. */
147		if (re_compile(sp, ptrn, t - ptrn,
148		    &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH))
149			return (1);
150
151		/*
152		 * Set saved RE.  Historic practice is that globals set
153		 * direction as well as the RE.
154		 */
155		sp->searchdir = FORWARD;
156	}
157	re = &sp->re_c;
158
159	/* The global commands always set the previous context mark. */
160	abs.lno = sp->lno;
161	abs.cno = sp->cno;
162	if (mark_set(sp, ABSMARK1, &abs, 1))
163		return (1);
164
165	/* Get an EXCMD structure. */
166	CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD));
167	CIRCLEQ_INIT(&ecp->rq);
168
169	/*
170	 * Get a copy of the command string; the default command is print.
171	 * Don't worry about a set of <blank>s with no command, that will
172	 * default to print in the ex parser.  We need to have two copies
173	 * because the ex parser may step on the command string when it's
174	 * parsing it.
175	 */
176	if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) {
177		p = "pp";
178		len = 1;
179	}
180
181	MALLOC_RET(sp, ecp->cp, char *, len * 2);
182	ecp->o_cp = ecp->cp;
183	ecp->o_clen = len;
184	memcpy(ecp->cp + len, p, len);
185	ecp->range_lno = OOBLNO;
186	FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V);
187	LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q);
188
189	/*
190	 * For each line...  The semantics of global matching are that we first
191	 * have to decide which lines are going to get passed to the command,
192	 * and then pass them to the command, ignoring other changes.  There's
193	 * really no way to do this in a single pass, since arbitrary line
194	 * creation, deletion and movement can be done in the ex command.  For
195	 * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d".
196	 * What we do is create linked list of lines that are tracked through
197	 * each ex command.  There's a callback routine which the DB interface
198	 * routines call when a line is created or deleted.  This doesn't help
199	 * the layering much.
200	 */
201	btype = BUSY_ON;
202	cnt = INTERRUPT_CHECK;
203	for (start = cmdp->addr1.lno,
204	    end = cmdp->addr2.lno; start <= end; ++start) {
205		if (cnt-- == 0) {
206			if (INTERRUPTED(sp)) {
207				LIST_REMOVE(ecp, q);
208				free(ecp->cp);
209				free(ecp);
210				break;
211			}
212			search_busy(sp, btype);
213			btype = BUSY_UPDATE;
214			cnt = INTERRUPT_CHECK;
215		}
216		if (db_get(sp, start, DBG_FATAL, &dbp, &len))
217			return (1);
218		match[0].rm_so = 0;
219		match[0].rm_eo = len;
220		switch (eval =
221		    regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) {
222		case 0:
223			if (cmd == V)
224				continue;
225			break;
226		case REG_NOMATCH:
227			if (cmd == GLOBAL)
228				continue;
229			break;
230		default:
231			re_error(sp, eval, &sp->re_c);
232			break;
233		}
234
235		/* If follows the last entry, extend the last entry's range. */
236		if ((rp = ecp->rq.cqh_last) != (void *)&ecp->rq &&
237		    rp->stop == start - 1) {
238			++rp->stop;
239			continue;
240		}
241
242		/* Allocate a new range, and append it to the list. */
243		CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE));
244		if (rp == NULL)
245			return (1);
246		rp->start = rp->stop = start;
247		CIRCLEQ_INSERT_TAIL(&ecp->rq, rp, q);
248	}
249	search_busy(sp, BUSY_OFF);
250	return (0);
251}
252
253/*
254 * ex_g_insdel --
255 *	Update the ranges based on an insertion or deletion.
256 *
257 * PUBLIC: int ex_g_insdel __P((SCR *, lnop_t, recno_t));
258 */
259int
260ex_g_insdel(sp, op, lno)
261	SCR *sp;
262	lnop_t op;
263	recno_t lno;
264{
265	EXCMD *ecp;
266	RANGE *nrp, *rp;
267
268	/* All insert/append operations are done as inserts. */
269	if (op == LINE_APPEND)
270		abort();
271
272	if (op == LINE_RESET)
273		return (0);
274
275	for (ecp = sp->gp->ecq.lh_first; ecp != NULL; ecp = ecp->q.le_next) {
276		if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V))
277			continue;
278		for (rp = ecp->rq.cqh_first; rp != (void *)&ecp->rq; rp = nrp) {
279			nrp = rp->q.cqe_next;
280
281			/* If range less than the line, ignore it. */
282			if (rp->stop < lno)
283				continue;
284
285			/*
286			 * If range greater than the line, decrement or
287			 * increment the range.
288			 */
289			if (rp->start > lno) {
290				if (op == LINE_DELETE) {
291					--rp->start;
292					--rp->stop;
293				} else {
294					++rp->start;
295					++rp->stop;
296				}
297				continue;
298			}
299
300			/*
301			 * Lno is inside the range, decrement the end point
302			 * for deletion, and split the range for insertion.
303			 * In the latter case, since we're inserting a new
304			 * element, neither range can be exhausted.
305			 */
306			if (op == LINE_DELETE) {
307				if (rp->start > --rp->stop) {
308					CIRCLEQ_REMOVE(&ecp->rq, rp, q);
309					free(rp);
310				}
311			} else {
312				CALLOC_RET(sp, nrp, RANGE *, 1, sizeof(RANGE));
313				nrp->start = lno + 1;
314				nrp->stop = rp->stop + 1;
315				rp->stop = lno - 1;
316				CIRCLEQ_INSERT_AFTER(&ecp->rq, rp, nrp, q);
317				rp = nrp;
318			}
319		}
320
321		/*
322		 * If the command deleted/inserted lines, the cursor moves to
323		 * the line after the deleted/inserted line.
324		 */
325		ecp->range_lno = lno;
326	}
327	return (0);
328}
329