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