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