| #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: |
| 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.q1; |
| 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 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->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] = '\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; |
| 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!=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; |
| QLock *q; |
| |
| 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->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); |
| /* |
| * The editoutlk exists only so that we can tell when |
| * the editout file has been closed. It can get closed *after* |
| * the process exits because, since the process cannot be |
| * connected directly to editout (no 9P kernel support), |
| * the process is actually connected to a pipe to another |
| * process (arranged via 9pserve) that reads from the pipe |
| * and then writes the data in the pipe to editout using |
| * 9P transactions. This process might still have a couple |
| * writes left to copy after the original process has exited. |
| */ |
| if(w) |
| q = &w->editoutlk; |
| else |
| q = &editoutlk; |
| qlock(q); /* wait for file to close */ |
| qunlock(q); |
| qlock(&row.lk); |
| editing = Inactive; |
| if(t!=nil && t->w!=nil) |
| winlock(t->w, 'M'); |
| } |
| |
| 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; |
| 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] = '\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. note this with globalincref so that any |
| * newly created windows start with an extra reference. |
| */ |
| allwindows(alllocker, (void*)1); |
| globalincref = 1; |
| for(i=0; i<loopstruct.nw; i++) |
| cmdexec(&loopstruct.w[i]->body, cp->u.cmd); |
| 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->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); |
| 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; |
| } |