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