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.29 1994/01/09 17:56:13 bostic Exp $ (Berkeley) $Date: 1994/01/09 17:56:13 $";
12 #include <sys/types.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.
34 ex_global(sp
, ep
, 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.
48 ex_vglobal(sp
, ep
, cmdp
)
53 return (global(sp
, ep
, cmdp
, VGLOBAL
));
57 global(sp
, ep
, cmdp
, cmd
)
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
);
85 * Get the pattern string, toss escaped characters.
88 * Only toss an escaped character if it escapes a delimiter.
90 for (ptrn
= t
= p
;;) {
91 if (p
[0] == '\0' || p
[0] == delim
) {
96 * Nul terminate the pattern string -- it's passed
97 * to regcomp which doesn't understand anything else.
102 if (p
[0] == '\\' && p
[1] == delim
)
107 /* If the pattern string is empty, use the last one. */
109 if (!F_ISSET(sp
, S_SRE_SET
)) {
110 msgq(sp
, M_ERR
, "No previous regular expression.");
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
))
126 /* Compile the RE. */
128 eval
= regcomp(re
, ptrn
, reflags
);
130 /* Free up any allocated memory. */
135 re_error(sp
, eval
, re
);
140 * Set saved RE. Historic practice is that
141 * globals set direction as well as the RE.
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.");
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.
161 F_SET(sp
, S_SUBRE_SET
);
163 /* Set the global flag, and set up interrupts. */
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
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
))
186 /* Get the line and search for a match. */
187 if ((t
= file_gline(sp
, ep
, lno
, &len
)) == NULL
) {
188 GETLINE_ERR(sp
, lno
);
192 match
[0].rm_eo
= len
;
193 switch(eval
= regexec(re
, t
, 1, match
, REG_STARTEND
)) {
203 re_error(sp
, eval
, re
);
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) {
214 /* Allocate a new range, and append it to the list. */
215 CALLOC(sp
, rp
, RANGE
*, 1, sizeof(RANGE
));
218 rp
->start
= rp
->stop
= lno
;
219 CIRCLEQ_INSERT_TAIL(&exp
->rangeq
, rp
, q
);
223 exp
->range_lno
= OOBLNO
;
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
)
231 if (rp
->start
> rp
->stop
) {
232 CIRCLEQ_REMOVE(&exp
->rangeq
, exp
->rangeq
.cqh_first
, q
);
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
243 exp
->range_lno
= sp
->lno
= rp
->start
++;
244 if (ex_cmd(sp
, ep
, cb
, clen
))
247 /* Someone's unhappy, time to stop. */
248 if (F_ISSET(sp
, S_INTERRUPTED
)) {
249 interrupted
: msgq(sp
, M_INFO
, "Interrupted.");
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
))
259 lno
< exp
->range_lno
? (lno
? lno
: 1) : exp
->range_lno
;
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
);
280 * Update the ranges based on an insertion or deletion.
283 global_insdel(sp
, ep
, op
, lno
)
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. */
304 /* If range greater than the line, decrement range. */
305 if (rp
->start
> lno
) {
310 /* Lno is inside the range, decrement the end point. */
311 if (rp
->start
> --rp
->stop
) {
312 CIRCLEQ_REMOVE(&exp
->rangeq
, rp
, q
);
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. */
323 /* If range greater than the line, increment range. */
324 if (rp
->start
>= lno
) {
330 * Lno is inside the range, so the range must be split.
331 * Since we're inserting a new element, neither range
334 CALLOC(sp
, nrp
, RANGE
*, 1, sizeof(RANGE
));
336 F_SET(sp
, S_INTERRUPTED
);
339 nrp
->start
= lno
+ 1;
340 nrp
->stop
= rp
->stop
+ 1;
342 CIRCLEQ_INSERT_AFTER(&exp
->rangeq
, rp
, nrp
, q
);
350 * If the command deleted/inserted lines, the cursor moves to
351 * the line after the deleted/inserted line.
353 exp
->range_lno
= lno
;
358 * Set the interrupt bit in any screen that is running an interruptible
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
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
);