diff options
author | rsc <devnull@localhost> | 2003-12-11 17:50:28 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2003-12-11 17:50:28 +0000 |
commit | b3994ec5c78e6c18885079b58abb7fb997899c3f (patch) | |
tree | d4ead391f5ebd1554cc5ecfba69130e750de67bb /src/cmd/acme/ecmd.c | |
parent | 32f69c36e0eec1227934bbd34854bfebd88686f2 (diff) | |
download | plan9port-b3994ec5c78e6c18885079b58abb7fb997899c3f.tar.gz plan9port-b3994ec5c78e6c18885079b58abb7fb997899c3f.tar.bz2 plan9port-b3994ec5c78e6c18885079b58abb7fb997899c3f.zip |
More files related to user-level file servers.
Also add acme!
Diffstat (limited to 'src/cmd/acme/ecmd.c')
-rw-r--r-- | src/cmd/acme/ecmd.c | 1325 |
1 files changed, 1325 insertions, 0 deletions
diff --git a/src/cmd/acme/ecmd.c b/src/cmd/acme/ecmd.c new file mode 100644 index 00000000..677bafb1 --- /dev/null +++ b/src/cmd/acme/ecmd.c @@ -0,0 +1,1325 @@ +#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(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->u.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->u.cmd; cp; cp = cp->next){ + if(dot.r.q1 > t->file->b.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; + + f = w->body.file; + switch(editing){ + case Inactive: + return "permission denied"; + case Inserting: + w->neditwrsel += nr; + 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); + if(r[0] != '<') + return runestrdup(r); + /* use < command to collect text */ + clearcollection(); + 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 *t, Cmd *cp) +{ + File *f; + + USED(t); + f = tofile(cp->u.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->u.text->r, cp->u.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->u.text->r, cp->u.text->n); + t->q0 = addr.r.q0; + t->q1 = addr.r.q0+cp->u.text->n; + return TRUE; +} + +int +d_cmd(Text *t, Cmd *cp) +{ + USED(cp); + 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->u.text->r, cp->u.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->b.nc; + } + allreplaced = (q0==0 && q1==f->b.nc); + name = cmdname(f, cp->u.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; +} + +static Rune Lempty[] = { 0 }; +int +f_cmd(Text *t, Cmd *cp) +{ + Rune *name; + String *str; + String empty; + + if(cp->u.text == nil){ + empty.n = 0; + empty.r = Lempty; + str = ∅ + }else + str = cp->u.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->u.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->b, 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 + error("move overlaps itself"); +} + +int +m_cmd(Text *t, Cmd *cp) +{ + Address dot, addr2; + + mkaddr(&dot, t->file); + addr2 = cmdaddress(cp->u.mtaddr, dot, 0); + if(cp->cmdc == 'm') + move(t->file, addr2); + else + copy(t->file, addr2); + return TRUE; +} + +int +p_cmd(Text *t, Cmd *cp) +{ + USED(cp); + 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->u.text->n; i++) + if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){ + c = cp->u.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->b, 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->b, 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+delta; + 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->u.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) +{ + USED(t); + + filelooper(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 >"); + w = nil; + if(state == Inserting){ + w = t->w; + t->q0 = addr.r.q0; + t->q1 = addr.r.q1; + w->neditwrsel = 0; + 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->ref); /* 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.lk); + recvul(cedit); + qlock(&row.lk); + editing = Inactive; + if(t!=nil && t->w!=nil) + winlock(t->w, 'M'); + if(state == Inserting){ + t->q0 = addr.r.q0; + t->q1 = addr.r.q0 + t->w->neditwrsel; + } +} + +int +pipe_cmd(Text *t, Cmd *cp) +{ + runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting); + return TRUE; +} + +long +nlcount(Text *t, long q0, long q1) +{ + long nl; + Rune *buf; + int i, nbuf; + + buf = fbufalloc(); + nbuf = 0; + i = nl = 0; + while(q0 < q1){ + if(i == nbuf){ + nbuf = q1-q0; + if(nbuf > RBUFSIZE) + nbuf = RBUFSIZE; + bufread(&t->file->b, q0, buf, nbuf); + i = 0; + } + if(buf[i++] == '\n') + nl++; + q0++; + } + fbuffree(buf); + return nl; +} + +void +printposn(Text *t, int charsonly) +{ + long l1, l2; + + if (t != nil && t->file != nil && t->file->name != nil) + warning(nil, "%.*S:", t->file->nname, t->file->name); + if(!charsonly){ + l1 = 1+nlcount(t, 0, addr.r.q0); + l2 = l1+nlcount(t, addr.r.q0, addr.r.q1); + /* 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; + } + warning(nil, "#%d", addr.r.q0); + if(addr.r.q1 != addr.r.q0) + warning(nil, ",#%d", addr.r.q1); + warning(nil, "\n"); +} + +int +eq_cmd(Text *t, Cmd *cp) +{ + int charsonly; + + switch(cp->u.text->n){ + case 0: + charsonly = FALSE; + break; + case 1: + if(cp->u.text->r[0] == '#'){ + charsonly = TRUE; + break; + } + default: + SET(charsonly); + editerror("newline expected"); + } + printposn(t, charsonly); + 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->u.text->n > 0) + eloginsert(f, p, cp->u.text->r, cp->u.text->n); + f->curtext->q0 = p; + f->curtext->q1 = p+cp->u.text->n; + return TRUE; +} + +int +pdisplay(File *f) +{ + long p1, p2; + int np; + Rune *buf; + + p1 = addr.r.q0; + p2 = addr.r.q1; + if(p2 > f->b.nc) + p2 = f->b.nc; + buf = fbufalloc(); + while(p1 < p2){ + np = p2-p1; + if(np>RBUFSIZE-1) + np = RBUFSIZE-1; + bufread(&f->b, 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->u.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->u.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->ref); + else + winclose(w); +} + +void +filelooper(Cmd *cp, int XY) +{ + int i; + + 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. + */ + allwindows(alllocker, (void*)1); + for(i=0; i<loopstruct.nw; i++) + cmdexec(&loopstruct.w[i]->body, cp->u.cmd); + allwindows(alllocker, (void*)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->b.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->b.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->b.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->u.re, sign>=0? a.r.q1 : a.r.q0, sign); + a.r = sel.r[0]; + break; + + case '"': + f = matchfile(ap->u.re); + mkaddr(&a, f); + break; + + case '*': + a.r.q0 = 0, a.r.q1 = f->b.nc; + return a; + + case ',': + case ';': + if(ap->u.left) + a1 = cmdaddress(ap->u.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->b.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->b.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->b.nc) + editerror("address out of range"); + if(textreadc(f->curtext, p++) == '\n') + n++; + } + a.r.q0 = p; + } + while(p < f->b.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); + r = newname.r; + n = newname.nr; + } + 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; +} |