1*ddc53e66Smiod /* $OpenBSD: ex_global.c,v 1.8 2006/01/08 21:05:40 miod Exp $ */ 2d4e7c603Sniklas 3df930be7Sderaadt /*- 4df930be7Sderaadt * Copyright (c) 1992, 1993, 1994 5df930be7Sderaadt * The Regents of the University of California. All rights reserved. 645f2ab88Sderaadt * Copyright (c) 1992, 1993, 1994, 1995, 1996 745f2ab88Sderaadt * Keith Bostic. All rights reserved. 8df930be7Sderaadt * 945f2ab88Sderaadt * See the LICENSE file for redistribution information. 10df930be7Sderaadt */ 11df930be7Sderaadt 1245f2ab88Sderaadt #include "config.h" 1345f2ab88Sderaadt 14df930be7Sderaadt #ifndef lint 1532ceefb1Sdownsj static const char sccsid[] = "@(#)ex_global.c 10.22 (Berkeley) 10/10/96"; 16df930be7Sderaadt #endif /* not lint */ 17df930be7Sderaadt 18df930be7Sderaadt #include <sys/types.h> 19df930be7Sderaadt #include <sys/queue.h> 20df930be7Sderaadt 21df930be7Sderaadt #include <bitstring.h> 22df930be7Sderaadt #include <ctype.h> 23df930be7Sderaadt #include <errno.h> 24df930be7Sderaadt #include <limits.h> 25df930be7Sderaadt #include <stdio.h> 26df930be7Sderaadt #include <stdlib.h> 27df930be7Sderaadt #include <string.h> 28df930be7Sderaadt #include <unistd.h> 29df930be7Sderaadt 3045f2ab88Sderaadt #include "../common/common.h" 31df930be7Sderaadt 3245f2ab88Sderaadt enum which {GLOBAL, V}; 33df930be7Sderaadt 34c72b5b24Smillert static int ex_g_setup(SCR *, EXCMD *, enum which); 35df930be7Sderaadt 36df930be7Sderaadt /* 37df930be7Sderaadt * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands] 38df930be7Sderaadt * Exec on lines matching a pattern. 3945f2ab88Sderaadt * 40c72b5b24Smillert * PUBLIC: int ex_global(SCR *, EXCMD *); 41df930be7Sderaadt */ 42df930be7Sderaadt int 4345f2ab88Sderaadt ex_global(sp, cmdp) 44df930be7Sderaadt SCR *sp; 4545f2ab88Sderaadt EXCMD *cmdp; 46df930be7Sderaadt { 4745f2ab88Sderaadt return (ex_g_setup(sp, 4845f2ab88Sderaadt cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL)); 49df930be7Sderaadt } 50df930be7Sderaadt 51df930be7Sderaadt /* 5245f2ab88Sderaadt * ex_v -- [line [,line]] v /pattern/ [commands] 53df930be7Sderaadt * Exec on lines not matching a pattern. 5445f2ab88Sderaadt * 55c72b5b24Smillert * PUBLIC: int ex_v(SCR *, EXCMD *); 56df930be7Sderaadt */ 57df930be7Sderaadt int 5845f2ab88Sderaadt ex_v(sp, cmdp) 59df930be7Sderaadt SCR *sp; 6045f2ab88Sderaadt EXCMD *cmdp; 61df930be7Sderaadt { 6245f2ab88Sderaadt return (ex_g_setup(sp, cmdp, V)); 63df930be7Sderaadt } 64df930be7Sderaadt 6545f2ab88Sderaadt /* 6645f2ab88Sderaadt * ex_g_setup -- 6745f2ab88Sderaadt * Ex global and v commands. 6845f2ab88Sderaadt */ 69df930be7Sderaadt static int 7045f2ab88Sderaadt ex_g_setup(sp, cmdp, cmd) 71df930be7Sderaadt SCR *sp; 7245f2ab88Sderaadt EXCMD *cmdp; 73df930be7Sderaadt enum which cmd; 74df930be7Sderaadt { 7532ceefb1Sdownsj CHAR_T *ptrn, *p, *t; 7645f2ab88Sderaadt EXCMD *ecp; 77df930be7Sderaadt MARK abs; 78df930be7Sderaadt RANGE *rp; 7945f2ab88Sderaadt busy_t btype; 8045f2ab88Sderaadt recno_t start, end; 8145f2ab88Sderaadt regex_t *re; 82df930be7Sderaadt regmatch_t match[1]; 8345f2ab88Sderaadt size_t len; 8445f2ab88Sderaadt int cnt, delim, eval; 8532ceefb1Sdownsj char *dbp; 8645f2ab88Sderaadt 8745f2ab88Sderaadt NEEDFILE(sp, cmdp); 8845f2ab88Sderaadt 8945f2ab88Sderaadt if (F_ISSET(sp, SC_EX_GLOBAL)) { 9045f2ab88Sderaadt msgq(sp, M_ERR, 9145f2ab88Sderaadt "124|The %s command can't be used as part of a global or v command", 9245f2ab88Sderaadt cmdp->cmd->name); 9345f2ab88Sderaadt return (1); 9445f2ab88Sderaadt } 95df930be7Sderaadt 96df930be7Sderaadt /* 9745f2ab88Sderaadt * Skip leading white space. Historic vi allowed any non-alphanumeric 9845f2ab88Sderaadt * to serve as the global command delimiter. 99df930be7Sderaadt */ 10045f2ab88Sderaadt if (cmdp->argc == 0) 10145f2ab88Sderaadt goto usage; 102df930be7Sderaadt for (p = cmdp->argv[0]->bp; isblank(*p); ++p); 10345f2ab88Sderaadt if (*p == '\0' || isalnum(*p) || 10445f2ab88Sderaadt *p == '\\' || *p == '|' || *p == '\n') { 10545f2ab88Sderaadt usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); 106df930be7Sderaadt return (1); 107df930be7Sderaadt } 108df930be7Sderaadt delim = *p++; 109df930be7Sderaadt 110df930be7Sderaadt /* 111df930be7Sderaadt * Get the pattern string, toss escaped characters. 112df930be7Sderaadt * 113df930be7Sderaadt * QUOTING NOTE: 114df930be7Sderaadt * Only toss an escaped character if it escapes a delimiter. 115df930be7Sderaadt */ 116df930be7Sderaadt for (ptrn = t = p;;) { 117df930be7Sderaadt if (p[0] == '\0' || p[0] == delim) { 118df930be7Sderaadt if (p[0] == delim) 119df930be7Sderaadt ++p; 120df930be7Sderaadt /* 121df930be7Sderaadt * !!! 122df930be7Sderaadt * Nul terminate the pattern string -- it's passed 123df930be7Sderaadt * to regcomp which doesn't understand anything else. 124df930be7Sderaadt */ 125df930be7Sderaadt *t = '\0'; 126df930be7Sderaadt break; 127df930be7Sderaadt } 128*ddc53e66Smiod if (p[0] == '\\') { 12945f2ab88Sderaadt if (p[1] == delim) 130df930be7Sderaadt ++p; 13145f2ab88Sderaadt else if (p[1] == '\\') 13245f2ab88Sderaadt *t++ = *p++; 133*ddc53e66Smiod } 134df930be7Sderaadt *t++ = *p++; 135df930be7Sderaadt } 136df930be7Sderaadt 137df930be7Sderaadt /* If the pattern string is empty, use the last one. */ 138df930be7Sderaadt if (*ptrn == '\0') { 13945f2ab88Sderaadt if (sp->re == NULL) { 14045f2ab88Sderaadt ex_emsg(sp, NULL, EXM_NOPREVRE); 141df930be7Sderaadt return (1); 142df930be7Sderaadt } 14345f2ab88Sderaadt 144e91eb1deSmickey /* Re-compile the RE if necessary. */ 145e91eb1deSmickey if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, 146e91eb1deSmickey sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) 14745f2ab88Sderaadt return (1); 148df930be7Sderaadt } else { 149df930be7Sderaadt /* Compile the RE. */ 150e91eb1deSmickey if (re_compile(sp, ptrn, t - ptrn, 151e91eb1deSmickey &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) 152df930be7Sderaadt return (1); 153df930be7Sderaadt 154df930be7Sderaadt /* 15545f2ab88Sderaadt * Set saved RE. Historic practice is that globals set 15645f2ab88Sderaadt * direction as well as the RE. 157df930be7Sderaadt */ 158df930be7Sderaadt sp->searchdir = FORWARD; 159df930be7Sderaadt } 16045f2ab88Sderaadt re = &sp->re_c; 161df930be7Sderaadt 162df930be7Sderaadt /* The global commands always set the previous context mark. */ 163df930be7Sderaadt abs.lno = sp->lno; 164df930be7Sderaadt abs.cno = sp->cno; 16545f2ab88Sderaadt if (mark_set(sp, ABSMARK1, &abs, 1)) 16645f2ab88Sderaadt return (1); 16745f2ab88Sderaadt 16845f2ab88Sderaadt /* Get an EXCMD structure. */ 16945f2ab88Sderaadt CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD)); 17045f2ab88Sderaadt CIRCLEQ_INIT(&ecp->rq); 17145f2ab88Sderaadt 17245f2ab88Sderaadt /* 17345f2ab88Sderaadt * Get a copy of the command string; the default command is print. 17445f2ab88Sderaadt * Don't worry about a set of <blank>s with no command, that will 17545f2ab88Sderaadt * default to print in the ex parser. We need to have two copies 17645f2ab88Sderaadt * because the ex parser may step on the command string when it's 17745f2ab88Sderaadt * parsing it. 17845f2ab88Sderaadt */ 17932ceefb1Sdownsj if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) { 18045f2ab88Sderaadt p = "pp"; 18145f2ab88Sderaadt len = 1; 18245f2ab88Sderaadt } 18345f2ab88Sderaadt 184e91eb1deSmickey MALLOC_RET(sp, ecp->cp, char *, len * 2); 18545f2ab88Sderaadt ecp->o_cp = ecp->cp; 18645f2ab88Sderaadt ecp->o_clen = len; 187e91eb1deSmickey memcpy(ecp->cp + len, p, len); 18845f2ab88Sderaadt ecp->range_lno = OOBLNO; 18945f2ab88Sderaadt FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V); 19045f2ab88Sderaadt LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q); 191df930be7Sderaadt 192df930be7Sderaadt /* 193df930be7Sderaadt * For each line... The semantics of global matching are that we first 194df930be7Sderaadt * have to decide which lines are going to get passed to the command, 195df930be7Sderaadt * and then pass them to the command, ignoring other changes. There's 196df930be7Sderaadt * really no way to do this in a single pass, since arbitrary line 197df930be7Sderaadt * creation, deletion and movement can be done in the ex command. For 198df930be7Sderaadt * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d". 199df930be7Sderaadt * What we do is create linked list of lines that are tracked through 200df930be7Sderaadt * each ex command. There's a callback routine which the DB interface 201df930be7Sderaadt * routines call when a line is created or deleted. This doesn't help 202df930be7Sderaadt * the layering much. 203df930be7Sderaadt */ 20445f2ab88Sderaadt btype = BUSY_ON; 20545f2ab88Sderaadt cnt = INTERRUPT_CHECK; 20645f2ab88Sderaadt for (start = cmdp->addr1.lno, 20745f2ab88Sderaadt end = cmdp->addr2.lno; start <= end; ++start) { 20845f2ab88Sderaadt if (cnt-- == 0) { 20945f2ab88Sderaadt if (INTERRUPTED(sp)) { 21045f2ab88Sderaadt LIST_REMOVE(ecp, q); 21145f2ab88Sderaadt free(ecp->cp); 21245f2ab88Sderaadt free(ecp); 21345f2ab88Sderaadt break; 214df930be7Sderaadt } 21545f2ab88Sderaadt search_busy(sp, btype); 21645f2ab88Sderaadt btype = BUSY_UPDATE; 21745f2ab88Sderaadt cnt = INTERRUPT_CHECK; 21845f2ab88Sderaadt } 21932ceefb1Sdownsj if (db_get(sp, start, DBG_FATAL, &dbp, &len)) 22045f2ab88Sderaadt return (1); 221df930be7Sderaadt match[0].rm_so = 0; 222df930be7Sderaadt match[0].rm_eo = len; 22332ceefb1Sdownsj switch (eval = 22432ceefb1Sdownsj regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) { 225df930be7Sderaadt case 0: 22645f2ab88Sderaadt if (cmd == V) 227df930be7Sderaadt continue; 228df930be7Sderaadt break; 229df930be7Sderaadt case REG_NOMATCH: 230df930be7Sderaadt if (cmd == GLOBAL) 231df930be7Sderaadt continue; 232df930be7Sderaadt break; 233df930be7Sderaadt default: 23445f2ab88Sderaadt re_error(sp, eval, &sp->re_c); 23545f2ab88Sderaadt break; 236df930be7Sderaadt } 237df930be7Sderaadt 238df930be7Sderaadt /* If follows the last entry, extend the last entry's range. */ 23945f2ab88Sderaadt if ((rp = ecp->rq.cqh_last) != (void *)&ecp->rq && 24045f2ab88Sderaadt rp->stop == start - 1) { 241df930be7Sderaadt ++rp->stop; 242df930be7Sderaadt continue; 243df930be7Sderaadt } 244df930be7Sderaadt 245df930be7Sderaadt /* Allocate a new range, and append it to the list. */ 246df930be7Sderaadt CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE)); 247df930be7Sderaadt if (rp == NULL) 248df930be7Sderaadt return (1); 24945f2ab88Sderaadt rp->start = rp->stop = start; 25045f2ab88Sderaadt CIRCLEQ_INSERT_TAIL(&ecp->rq, rp, q); 251df930be7Sderaadt } 25245f2ab88Sderaadt search_busy(sp, BUSY_OFF); 25345f2ab88Sderaadt return (0); 254df930be7Sderaadt } 255df930be7Sderaadt 256df930be7Sderaadt /* 25745f2ab88Sderaadt * ex_g_insdel -- 258df930be7Sderaadt * Update the ranges based on an insertion or deletion. 25945f2ab88Sderaadt * 260c72b5b24Smillert * PUBLIC: int ex_g_insdel(SCR *, lnop_t, recno_t); 261df930be7Sderaadt */ 26245f2ab88Sderaadt int 26345f2ab88Sderaadt ex_g_insdel(sp, op, lno) 264df930be7Sderaadt SCR *sp; 26545f2ab88Sderaadt lnop_t op; 266df930be7Sderaadt recno_t lno; 267df930be7Sderaadt { 26845f2ab88Sderaadt EXCMD *ecp; 269df930be7Sderaadt RANGE *nrp, *rp; 270df930be7Sderaadt 27145f2ab88Sderaadt /* All insert/append operations are done as inserts. */ 27245f2ab88Sderaadt if (op == LINE_APPEND) 27345f2ab88Sderaadt abort(); 274df930be7Sderaadt 27545f2ab88Sderaadt if (op == LINE_RESET) 27645f2ab88Sderaadt return (0); 27745f2ab88Sderaadt 278ce7de1e9Sotto LIST_FOREACH(ecp, &sp->gp->ecq, q) { 27945f2ab88Sderaadt if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V)) 28045f2ab88Sderaadt continue; 281ce7de1e9Sotto for (rp = CIRCLEQ_FIRST(&ecp->rq); rp != CIRCLEQ_END(&ecp->rq); 282ce7de1e9Sotto rp = nrp) { 283ce7de1e9Sotto nrp = CIRCLEQ_NEXT(rp, q); 28445f2ab88Sderaadt 285df930be7Sderaadt /* If range less than the line, ignore it. */ 286df930be7Sderaadt if (rp->stop < lno) 287df930be7Sderaadt continue; 28845f2ab88Sderaadt 28945f2ab88Sderaadt /* 29045f2ab88Sderaadt * If range greater than the line, decrement or 29145f2ab88Sderaadt * increment the range. 29245f2ab88Sderaadt */ 293df930be7Sderaadt if (rp->start > lno) { 29445f2ab88Sderaadt if (op == LINE_DELETE) { 295df930be7Sderaadt --rp->start; 296df930be7Sderaadt --rp->stop; 29745f2ab88Sderaadt } else { 298df930be7Sderaadt ++rp->start; 299df930be7Sderaadt ++rp->stop; 30045f2ab88Sderaadt } 301df930be7Sderaadt continue; 302df930be7Sderaadt } 30345f2ab88Sderaadt 304df930be7Sderaadt /* 30545f2ab88Sderaadt * Lno is inside the range, decrement the end point 30645f2ab88Sderaadt * for deletion, and split the range for insertion. 30745f2ab88Sderaadt * In the latter case, since we're inserting a new 30845f2ab88Sderaadt * element, neither range can be exhausted. 309df930be7Sderaadt */ 31045f2ab88Sderaadt if (op == LINE_DELETE) { 31145f2ab88Sderaadt if (rp->start > --rp->stop) { 31245f2ab88Sderaadt CIRCLEQ_REMOVE(&ecp->rq, rp, q); 31345f2ab88Sderaadt free(rp); 314df930be7Sderaadt } 31545f2ab88Sderaadt } else { 31645f2ab88Sderaadt CALLOC_RET(sp, nrp, RANGE *, 1, sizeof(RANGE)); 317df930be7Sderaadt nrp->start = lno + 1; 318df930be7Sderaadt nrp->stop = rp->stop + 1; 319df930be7Sderaadt rp->stop = lno - 1; 32045f2ab88Sderaadt CIRCLEQ_INSERT_AFTER(&ecp->rq, rp, nrp, q); 321df930be7Sderaadt rp = nrp; 322df930be7Sderaadt } 323df930be7Sderaadt } 32445f2ab88Sderaadt 325df930be7Sderaadt /* 326df930be7Sderaadt * If the command deleted/inserted lines, the cursor moves to 327df930be7Sderaadt * the line after the deleted/inserted line. 328df930be7Sderaadt */ 32945f2ab88Sderaadt ecp->range_lno = lno; 33045f2ab88Sderaadt } 33145f2ab88Sderaadt return (0); 332df930be7Sderaadt } 333