ref: 02c6058f5d529e41afb0208c69f0cbf694b02b18
dir: /sys/src/cmd/acme/ecmd.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <thread.h> #include <cursor.h> #include <mouse.h> #include <keyboard.h> #include <frame.h> #include <fcall.h> #include <plumb.h> #include "dat.h" #include "edit.h" #include "fns.h" int Glooping; int nest; char Enoname[] = "no file name given"; Address addr; File *menu; Rangeset sel; extern Text* curtext; Rune *collection; int ncollection; int append(File*, Cmd*, long); int pdisplay(File*); void pfilename(File*); void looper(File*, Cmd*, int); void filelooper(Text*, Cmd*, int); void linelooper(File*, Cmd*); Address lineaddr(long, Address, int); int filematch(File*, String*); File *tofile(String*); Rune* cmdname(File *f, String *s, int); void runpipe(Text*, int, Rune*, int, int); void clearcollection(void) { free(collection); collection = nil; ncollection = 0; } void resetxec(void) { Glooping = nest = 0; clearcollection(); } void mkaddr(Address *a, File *f) { a->r.q0 = f->curtext->q0; a->r.q1 = f->curtext->q1; a->f = f; } int cmdexec(Text *t, Cmd *cp) { int i; Addr *ap; File *f; Window *w; Address dot; if(t == nil) w = nil; else w = t->w; if(w==nil && (cp->addr==0 || cp->addr->type!='"') && !utfrune("bBnqUXY!", cp->cmdc) && !(cp->cmdc=='D' && cp->text)) editerror("no current window"); i = cmdlookup(cp->cmdc); /* will be -1 for '{' */ f = nil; if(t && t->w){ t = &t->w->body; f = t->file; f->curtext = t; } if(i>=0 && cmdtab[i].defaddr != aNo){ if((ap=cp->addr)==0 && cp->cmdc!='\n'){ cp->addr = ap = newaddr(); ap->type = '.'; if(cmdtab[i].defaddr == aAll) ap->type = '*'; }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){ ap->next = newaddr(); ap->next->type = '.'; if(cmdtab[i].defaddr == aAll) ap->next->type = '*'; } if(cp->addr){ /* may be false for '\n' (only) */ static Address none = {0,0,nil}; if(f){ mkaddr(&dot, f); addr = cmdaddress(ap, dot, 0); }else /* a " */ addr = cmdaddress(ap, none, 0); f = addr.f; t = f->curtext; } } switch(cp->cmdc){ case '{': mkaddr(&dot, f); if(cp->addr != nil) dot = cmdaddress(cp->addr, dot, 0); for(cp = cp->cmd; cp; cp = cp->next){ if(dot.r.q1 > t->file->nc) editerror("dot extends past end of buffer during { command"); t->q0 = dot.r.q0; t->q1 = dot.r.q1; cmdexec(t, cp); } break; default: if(i < 0) editerror("unknown command %c in cmdexec", cp->cmdc); i = (*cmdtab[i].fn)(t, cp); return i; } return 1; } char* edittext(Window *w, int q, Rune *r, int nr) { File *f; switch(editing){ case Inactive: return "permission denied"; case Inserting: f = w->body.file; eloginsert(f, q, r, nr); return nil; case Collecting: collection = runerealloc(collection, ncollection+nr+1); runemove(collection+ncollection, r, nr); ncollection += nr; collection[ncollection] = '\0'; return nil; default: return "unknown state in edittext"; } } /* string is known to be NUL-terminated */ Rune* filelist(Text *t, Rune *r, int nr) { if(nr == 0) return nil; r = skipbl(r, nr, &nr); clearcollection(); if(r[0] != '<'){ if((collection = runestrdup(r)) != nil) ncollection += runestrlen(r); }else /* use < command to collect text */ runpipe(t, '<', r+1, nr-1, Collecting); return collection; } int a_cmd(Text *t, Cmd *cp) { return append(t->file, cp, addr.r.q1); } int b_cmd(Text*, Cmd *cp) { File *f; f = tofile(cp->text); if(nest == 0) pfilename(f); curtext = f->curtext; return TRUE; } int B_cmd(Text *t, Cmd *cp) { Rune *list, *r, *s; int nr; list = filelist(t, cp->text->r, cp->text->n); if(list == nil) editerror(Enoname); r = list; nr = runestrlen(r); r = skipbl(r, nr, &nr); if(nr == 0) new(t, t, nil, 0, 0, r, 0); else while(nr > 0){ s = findbl(r, nr, &nr); *s = '\0'; new(t, t, nil, 0, 0, r, runestrlen(r)); if(nr > 0) r = skipbl(s+1, nr-1, &nr); } clearcollection(); return TRUE; } int c_cmd(Text *t, Cmd *cp) { elogreplace(t->file, addr.r.q0, addr.r.q1, cp->text->r, cp->text->n); t->q0 = addr.r.q0; t->q1 = addr.r.q0; return TRUE; } int d_cmd(Text *t, Cmd*) { if(addr.r.q1 > addr.r.q0) elogdelete(t->file, addr.r.q0, addr.r.q1); t->q0 = addr.r.q0; t->q1 = addr.r.q0; return TRUE; } void D1(Text *t) { if(t->w->body.file->ntext>1 || winclean(t->w, FALSE)) colclose(t->col, t->w, TRUE); } int D_cmd(Text *t, Cmd *cp) { Rune *list, *r, *s, *n; int nr, nn; Window *w; Runestr dir, rs; char buf[128]; list = filelist(t, cp->text->r, cp->text->n); if(list == nil){ D1(t); return TRUE; } dir = dirname(t, nil, 0); r = list; nr = runestrlen(r); r = skipbl(r, nr, &nr); do{ s = findbl(r, nr, &nr); *s = '\0'; /* first time through, could be empty string, meaning delete file empty name */ nn = runestrlen(r); if(r[0]=='/' || nn==0 || dir.nr==0){ rs.r = runestrdup(r); rs.nr = nn; }else{ n = runemalloc(dir.nr+1+nn); runemove(n, dir.r, dir.nr); n[dir.nr] = '/'; runemove(n+dir.nr+1, r, nn); rs = cleanrname((Runestr){n, dir.nr+1+nn}); } w = lookfile(rs.r, rs.nr); if(w == nil){ snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r); free(rs.r); editerror(buf); } free(rs.r); D1(&w->body); if(nr > 0) r = skipbl(s+1, nr-1, &nr); }while(nr > 0); clearcollection(); free(dir.r); return TRUE; } static int readloader(void *v, uint q0, Rune *r, int nr) { if(nr > 0) eloginsert(v, q0, r, nr); return 0; } int e_cmd(Text *t, Cmd *cp) { Rune *name; File *f; int i, isdir, q0, q1, fd, nulls, samename, allreplaced; char *s, tmp[128]; Dir *d; f = t->file; q0 = addr.r.q0; q1 = addr.r.q1; if(cp->cmdc == 'e'){ if(winclean(t->w, TRUE)==FALSE) editerror(""); /* winclean generated message already */ q0 = 0; q1 = f->nc; } allreplaced = (q0==0 && q1==f->nc); name = cmdname(f, cp->text, cp->cmdc=='e'); if(name == nil) editerror(Enoname); i = runestrlen(name); samename = runeeq(name, i, t->file->name, t->file->nname); s = runetobyte(name, i); free(name); fd = open(s, OREAD); if(fd < 0){ snprint(tmp, sizeof tmp, "can't open %s: %r", s); free(s); editerror(tmp); } d = dirfstat(fd); isdir = (d!=nil && (d->qid.type&QTDIR)); free(d); if(isdir){ close(fd); snprint(tmp, sizeof tmp, "%s is a directory", s); free(s); editerror(tmp); } elogdelete(f, q0, q1); nulls = 0; loadfile(fd, q1, &nulls, readloader, f); free(s); close(fd); if(nulls) warning(nil, "%s: NUL bytes elided\n", s); else if(allreplaced && samename) f->editclean = TRUE; return TRUE; } int f_cmd(Text *t, Cmd *cp) { Rune *name; String *str; String empty; if(cp->text == nil){ empty.n = 0; empty.r = L""; str = ∅ }else str = cp->text; name = cmdname(t->file, str, TRUE); free(name); pfilename(t->file); return TRUE; } int g_cmd(Text *t, Cmd *cp) { if(t->file != addr.f){ warning(nil, "internal error: g_cmd f!=addr.f\n"); return FALSE; } if(rxcompile(cp->re->r) == FALSE) editerror("bad regexp in g command"); if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){ t->q0 = addr.r.q0; t->q1 = addr.r.q1; return cmdexec(t, cp->cmd); } return TRUE; } int i_cmd(Text *t, Cmd *cp) { return append(t->file, cp, addr.r.q0); } void copy(File *f, Address addr2) { long p; int ni; Rune *buf; buf = fbufalloc(); for(p=addr.r.q0; p<addr.r.q1; p+=ni){ ni = addr.r.q1-p; if(ni > RBUFSIZE) ni = RBUFSIZE; bufread(f, p, buf, ni); eloginsert(addr2.f, addr2.r.q1, buf, ni); } fbuffree(buf); } void move(File *f, Address addr2) { if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){ elogdelete(f, addr.r.q0, addr.r.q1); copy(f, addr2); }else if(addr.r.q0 >= addr2.r.q1){ copy(f, addr2); elogdelete(f, addr.r.q0, addr.r.q1); }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){ ; /* move to self; no-op */ }else editerror("move overlaps itself"); } int m_cmd(Text *t, Cmd *cp) { Address dot, addr2; mkaddr(&dot, t->file); addr2 = cmdaddress(cp->mtaddr, dot, 0); if(cp->cmdc == 'm') move(t->file, addr2); else copy(t->file, addr2); return TRUE; } int p_cmd(Text *t, Cmd*) { return pdisplay(t->file); } int s_cmd(Text *t, Cmd *cp) { int i, j, k, c, m, n, nrp, didsub; long p1, op, delta; String *buf; Rangeset *rp; char *err; Rune *rbuf; n = cp->num; op= -1; if(rxcompile(cp->re->r) == FALSE) editerror("bad regexp in s command"); nrp = 0; rp = nil; delta = 0; didsub = FALSE; for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){ if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */ if(sel.r[0].q0 == op){ p1++; continue; } p1 = sel.r[0].q1+1; }else p1 = sel.r[0].q1; op = sel.r[0].q1; if(--n>0) continue; nrp++; rp = erealloc(rp, nrp*sizeof(Rangeset)); rp[nrp-1] = sel; } rbuf = fbufalloc(); buf = allocstring(0); for(m=0; m<nrp; m++){ buf->n = 0; buf->r[0] = L'\0'; sel = rp[m]; for(i = 0; i<cp->text->n; i++) if((c = cp->text->r[i])=='\\' && i<cp->text->n-1){ c = cp->text->r[++i]; if('1'<=c && c<='9') { j = c-'0'; if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){ err = "replacement string too long"; goto Err; } bufread(t->file, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0); for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++) Straddc(buf, rbuf[k]); }else Straddc(buf, c); }else if(c!='&') Straddc(buf, c); else{ if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){ err = "right hand side too long in substitution"; goto Err; } bufread(t->file, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0); for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++) Straddc(buf, rbuf[k]); } elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n); delta -= sel.r[0].q1-sel.r[0].q0; delta += buf->n; didsub = 1; if(!cp->flag) break; } free(rp); freestring(buf); fbuffree(rbuf); if(!didsub && nest==0) editerror("no substitution"); t->q0 = addr.r.q0; t->q1 = addr.r.q1; return TRUE; Err: free(rp); freestring(buf); fbuffree(rbuf); editerror(err); return FALSE; } int u_cmd(Text *t, Cmd *cp) { int n, oseq, flag; n = cp->num; flag = TRUE; if(n < 0){ n = -n; flag = FALSE; } oseq = -1; while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){ oseq = t->file->seq; undo(t, nil, nil, flag, 0, nil, 0); } return TRUE; } int w_cmd(Text *t, Cmd *cp) { Rune *r; File *f; f = t->file; if(f->seq == seq) editerror("can't write file with pending modifications"); r = cmdname(f, cp->text, FALSE); if(r == nil) editerror("no name specified for 'w' command"); putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r)); /* r is freed by putfile */ return TRUE; } int x_cmd(Text *t, Cmd *cp) { if(cp->re) looper(t->file, cp, cp->cmdc=='x'); else linelooper(t->file, cp); return TRUE; } int X_cmd(Text *t, Cmd *cp) { filelooper(t, cp, cp->cmdc=='X'); return TRUE; } void runpipe(Text *t, int cmd, Rune *cr, int ncr, int state) { Rune *r, *s; int n; Runestr dir; Window *w; r = skipbl(cr, ncr, &n); if(n == 0) editerror("no command specified for %c", cmd); w = nil; if(state == Inserting){ w = t->w; t->q0 = addr.r.q0; t->q1 = addr.r.q1; if(cmd == '<' || cmd=='|') elogdelete(t->file, t->q0, t->q1); } s = runemalloc(n+2); s[0] = cmd; runemove(s+1, r, n); n++; dir.r = nil; dir.nr = 0; if(t != nil) dir = dirname(t, nil, 0); if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */ free(dir.r); dir.r = nil; dir.nr = 0; } editing = state; if(t!=nil && t->w!=nil) incref(t->w); /* run will decref */ run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE); free(s); if(t!=nil && t->w!=nil) winunlock(t->w); qunlock(&row); recvul(cedit); qlock(&row); editing = Inactive; if(t!=nil && t->w!=nil) winlock(t->w, 'M'); } int pipe_cmd(Text *t, Cmd *cp) { runpipe(t, cp->cmdc, cp->text->r, cp->text->n, Inserting); return TRUE; } long nlcount(Text *t, long q0, long q1, long *pnr) { long nl, start; Rune *buf; int i, nbuf; buf = fbufalloc(); nbuf = 0; i = nl = 0; start = q0; while(q0 < q1){ if(i == nbuf){ nbuf = q1-q0; if(nbuf > RBUFSIZE) nbuf = RBUFSIZE; bufread(t->file, q0, buf, nbuf); i = 0; } if(buf[i++] == '\n'){ start = q0+1; nl++; } q0++; } fbuffree(buf); if(pnr != nil) *pnr = q0 - start; return nl; } enum { PosnLine = 0, PosnChars = 1, PosnLineChars = 2, }; void printposn(Text *t, int mode) { long l1, l2, r1, r2; if (t != nil && t->file != nil && t->file->name != nil) warning(nil, "%.*S:", t->file->nname, t->file->name); switch(mode) { case PosnChars: warning(nil, "#%d", addr.r.q0); if(addr.r.q1 != addr.r.q0) warning(nil, ",#%d", addr.r.q1); warning(nil, "\n"); return; default: case PosnLine: l1 = 1+nlcount(t, 0, addr.r.q0, nil); l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, nil); /* check if addr ends with '\n' */ if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n') --l2; warning(nil, "%lud", l1); if(l2 != l1) warning(nil, ",%lud", l2); warning(nil, "\n"); return; case PosnLineChars: l1 = 1+nlcount(t, 0, addr.r.q0, &r1); l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, &r2); if(l2 == l1) r2 += r1; warning(nil, "%lud+#%lud", l1, r1); if(l2 != l1) warning(nil, ",%lud+#%lud", l2, r2); warning(nil, "\n"); return; } } int eq_cmd(Text *t, Cmd *cp) { int mode; switch(cp->text->n){ case 0: mode = PosnLine; break; case 1: if(cp->text->r[0] == '#'){ mode = PosnChars; break; } if(cp->text->r[0] == '+'){ mode = PosnLineChars; break; } default: SET(mode); editerror("newline expected"); } printposn(t, mode); return TRUE; } int nl_cmd(Text *t, Cmd *cp) { Address a; File *f; f = t->file; if(cp->addr == 0){ /* First put it on newline boundaries */ mkaddr(&a, f); addr = lineaddr(0, a, -1); a = lineaddr(0, a, 1); addr.r.q1 = a.r.q1; if(addr.r.q0==t->q0 && addr.r.q1==t->q1){ mkaddr(&a, f); addr = lineaddr(1, a, 1); } } textshow(t, addr.r.q0, addr.r.q1, 1); return TRUE; } int append(File *f, Cmd *cp, long p) { if(cp->text->n > 0) eloginsert(f, p, cp->text->r, cp->text->n); f->curtext->q0 = p; f->curtext->q1 = p; return TRUE; } int pdisplay(File *f) { long p1, p2; int np; Rune *buf; p1 = addr.r.q0; p2 = addr.r.q1; if(p2 > f->nc) p2 = f->nc; buf = fbufalloc(); while(p1 < p2){ np = p2-p1; if(np>RBUFSIZE-1) np = RBUFSIZE-1; bufread(f, p1, buf, np); buf[np] = L'\0'; warning(nil, "%S", buf); p1 += np; } fbuffree(buf); f->curtext->q0 = addr.r.q0; f->curtext->q1 = addr.r.q1; return TRUE; } void pfilename(File *f) { int dirty; Window *w; w = f->curtext->w; /* same check for dirty as in settag, but we know ncache==0 */ dirty = !w->isdir && !w->isscratch && f->mod; warning(nil, "%c%c%c %.*S\n", " '"[dirty], '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name); } void loopcmd(File *f, Cmd *cp, Range *rp, long nrp) { long i; for(i=0; i<nrp; i++){ f->curtext->q0 = rp[i].q0; f->curtext->q1 = rp[i].q1; cmdexec(f->curtext, cp); } } void looper(File *f, Cmd *cp, int xy) { long p, op, nrp; Range r, tr; Range *rp; r = addr.r; op= xy? -1 : r.q0; nest++; if(rxcompile(cp->re->r) == FALSE) editerror("bad regexp in %c command", cp->cmdc); nrp = 0; rp = nil; for(p = r.q0; p<=r.q1; ){ if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */ if(xy || op>r.q1) break; tr.q0 = op, tr.q1 = r.q1; p = r.q1+1; /* exit next loop */ }else{ if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */ if(sel.r[0].q0==op){ p++; continue; } p = sel.r[0].q1+1; }else p = sel.r[0].q1; if(xy) tr = sel.r[0]; else tr.q0 = op, tr.q1 = sel.r[0].q0; } op = sel.r[0].q1; nrp++; rp = erealloc(rp, nrp*sizeof(Range)); rp[nrp-1] = tr; } loopcmd(f, cp->cmd, rp, nrp); free(rp); --nest; } void linelooper(File *f, Cmd *cp) { long nrp, p; Range r, linesel; Address a, a3; Range *rp; nest++; nrp = 0; rp = nil; r = addr.r; a3.f = f; a3.r.q0 = a3.r.q1 = r.q0; a = lineaddr(0, a3, 1); linesel = a.r; for(p = r.q0; p<r.q1; p = a3.r.q1){ a3.r.q0 = a3.r.q1; if(p!=r.q0 || linesel.q1==p){ a = lineaddr(1, a3, 1); linesel = a.r; } if(linesel.q0 >= r.q1) break; if(linesel.q1 >= r.q1) linesel.q1 = r.q1; if(linesel.q1 > linesel.q0) if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){ a3.r = linesel; nrp++; rp = erealloc(rp, nrp*sizeof(Range)); rp[nrp-1] = linesel; continue; } break; } loopcmd(f, cp->cmd, rp, nrp); free(rp); --nest; } struct Looper { Cmd *cp; int XY; Window **w; int nw; } loopstruct; /* only one; X and Y can't nest */ void alllooper(Window *w, void *v) { Text *t; struct Looper *lp; Cmd *cp; lp = v; cp = lp->cp; // if(w->isscratch || w->isdir) // return; t = &w->body; /* only use this window if it's the current window for the file */ if(t->file->curtext != t) return; // if(w->nopen[QWevent] > 0) // return; /* no auto-execute on files without names */ if(cp->re==nil && t->file->nname==0) return; if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){ lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*)); lp->w[lp->nw++] = w; } } void alllocker(Window *w, void *v) { if(v) incref(w); else winclose(w); } void filelooper(Text *t, Cmd *cp, int XY) { int i; Text *targ; if(Glooping++) editerror("can't nest %c command", "YX"[XY]); nest++; loopstruct.cp = cp; loopstruct.XY = XY; if(loopstruct.w) /* error'ed out last time */ free(loopstruct.w); loopstruct.w = nil; loopstruct.nw = 0; allwindows(alllooper, &loopstruct); /* * add a ref to all windows to keep safe windows accessed by X * that would not otherwise have a ref to hold them up during * the shenanigans. note this with globalincref so that any * newly created windows start with an extra reference. */ allwindows(alllocker, (void*)1); globalincref = 1; /* * Unlock the window running the X command. * We'll need to lock and unlock each target window in turn. */ if(t && t->w) winunlock(t->w); for(i=0; i<loopstruct.nw; i++){ targ = &loopstruct.w[i]->body; if(targ && targ->w) winlock(targ->w, cp->cmdc); cmdexec(targ, cp->cmd); if(targ && targ->w) winunlock(targ->w); } if(t && t->w) winlock(t->w, cp->cmdc); allwindows(alllocker, (void*)0); globalincref = 0; free(loopstruct.w); loopstruct.w = nil; --Glooping; --nest; } void nextmatch(File *f, String *r, long p, int sign) { if(rxcompile(r->r) == FALSE) editerror("bad regexp in command address"); if(sign >= 0){ if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel)) editerror("no match for regexp"); if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){ if(++p>f->nc) p = 0; if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel)) editerror("address"); } }else{ if(!rxbexecute(f->curtext, p, &sel)) editerror("no match for regexp"); if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){ if(--p<0) p = f->nc; if(!rxbexecute(f->curtext, p, &sel)) editerror("address"); } } } File *matchfile(String*); Address charaddr(long, Address, int); Address lineaddr(long, Address, int); Address cmdaddress(Addr *ap, Address a, int sign) { File *f = a.f; Address a1, a2; do{ switch(ap->type){ case 'l': case '#': a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign); break; case '.': mkaddr(&a, f); break; case '$': a.r.q0 = a.r.q1 = f->nc; break; case '\'': editerror("can't handle '"); // a.r = f->mark; break; case '?': sign = -sign; if(sign == 0) sign = -1; /* fall through */ case '/': nextmatch(f, ap->re, sign>=0? a.r.q1 : a.r.q0, sign); a.r = sel.r[0]; break; case '"': f = matchfile(ap->re); mkaddr(&a, f); break; case '*': a.r.q0 = 0, a.r.q1 = f->nc; return a; case ',': case ';': if(ap->left) a1 = cmdaddress(ap->left, a, 0); else a1.f = a.f, a1.r.q0 = a1.r.q1 = 0; if(ap->type == ';'){ f = a1.f; a = a1; f->curtext->q0 = a1.r.q0; f->curtext->q1 = a1.r.q1; } if(ap->next) a2 = cmdaddress(ap->next, a, 0); else a2.f = a.f, a2.r.q0 = a2.r.q1 = f->nc; if(a1.f != a2.f) editerror("addresses in different files"); a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1; if(a.r.q1 < a.r.q0) editerror("addresses out of order"); return a; case '+': case '-': sign = 1; if(ap->type == '-') sign = -1; if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-') a = lineaddr(1L, a, sign); break; default: error("cmdaddress"); return a; } }while(ap = ap->next); /* assign = */ return a; } struct Tofile{ File *f; String *r; }; void alltofile(Window *w, void *v) { Text *t; struct Tofile *tp; tp = v; if(tp->f != nil) return; if(w->isscratch || w->isdir) return; t = &w->body; /* only use this window if it's the current window for the file */ if(t->file->curtext != t) return; // if(w->nopen[QWevent] > 0) // return; if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname)) tp->f = t->file; } File* tofile(String *r) { struct Tofile t; String rr; rr.r = skipbl(r->r, r->n, &rr.n); t.f = nil; t.r = &rr; allwindows(alltofile, &t); if(t.f == nil) editerror("no such file\"%S\"", rr.r); return t.f; } void allmatchfile(Window *w, void *v) { struct Tofile *tp; Text *t; tp = v; if(w->isscratch || w->isdir) return; t = &w->body; /* only use this window if it's the current window for the file */ if(t->file->curtext != t) return; // if(w->nopen[QWevent] > 0) // return; if(filematch(w->body.file, tp->r)){ if(tp->f != nil) editerror("too many files match \"%S\"", tp->r->r); tp->f = w->body.file; } } File* matchfile(String *r) { struct Tofile tf; tf.f = nil; tf.r = r; allwindows(allmatchfile, &tf); if(tf.f == nil) editerror("no file matches \"%S\"", r->r); return tf.f; } int filematch(File *f, String *r) { char *buf; Rune *rbuf; Window *w; int match, i, dirty; Rangeset s; /* compile expr first so if we get an error, we haven't allocated anything */ if(rxcompile(r->r) == FALSE) editerror("bad regexp in file match"); buf = fbufalloc(); w = f->curtext->w; /* same check for dirty as in settag, but we know ncache==0 */ dirty = !w->isdir && !w->isscratch && f->mod; snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty], '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name); rbuf = bytetorune(buf, &i); fbuffree(buf); match = rxexecute(nil, rbuf, 0, i, &s); free(rbuf); return match; } Address charaddr(long l, Address addr, int sign) { if(sign == 0) addr.r.q0 = addr.r.q1 = l; else if(sign < 0) addr.r.q1 = addr.r.q0 -= l; else if(sign > 0) addr.r.q0 = addr.r.q1 += l; if(addr.r.q0<0 || addr.r.q1>addr.f->nc) editerror("address out of range"); return addr; } Address lineaddr(long l, Address addr, int sign) { int n; int c; File *f = addr.f; Address a; long p; a.f = f; if(sign >= 0){ if(l == 0){ if(sign==0 || addr.r.q1==0){ a.r.q0 = a.r.q1 = 0; return a; } a.r.q0 = addr.r.q1; p = addr.r.q1-1; }else{ if(sign==0 || addr.r.q1==0){ p = 0; n = 1; }else{ p = addr.r.q1-1; n = textreadc(f->curtext, p++)=='\n'; } while(n < l){ if(p >= f->nc) editerror("address out of range"); if(textreadc(f->curtext, p++) == '\n') n++; } a.r.q0 = p; } while(p < f->nc && textreadc(f->curtext, p++)!='\n') ; a.r.q1 = p; }else{ p = addr.r.q0; if(l == 0) a.r.q1 = addr.r.q0; else{ for(n = 0; n<l; ){ /* always runs once */ if(p == 0){ if(++n != l) editerror("address out of range"); }else{ c = textreadc(f->curtext, p-1); if(c != '\n' || ++n != l) p--; } } a.r.q1 = p; if(p > 0) p--; } while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */ p--; a.r.q0 = p; } return a; } struct Filecheck { File *f; Rune *r; int nr; }; void allfilecheck(Window *w, void *v) { struct Filecheck *fp; File *f; fp = v; f = w->body.file; if(w->body.file == fp->f) return; if(runeeq(fp->r, fp->nr, f->name, f->nname)) warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r); } Rune* cmdname(File *f, String *str, int set) { Rune *r, *s; int n; struct Filecheck fc; Runestr newname; r = nil; n = str->n; s = str->r; if(n == 0){ /* no name; use existing */ if(f->nname == 0) return nil; r = runemalloc(f->nname+1); runemove(r, f->name, f->nname); return r; } s = skipbl(s, n, &n); if(n == 0) goto Return; if(s[0] == '/'){ r = runemalloc(n+1); runemove(r, s, n); }else{ newname = dirname(f->curtext, runestrdup(s), n); n = newname.nr; r = runemalloc(n+1); /* NUL terminate */ runemove(r, newname.r, n); free(newname.r); } fc.f = f; fc.r = r; fc.nr = n; allwindows(allfilecheck, &fc); if(f->nname == 0) set = TRUE; Return: if(set && !runeeq(r, n, f->name, f->nname)){ filemark(f); f->mod = TRUE; f->curtext->w->dirty = TRUE; winsetname(f->curtext->w, r, n); } return r; }