arg1_len not initialized, was pushing extra gunk on the tty queue
[nvi.git] / ex / ex_global.c
blob83ff54d2b7a62d241502e9cf0f0df61913812985
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.28 1994/01/08 12:14:07 bostic Exp $ (Berkeley) $Date: 1994/01/08 12:14:07 $";
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"
23 enum which {GLOBAL, VGLOBAL};
25 static int global __P((SCR *, EXF *, EXCMDARG *, enum which));
26 static void global_intr __P((int));
29 * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands]
30 * Exec on lines matching a pattern.
32 int
33 ex_global(sp, ep, cmdp)
34 SCR *sp;
35 EXF *ep;
36 EXCMDARG *cmdp;
38 return (global(sp, ep,
39 cmdp, F_ISSET(cmdp, E_FORCE) ? VGLOBAL : GLOBAL));
43 * ex_vglobal -- [line [,line]] v[global] /pattern/ [commands]
44 * Exec on lines not matching a pattern.
46 int
47 ex_vglobal(sp, ep, cmdp)
48 SCR *sp;
49 EXF *ep;
50 EXCMDARG *cmdp;
52 return (global(sp, ep, cmdp, VGLOBAL));
55 static int
56 global(sp, ep, cmdp, cmd)
57 SCR *sp;
58 EXF *ep;
59 EXCMDARG *cmdp;
60 enum which cmd;
62 struct sigaction act, oact;
63 struct termios nterm, term;
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 u_int istate;
71 int delim, eval, isig, reflags, replaced, rval;
72 char *cb, *ptrn, *p, *t;
75 * Skip leading white space. Historic vi allowed any non-
76 * alphanumeric to serve as the global command delimiter.
78 for (p = cmdp->argv[0]->bp; isblank(*p); ++p);
79 if (*p == '\0' || isalnum(*p)) {
80 msgq(sp, M_ERR, "Usage: %s.", cmdp->cmd->usage);
81 return (1);
83 delim = *p++;
86 * Get the pattern string, toss escaped characters.
88 * QUOTING NOTE:
89 * Only toss an escaped character if it escapes a delimiter.
91 for (ptrn = t = p;;) {
92 if (p[0] == '\0' || p[0] == delim) {
93 if (p[0] == delim)
94 ++p;
96 * !!!
97 * Nul terminate the pattern string -- it's passed
98 * to regcomp which doesn't understand anything else.
100 *t = '\0';
101 break;
103 if (p[0] == '\\' && p[1] == delim)
104 ++p;
105 *t++ = *p++;
108 /* If the pattern string is empty, use the last one. */
109 if (*ptrn == '\0') {
110 if (!F_ISSET(sp, S_SRE_SET)) {
111 msgq(sp, M_ERR, "No previous regular expression.");
112 return (1);
114 re = &sp->sre;
115 } else {
116 /* Set RE flags. */
117 reflags = 0;
118 if (O_ISSET(sp, O_EXTENDED))
119 reflags |= REG_EXTENDED;
120 if (O_ISSET(sp, O_IGNORECASE))
121 reflags |= REG_ICASE;
123 /* Convert vi-style RE's to POSIX 1003.2 RE's. */
124 if (re_conv(sp, &ptrn, &replaced))
125 return (1);
127 /* Compile the RE. */
128 re = &lre;
129 eval = regcomp(re, ptrn, reflags);
131 /* Free up any allocated memory. */
132 if (replaced)
133 free(ptrn);
135 if (eval) {
136 re_error(sp, eval, re);
137 return (1);
141 * Set saved RE. Historic practice is that
142 * globals set direction as well as the RE.
144 sp->sre = lre;
145 sp->searchdir = FORWARD;
146 F_SET(sp, S_SRE_SET);
149 /* Get a copy of the command string. */
150 if ((clen = strlen(p)) == 0) {
151 msgq(sp, M_ERR, "No command string specified.");
152 return (1);
154 MALLOC_RET(sp, cb, char *, clen);
155 memmove(cb, p, clen);
158 * The global commands sets the substitute RE as well as
159 * the everything-else RE.
161 sp->subre = sp->sre;
162 F_SET(sp, S_SUBRE_SET);
165 * Command interrupts.
167 * ISIG turns on VINTR, VQUIT and VSUSP. We want VINTR to interrupt
168 * the command, so we install a handler. VQUIT is ignored by main()
169 * because nvi never wants to catch it. A handler for VSUSP should
170 * have been installed by the screen code.
172 F_SET(sp, S_GLOBAL);
173 if (F_ISSET(sp->gp, G_ISFROMTTY)) {
174 act.sa_handler = global_intr;
175 sigemptyset(&act.sa_mask);
176 act.sa_flags = 0;
177 if (isig = !sigaction(SIGINT, &act, &oact)) {
178 istate = F_ISSET(sp, S_INTERRUPTIBLE);
179 F_CLR(sp, S_INTERRUPTED);
180 F_SET(sp, S_INTERRUPTIBLE);
181 if (tcgetattr(STDIN_FILENO, &term)) {
182 msgq(sp, M_SYSERR, "tcgetattr");
183 goto err;
185 nterm = term;
186 nterm.c_lflag |= ISIG;
187 if (tcsetattr(STDIN_FILENO,
188 TCSANOW | TCSASOFT, &nterm)) {
189 msgq(sp, M_SYSERR, "tcsetattr");
190 goto err;
196 * For each line... The semantics of global matching are that we first
197 * have to decide which lines are going to get passed to the command,
198 * and then pass them to the command, ignoring other changes. There's
199 * really no way to do this in a single pass, since arbitrary line
200 * creation, deletion and movement can be done in the ex command. For
201 * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d".
202 * What we do is create linked list of lines that are tracked through
203 * each ex command. There's a callback routine which the DB interface
204 * routines call when a line is created or deleted. This doesn't help
205 * the layering much.
207 exp = EXP(sp);
208 for (rval = 0, lno = cmdp->addr1.lno,
209 elno = cmdp->addr2.lno; lno <= elno; ++lno) {
210 /* Get the line and search for a match. */
211 if ((t = file_gline(sp, ep, lno, &len)) == NULL) {
212 GETLINE_ERR(sp, lno);
213 goto err;
215 match[0].rm_so = 0;
216 match[0].rm_eo = len;
217 switch(eval = regexec(re, t, 1, match, REG_STARTEND)) {
218 case 0:
219 if (cmd == VGLOBAL)
220 continue;
221 break;
222 case REG_NOMATCH:
223 if (cmd == GLOBAL)
224 continue;
225 break;
226 default:
227 re_error(sp, eval, re);
228 goto err;
231 /* If follows the last entry, extend the last entry's range. */
232 if ((rp = exp->rangeq.cqh_last) != (void *)&exp->rangeq &&
233 rp->stop == lno - 1) {
234 ++rp->stop;
235 continue;
238 /* Allocate a new range, and append it to the list. */
239 CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE));
240 if (rp == NULL)
241 goto err;
242 rp->start = rp->stop = lno;
243 CIRCLEQ_INSERT_TAIL(&exp->rangeq, rp, q);
245 /* Someone's unhappy, time to stop. */
246 if (F_ISSET(sp, S_INTERRUPTED))
247 goto interrupted;
250 for (exp = EXP(sp);;) {
252 * Start at the beginning of the range each time, it may have
253 * been changed (or exhausted) if lines were inserted/deleted.
255 if ((rp = exp->rangeq.cqh_first) == (void *)&exp->rangeq)
256 break;
257 if (rp->start > rp->stop) {
258 CIRCLEQ_REMOVE(&exp->rangeq, exp->rangeq.cqh_first, q);
259 free(rp);
260 continue;
264 * Execute the command, setting the cursor to the line so that
265 * relative addressing works. This means that the cursor moves
266 * to the last line sent to the command, by default, even if
267 * the command fails.
269 exp->range_lno = sp->lno = rp->start++;
270 if (ex_cmd(sp, ep, cb, clen))
271 goto err;
273 /* Someone's unhappy, time to stop. */
274 if (F_ISSET(sp, S_INTERRUPTED)) {
275 interrupted: msgq(sp, M_INFO, "Interrupted.");
276 break;
280 /* Set the cursor to the new value, making sure it exists. */
281 if (file_lline(sp, ep, &lno))
282 return (1);
283 sp->lno = lno < exp->range_lno ? (lno ? lno : 1) : exp->range_lno;
284 if (0) {
285 err: rval = 1;
288 F_CLR(sp, S_GLOBAL);
289 if (F_ISSET(sp->gp, G_ISFROMTTY) && isig) {
290 if (sigaction(SIGINT, &oact, NULL))
291 msgq(sp, M_SYSERR, "signal");
292 if (tcsetattr(STDIN_FILENO, TCSANOW | TCSASOFT, &term))
293 msgq(sp, M_SYSERR, "tcsetattr");
294 F_CLR(sp, S_INTERRUPTED);
295 if (!istate)
296 F_CLR(sp, S_INTERRUPTIBLE);
299 /* Free any remaining ranges and the command buffer. */
300 while ((rp = exp->rangeq.cqh_first) != (void *)&exp->rangeq) {
301 CIRCLEQ_REMOVE(&exp->rangeq, exp->rangeq.cqh_first, q);
302 free(rp);
304 free(cb);
305 return (rval);
309 * global_insdel --
310 * Update the ranges based on an insertion or deletion.
312 void
313 global_insdel(sp, ep, op, lno)
314 SCR *sp;
315 EXF *ep;
316 enum operation op;
317 recno_t lno;
319 EX_PRIVATE *exp;
320 RANGE *nrp, *rp;
322 exp = EXP(sp);
324 switch (op) {
325 case LINE_APPEND:
326 return;
327 case LINE_DELETE:
328 for (rp = exp->rangeq.cqh_first;
329 rp != (void *)&exp->rangeq; rp = nrp) {
330 nrp = rp->q.cqe_next;
331 /* If range less than the line, ignore it. */
332 if (rp->stop < lno)
333 continue;
334 /* If range greater than the line, decrement range. */
335 if (rp->start > lno) {
336 --rp->start;
337 --rp->stop;
338 continue;
340 /* Lno is inside the range, decrement the end point. */
341 if (rp->start > --rp->stop) {
342 CIRCLEQ_REMOVE(&exp->rangeq, rp, q);
343 free(rp);
346 break;
347 case LINE_INSERT:
348 for (rp = exp->rangeq.cqh_first;
349 rp != (void *)&exp->rangeq; rp = rp->q.cqe_next) {
350 /* If range less than the line, ignore it. */
351 if (rp->stop < lno)
352 continue;
353 /* If range greater than the line, increment range. */
354 if (rp->start >= lno) {
355 ++rp->start;
356 ++rp->stop;
357 continue;
360 * Lno is inside the range, so the range must be split.
361 * Since we're inserting a new element, neither range
362 * can be exhausted.
364 CALLOC(sp, nrp, RANGE *, 1, sizeof(RANGE));
365 if (nrp == NULL) {
366 F_SET(sp, S_INTERRUPTED);
367 return;
369 nrp->start = lno + 1;
370 nrp->stop = rp->stop + 1;
371 rp->stop = lno - 1;
372 CIRCLEQ_INSERT_AFTER(&exp->rangeq, rp, nrp, q);
373 rp = nrp;
375 break;
376 case LINE_RESET:
377 return;
380 * If the command deleted/inserted lines, the cursor moves to
381 * the line after the deleted/inserted line.
383 exp->range_lno = lno;
387 * global_intr --
388 * Set the interrupt bit in any screen that is running an interruptible
389 * global.
391 * XXX
392 * In the future this may be a problem. The user should be able to move to
393 * another screen and keep typing while this runs. If so, and the user has
394 * more than one global running, it will be hard to decide which one to
395 * stop.
397 static void
398 global_intr(signo)
399 int signo;
401 SCR *sp;
403 for (sp = __global_list->dq.cqh_first;
404 sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
405 if (F_ISSET(sp, S_GLOBAL) && F_ISSET(sp, S_INTERRUPTIBLE))
406 F_SET(sp, S_INTERRUPTED);