aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/acme/ecmd.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2003-12-11 17:50:28 +0000
committerrsc <devnull@localhost>2003-12-11 17:50:28 +0000
commitb3994ec5c78e6c18885079b58abb7fb997899c3f (patch)
treed4ead391f5ebd1554cc5ecfba69130e750de67bb /src/cmd/acme/ecmd.c
parent32f69c36e0eec1227934bbd34854bfebd88686f2 (diff)
downloadplan9port-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.c1325
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 = &empty;
+ }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;
+}