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