update to 1.01
[nvi.git] / ex / ex_global.c
blob3c2e7fe40affbbf7f4b8a573397547eb924db489
1 /*-
2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
9 static char sccsid[] = "$Id: ex_global.c,v 8.29 1994/01/09 17:56:13 bostic Exp $ (Berkeley) $Date: 1994/01/09 17:56:13 $";
10 #endif /* not lint */
12 #include <sys/types.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
20 #include "vi.h"
21 #include "excmd.h"
22 #include "interrupt.h"
24 enum which {GLOBAL, VGLOBAL};
26 static int global __P((SCR *, EXF *, EXCMDARG *, enum which));
27 static void global_intr __P((int));
30 * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands]
31 * Exec on lines matching a pattern.
33 int
34 ex_global(sp, ep, cmdp)
35 SCR *sp;
36 EXF *ep;
37 EXCMDARG *cmdp;
39 return (global(sp, ep,
40 cmdp, F_ISSET(cmdp, E_FORCE) ? VGLOBAL : GLOBAL));
44 * ex_vglobal -- [line [,line]] v[global] /pattern/ [commands]
45 * Exec on lines not matching a pattern.
47 int
48 ex_vglobal(sp, ep, cmdp)
49 SCR *sp;
50 EXF *ep;
51 EXCMDARG *cmdp;
53 return (global(sp, ep, cmdp, VGLOBAL));
56 static int
57 global(sp, ep, cmdp, cmd)
58 SCR *sp;
59 EXF *ep;
60 EXCMDARG *cmdp;
61 enum which cmd;
63 DECLARE_INTERRUPTS;
64 RANGE *rp;
65 EX_PRIVATE *exp;
66 recno_t elno, lno;
67 regmatch_t match[1];
68 regex_t *re, lre;
69 size_t clen, len;
70 int delim, eval, reflags, replaced, rval;
71 char *cb, *ptrn, *p, *t;
74 * Skip leading white space. Historic vi allowed any non-
75 * alphanumeric to serve as the global command delimiter.
77 for (p = cmdp->argv[0]->bp; isblank(*p); ++p);
78 if (*p == '\0' || isalnum(*p)) {
79 msgq(sp, M_ERR, "Usage: %s.", cmdp->cmd->usage);
80 return (1);
82 delim = *p++;
85 * Get the pattern string, toss escaped characters.
87 * QUOTING NOTE:
88 * Only toss an escaped character if it escapes a delimiter.
90 for (ptrn = t = p;;) {
91 if (p[0] == '\0' || p[0] == delim) {
92 if (p[0] == delim)
93 ++p;
95 * !!!
96 * Nul terminate the pattern string -- it's passed
97 * to regcomp which doesn't understand anything else.
99 *t = '\0';
100 break;
102 if (p[0] == '\\' && p[1] == delim)
103 ++p;
104 *t++ = *p++;
107 /* If the pattern string is empty, use the last one. */
108 if (*ptrn == '\0') {
109 if (!F_ISSET(sp, S_SRE_SET)) {
110 msgq(sp, M_ERR, "No previous regular expression.");
111 return (1);
113 re = &sp->sre;
114 } else {
115 /* Set RE flags. */
116 reflags = 0;
117 if (O_ISSET(sp, O_EXTENDED))
118 reflags |= REG_EXTENDED;
119 if (O_ISSET(sp, O_IGNORECASE))
120 reflags |= REG_ICASE;
122 /* Convert vi-style RE's to POSIX 1003.2 RE's. */
123 if (re_conv(sp, &ptrn, &replaced))
124 return (1);
126 /* Compile the RE. */
127 re = &lre;
128 eval = regcomp(re, ptrn, reflags);
130 /* Free up any allocated memory. */
131 if (replaced)
132 free(ptrn);
134 if (eval) {
135 re_error(sp, eval, re);
136 return (1);
140 * Set saved RE. Historic practice is that
141 * globals set direction as well as the RE.
143 sp->sre = lre;
144 sp->searchdir = FORWARD;
145 F_SET(sp, S_SRE_SET);
148 /* Get a copy of the command string. */
149 if ((clen = strlen(p)) == 0) {
150 msgq(sp, M_ERR, "No command string specified.");
151 return (1);
153 MALLOC_RET(sp, cb, char *, clen);
154 memmove(cb, p, clen);
157 * The global commands sets the substitute RE as well as
158 * the everything-else RE.
160 sp->subre = sp->sre;
161 F_SET(sp, S_SUBRE_SET);
163 /* Set the global flag, and set up interrupts. */
164 F_SET(sp, S_GLOBAL);
165 SET_UP_INTERRUPTS(global_intr);
168 * For each line... The semantics of global matching are that we first
169 * have to decide which lines are going to get passed to the command,
170 * and then pass them to the command, ignoring other changes. There's
171 * really no way to do this in a single pass, since arbitrary line
172 * creation, deletion and movement can be done in the ex command. For
173 * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d".
174 * What we do is create linked list of lines that are tracked through
175 * each ex command. There's a callback routine which the DB interface
176 * routines call when a line is created or deleted. This doesn't help
177 * the layering much.
179 exp = EXP(sp);
180 for (rval = 0, lno = cmdp->addr1.lno,
181 elno = cmdp->addr2.lno; lno <= elno; ++lno) {
182 /* Someone's unhappy, time to stop. */
183 if (F_ISSET(sp, S_INTERRUPTED))
184 goto interrupted;
186 /* Get the line and search for a match. */
187 if ((t = file_gline(sp, ep, lno, &len)) == NULL) {
188 GETLINE_ERR(sp, lno);
189 goto err;
191 match[0].rm_so = 0;
192 match[0].rm_eo = len;
193 switch(eval = regexec(re, t, 1, match, REG_STARTEND)) {
194 case 0:
195 if (cmd == VGLOBAL)
196 continue;
197 break;
198 case REG_NOMATCH:
199 if (cmd == GLOBAL)
200 continue;
201 break;
202 default:
203 re_error(sp, eval, re);
204 goto err;
207 /* If follows the last entry, extend the last entry's range. */
208 if ((rp = exp->rangeq.cqh_last) != (void *)&exp->rangeq &&
209 rp->stop == lno - 1) {
210 ++rp->stop;
211 continue;
214 /* Allocate a new range, and append it to the list. */
215 CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE));
216 if (rp == NULL)
217 goto err;
218 rp->start = rp->stop = lno;
219 CIRCLEQ_INSERT_TAIL(&exp->rangeq, rp, q);
222 exp = EXP(sp);
223 exp->range_lno = OOBLNO;
224 for (;;) {
226 * Start at the beginning of the range each time, it may have
227 * been changed (or exhausted) if lines were inserted/deleted.
229 if ((rp = exp->rangeq.cqh_first) == (void *)&exp->rangeq)
230 break;
231 if (rp->start > rp->stop) {
232 CIRCLEQ_REMOVE(&exp->rangeq, exp->rangeq.cqh_first, q);
233 free(rp);
234 continue;
238 * Execute the command, setting the cursor to the line so that
239 * relative addressing works. This means that the cursor moves
240 * to the last line sent to the command, by default, even if
241 * the command fails.
243 exp->range_lno = sp->lno = rp->start++;
244 if (ex_cmd(sp, ep, cb, clen))
245 goto err;
247 /* Someone's unhappy, time to stop. */
248 if (F_ISSET(sp, S_INTERRUPTED)) {
249 interrupted: msgq(sp, M_INFO, "Interrupted.");
250 break;
254 /* Set the cursor to the new value, making sure it exists. */
255 if (exp->range_lno != OOBLNO) {
256 if (file_lline(sp, ep, &lno))
257 return (1);
258 sp->lno =
259 lno < exp->range_lno ? (lno ? lno : 1) : exp->range_lno;
261 if (0) {
262 err: rval = 1;
265 interrupt_err:
266 F_CLR(sp, S_GLOBAL);
267 TEAR_DOWN_INTERRUPTS;
269 /* Free any remaining ranges and the command buffer. */
270 while ((rp = exp->rangeq.cqh_first) != (void *)&exp->rangeq) {
271 CIRCLEQ_REMOVE(&exp->rangeq, exp->rangeq.cqh_first, q);
272 free(rp);
274 free(cb);
275 return (rval);
279 * global_insdel --
280 * Update the ranges based on an insertion or deletion.
282 void
283 global_insdel(sp, ep, op, lno)
284 SCR *sp;
285 EXF *ep;
286 enum operation op;
287 recno_t lno;
289 EX_PRIVATE *exp;
290 RANGE *nrp, *rp;
292 exp = EXP(sp);
294 switch (op) {
295 case LINE_APPEND:
296 return;
297 case LINE_DELETE:
298 for (rp = exp->rangeq.cqh_first;
299 rp != (void *)&exp->rangeq; rp = nrp) {
300 nrp = rp->q.cqe_next;
301 /* If range less than the line, ignore it. */
302 if (rp->stop < lno)
303 continue;
304 /* If range greater than the line, decrement range. */
305 if (rp->start > lno) {
306 --rp->start;
307 --rp->stop;
308 continue;
310 /* Lno is inside the range, decrement the end point. */
311 if (rp->start > --rp->stop) {
312 CIRCLEQ_REMOVE(&exp->rangeq, rp, q);
313 free(rp);
316 break;
317 case LINE_INSERT:
318 for (rp = exp->rangeq.cqh_first;
319 rp != (void *)&exp->rangeq; rp = rp->q.cqe_next) {
320 /* If range less than the line, ignore it. */
321 if (rp->stop < lno)
322 continue;
323 /* If range greater than the line, increment range. */
324 if (rp->start >= lno) {
325 ++rp->start;
326 ++rp->stop;
327 continue;
330 * Lno is inside the range, so the range must be split.
331 * Since we're inserting a new element, neither range
332 * can be exhausted.
334 CALLOC(sp, nrp, RANGE *, 1, sizeof(RANGE));
335 if (nrp == NULL) {
336 F_SET(sp, S_INTERRUPTED);
337 return;
339 nrp->start = lno + 1;
340 nrp->stop = rp->stop + 1;
341 rp->stop = lno - 1;
342 CIRCLEQ_INSERT_AFTER(&exp->rangeq, rp, nrp, q);
343 rp = nrp;
345 break;
346 case LINE_RESET:
347 return;
350 * If the command deleted/inserted lines, the cursor moves to
351 * the line after the deleted/inserted line.
353 exp->range_lno = lno;
357 * global_intr --
358 * Set the interrupt bit in any screen that is running an interruptible
359 * global.
361 * XXX
362 * In the future this may be a problem. The user should be able to move to
363 * another screen and keep typing while this runs. If so, and the user has
364 * more than one global running, it will be hard to decide which one to
365 * stop.
367 static void
368 global_intr(signo)
369 int signo;
371 SCR *sp;
373 for (sp = __global_list->dq.cqh_first;
374 sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
375 if (F_ISSET(sp, S_GLOBAL) && F_ISSET(sp, S_INTERRUPTIBLE))
376 F_SET(sp, S_INTERRUPTED);