2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
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 $";
12 #include <sys/types.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.
33 ex_global(sp
, ep
, 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.
47 ex_vglobal(sp
, ep
, cmdp
)
52 return (global(sp
, ep
, cmdp
, VGLOBAL
));
56 global(sp
, ep
, cmdp
, cmd
)
62 struct sigaction act
, oact
;
63 struct termios nterm
, term
;
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
);
86 * Get the pattern string, toss escaped characters.
89 * Only toss an escaped character if it escapes a delimiter.
91 for (ptrn
= t
= p
;;) {
92 if (p
[0] == '\0' || p
[0] == delim
) {
97 * Nul terminate the pattern string -- it's passed
98 * to regcomp which doesn't understand anything else.
103 if (p
[0] == '\\' && p
[1] == delim
)
108 /* If the pattern string is empty, use the last one. */
110 if (!F_ISSET(sp
, S_SRE_SET
)) {
111 msgq(sp
, M_ERR
, "No previous regular expression.");
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
))
127 /* Compile the RE. */
129 eval
= regcomp(re
, ptrn
, reflags
);
131 /* Free up any allocated memory. */
136 re_error(sp
, eval
, re
);
141 * Set saved RE. Historic practice is that
142 * globals set direction as well as the RE.
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.");
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.
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.
173 if (F_ISSET(sp
->gp
, G_ISFROMTTY
)) {
174 act
.sa_handler
= global_intr
;
175 sigemptyset(&act
.sa_mask
);
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");
186 nterm
.c_lflag
|= ISIG
;
187 if (tcsetattr(STDIN_FILENO
,
188 TCSANOW
| TCSASOFT
, &nterm
)) {
189 msgq(sp
, M_SYSERR
, "tcsetattr");
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
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
);
216 match
[0].rm_eo
= len
;
217 switch(eval
= regexec(re
, t
, 1, match
, REG_STARTEND
)) {
227 re_error(sp
, eval
, re
);
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) {
238 /* Allocate a new range, and append it to the list. */
239 CALLOC(sp
, rp
, RANGE
*, 1, sizeof(RANGE
));
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
))
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
)
257 if (rp
->start
> rp
->stop
) {
258 CIRCLEQ_REMOVE(&exp
->rangeq
, exp
->rangeq
.cqh_first
, q
);
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
269 exp
->range_lno
= sp
->lno
= rp
->start
++;
270 if (ex_cmd(sp
, ep
, cb
, clen
))
273 /* Someone's unhappy, time to stop. */
274 if (F_ISSET(sp
, S_INTERRUPTED
)) {
275 interrupted
: msgq(sp
, M_INFO
, "Interrupted.");
280 /* Set the cursor to the new value, making sure it exists. */
281 if (file_lline(sp
, ep
, &lno
))
283 sp
->lno
= lno
< exp
->range_lno
? (lno
? lno
: 1) : exp
->range_lno
;
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
);
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
);
310 * Update the ranges based on an insertion or deletion.
313 global_insdel(sp
, ep
, op
, lno
)
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. */
334 /* If range greater than the line, decrement range. */
335 if (rp
->start
> lno
) {
340 /* Lno is inside the range, decrement the end point. */
341 if (rp
->start
> --rp
->stop
) {
342 CIRCLEQ_REMOVE(&exp
->rangeq
, rp
, q
);
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. */
353 /* If range greater than the line, increment range. */
354 if (rp
->start
>= lno
) {
360 * Lno is inside the range, so the range must be split.
361 * Since we're inserting a new element, neither range
364 CALLOC(sp
, nrp
, RANGE
*, 1, sizeof(RANGE
));
366 F_SET(sp
, S_INTERRUPTED
);
369 nrp
->start
= lno
+ 1;
370 nrp
->stop
= rp
->stop
+ 1;
372 CIRCLEQ_INSERT_AFTER(&exp
->rangeq
, rp
, nrp
, q
);
380 * If the command deleted/inserted lines, the cursor moves to
381 * the line after the deleted/inserted line.
383 exp
->range_lno
= lno
;
388 * Set the interrupt bit in any screen that is running an interruptible
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
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
);