diff options
Diffstat (limited to 'src/cmd')
-rw-r--r-- | src/cmd/acme/acme.c | 949 | ||||
-rw-r--r-- | src/cmd/acme/addr.c | 269 | ||||
-rw-r--r-- | src/cmd/acme/buff.c | 322 | ||||
-rw-r--r-- | src/cmd/acme/cols.c | 556 | ||||
-rw-r--r-- | src/cmd/acme/dat.h | 546 | ||||
-rw-r--r-- | src/cmd/acme/disk.c | 129 | ||||
-rw-r--r-- | src/cmd/acme/ecmd.c | 1325 | ||||
-rw-r--r-- | src/cmd/acme/edit.c | 682 | ||||
-rw-r--r-- | src/cmd/acme/edit.h | 101 | ||||
-rw-r--r-- | src/cmd/acme/elog.c | 350 | ||||
-rw-r--r-- | src/cmd/acme/exec.c | 1491 | ||||
-rw-r--r-- | src/cmd/acme/file.c | 310 | ||||
-rw-r--r-- | src/cmd/acme/fns.h | 92 | ||||
-rw-r--r-- | src/cmd/acme/fsys.c | 717 | ||||
-rw-r--r-- | src/cmd/acme/look.c | 772 | ||||
-rw-r--r-- | src/cmd/acme/mkfile | 41 | ||||
-rw-r--r-- | src/cmd/acme/regx.c | 835 | ||||
-rw-r--r-- | src/cmd/acme/rows.c | 731 | ||||
-rw-r--r-- | src/cmd/acme/scrl.c | 165 | ||||
-rw-r--r-- | src/cmd/acme/text.c | 1221 | ||||
-rw-r--r-- | src/cmd/acme/time.c | 121 | ||||
-rw-r--r-- | src/cmd/acme/util.c | 395 | ||||
-rw-r--r-- | src/cmd/acme/wind.c | 576 | ||||
-rw-r--r-- | src/cmd/acme/xfid.c | 1046 |
24 files changed, 13742 insertions, 0 deletions
diff --git a/src/cmd/acme/acme.c b/src/cmd/acme/acme.c new file mode 100644 index 00000000..d9412c4e --- /dev/null +++ b/src/cmd/acme/acme.c @@ -0,0 +1,949 @@ +#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 "fns.h" + /* for generating syms in mkfile only: */ + #include <bio.h> + #include "edit.h" + +void mousethread(void*); +void keyboardthread(void*); +void waitthread(void*); +void xfidallocthread(void*); +void newwindowthread(void*); +void plumbproc(void*); + +Reffont **fontcache; +int nfontcache; +char wdir[512] = "."; +Reffont *reffonts[2]; +int snarffd = -1; +int mainpid; +int plumbsendfd; +int plumbeditfd; + +enum{ + NSnarf = 1000 /* less than 1024, I/O buffer size */ +}; +Rune snarfrune[NSnarf+1]; + +char *fontnames[2] = +{ + "/lib/font/bit/lucidasans/euro.8.font", + "/lib/font/bit/lucm/unicode.9.font", +}; + +Command *command; + +void acmeerrorinit(void); +void readfile(Column*, char*); +int shutdown(void*, char*); + +void +derror(Display *d, char *errorstr) +{ + USED(d); + error(errorstr); +} + +void +threadmain(int argc, char *argv[]) +{ + int i; + char *p, *loadfile; + char buf[256]; + Column *c; + int ncol; + Display *d; + + rfork(RFENVG|RFNAMEG); + + ncol = -1; + + loadfile = nil; + ARGBEGIN{ + case 'b': + bartflag = TRUE; + break; + case 'c': + p = ARGF(); + if(p == nil) + goto Usage; + ncol = atoi(p); + if(ncol <= 0) + goto Usage; + break; + case 'f': + fontnames[0] = ARGF(); + if(fontnames[0] == nil) + goto Usage; + break; + case 'F': + fontnames[1] = ARGF(); + if(fontnames[1] == nil) + goto Usage; + break; + case 'l': + loadfile = ARGF(); + if(loadfile == nil) + goto Usage; + break; + default: + Usage: + fprint(2, "usage: acme -c ncol -f fontname -F fixedwidthfontname -l loadfile\n"); + exits("usage"); + }ARGEND + + cputype = getenv("cputype"); + objtype = getenv("objtype"); + home = getenv("home"); + p = getenv("tabstop"); + if(p != nil){ + maxtab = strtoul(p, nil, 0); + free(p); + } + if(maxtab == 0) + maxtab = 4; + if(loadfile) + rowloadfonts(loadfile); + putenv("font", fontnames[0]); + snarffd = open("/dev/snarf", OREAD|OCEXEC); +/* + if(cputype){ + sprint(buf, "/acme/bin/%s", cputype); + bind(buf, "/bin", MBEFORE); + } + bind("/acme/bin", "/bin", MBEFORE); +*/ + getwd(wdir, sizeof wdir); + +/* + if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){ + fprint(2, "acme: can't open display: %r\n"); + exits("geninitdraw"); + } +*/ + if(initdraw(derror, fontnames[0], "acme") < 0){ + fprint(2, "acme: can't open display: %r\n"); + exits("initdraw"); + } + + d = display; + font = d->defaultfont; + + reffont.f = font; + reffonts[0] = &reffont; + incref(&reffont.ref); /* one to hold up 'font' variable */ + incref(&reffont.ref); /* one to hold up reffonts[0] */ + fontcache = emalloc(sizeof(Reffont*)); + nfontcache = 1; + fontcache[0] = &reffont; + + iconinit(); + timerinit(); + rxinit(); + + cwait = threadwaitchan(); + ccommand = chancreate(sizeof(Command**), 0); + ckill = chancreate(sizeof(Rune*), 0); + cxfidalloc = chancreate(sizeof(Xfid*), 0); + cxfidfree = chancreate(sizeof(Xfid*), 0); + cnewwindow = chancreate(sizeof(Channel*), 0); + cerr = chancreate(sizeof(char*), 0); + cedit = chancreate(sizeof(int), 0); + cexit = chancreate(sizeof(int), 0); + if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil){ + fprint(2, "acme: can't create initial channels: %r\n"); + exits("channels"); + } + + mousectl = initmouse(nil, screen); + if(mousectl == nil){ + fprint(2, "acme: can't initialize mouse: %r\n"); + exits("mouse"); + } + mouse = &mousectl->m; + keyboardctl = initkeyboard(nil); + if(keyboardctl == nil){ + fprint(2, "acme: can't initialize keyboard: %r\n"); + exits("keyboard"); + } + mainpid = getpid(); + plumbeditfd = plumbopen("edit", OREAD|OCEXEC); + if(plumbeditfd < 0) + fprint(2, "acme: can't initialize plumber: %r\n"); + else{ + cplumb = chancreate(sizeof(Plumbmsg*), 0); + proccreate(plumbproc, nil, STACK); + } + plumbsendfd = plumbopen("send", OWRITE|OCEXEC); + + fsysinit(); + + #define WPERCOL 8 + disk = diskinit(); + if(loadfile) + rowload(&row, loadfile, TRUE); + else{ + rowinit(&row, screen->clipr); + if(ncol < 0){ + if(argc == 0) + ncol = 2; + else{ + ncol = (argc+(WPERCOL-1))/WPERCOL; + if(ncol < 2) + ncol = 2; + } + } + if(ncol == 0) + ncol = 2; + for(i=0; i<ncol; i++){ + c = rowadd(&row, nil, -1); + if(c==nil && i==0) + error("initializing columns"); + } + c = row.col[row.ncol-1]; + if(argc == 0) + readfile(c, wdir); + else + for(i=0; i<argc; i++){ + p = utfrrune(argv[i], '/'); + if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol) + readfile(c, argv[i]); + else + readfile(row.col[i/WPERCOL], argv[i]); + } + } + flushimage(display, 1); + + acmeerrorinit(); + threadcreate(keyboardthread, nil, STACK); + threadcreate(mousethread, nil, STACK); + threadcreate(waitthread, nil, STACK); + threadcreate(xfidallocthread, nil, STACK); + threadcreate(newwindowthread, nil, STACK); + + threadnotify(shutdown, 1); + recvul(cexit); + killprocs(); + threadexitsall(nil); +} + +void +readfile(Column *c, char *s) +{ + Window *w; + Rune rb[256]; + int nb, nr; + Runestr rs; + + w = coladd(c, nil, nil, -1); + cvttorunes(s, strlen(s), rb, &nb, &nr, nil); + rs = cleanrname((Runestr){rb, nr}); + winsetname(w, rs.r, rs.nr); + textload(&w->body, 0, s, 1); + w->body.file->mod = FALSE; + w->dirty = FALSE; + winsettag(w); + textscrdraw(&w->body); + textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc); +} + +char *oknotes[] ={ + "delete", + "hangup", + "kill", + "exit", + nil +}; + +int dumping; + +int +shutdown(void *v, char *msg) +{ + int i; + + if(strcmp(msg, "sys: write on closed pipe") == 0) + return 1; + + USED(v); + killprocs(); + if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){ + dumping = TRUE; + rowdump(&row, nil); + } + for(i=0; oknotes[i]; i++) + if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0) + threadexitsall(msg); + print("acme: %s\n", msg); + abort(); + return 0; +} + +void +killprocs(void) +{ + Command *c; + + fsysclose(); +// if(display) +// flushimage(display, 1); + + for(c=command; c; c=c->next) + postnote(PNGROUP, c->pid, "hangup"); +} + +static int errorfd; +int erroutfd; + +void +acmeerrorproc(void *v) +{ + char *buf; + int n; + + USED(v); + threadsetname("acmeerrorproc"); + buf = emalloc(8192+1); + while((n=read(errorfd, buf, 8192)) >= 0){ + buf[n] = '\0'; + sendp(cerr, estrdup(buf)); + } +} + +void +acmeerrorinit(void) +{ + int fd, pfd[2]; + char buf[64]; + + if(pipe(pfd) < 0) + error("can't create pipe"); +#if 0 + sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid); + fd = create(acmeerrorfile, OWRITE, 0666); + if(fd < 0){ + remove(acmeerrorfile); + fd = create(acmeerrorfile, OWRITE, 0666); + if(fd < 0) + error("can't create acmeerror file"); + } + sprint(buf, "%d", pfd[0]); + write(fd, buf, strlen(buf)); + close(fd); + /* reopen pfd[1] close on exec */ + sprint(buf, "/fd/%d", pfd[1]); + errorfd = open(buf, OREAD|OCEXEC); +#endif + fcntl(pfd[0], F_SETFD, FD_CLOEXEC); + fcntl(pfd[1], F_SETFD, FD_CLOEXEC); + erroutfd = pfd[0]; + errorfd = pfd[1]; + if(errorfd < 0) + error("can't re-open acmeerror file"); + proccreate(acmeerrorproc, nil, STACK); +} + +void +plumbproc(void *v) +{ + Plumbmsg *m; + + USED(v); + threadsetname("plumbproc"); + for(;;){ + m = plumbrecv(plumbeditfd); + if(m == nil) + threadexits(nil); + sendp(cplumb, m); + } +} + +void +keyboardthread(void *v) +{ + Rune r; + Timer *timer; + Text *t; + enum { KTimer, KKey, NKALT }; + static Alt alts[NKALT+1]; + + USED(v); + alts[KTimer].c = nil; + alts[KTimer].v = nil; + alts[KTimer].op = CHANNOP; + alts[KKey].c = keyboardctl->c; + alts[KKey].v = &r; + alts[KKey].op = CHANRCV; + alts[NKALT].op = CHANEND; + + timer = nil; + typetext = nil; + threadsetname("keyboardthread"); + for(;;){ + switch(alt(alts)){ + case KTimer: + timerstop(timer); + t = typetext; + if(t!=nil && t->what==Tag){ + winlock(t->w, 'K'); + wincommit(t->w, t); + winunlock(t->w); + flushimage(display, 1); + } + alts[KTimer].c = nil; + alts[KTimer].op = CHANNOP; + break; + case KKey: + casekeyboard: + typetext = rowtype(&row, r, mouse->xy); + t = typetext; + if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */ + activecol = t->col; + if(t!=nil && t->w!=nil) + t->w->body.file->curtext = &t->w->body; + if(timer != nil) + timercancel(timer); + if(t!=nil && t->what==Tag) { + timer = timerstart(500); + alts[KTimer].c = timer->c; + alts[KTimer].op = CHANRCV; + }else{ + timer = nil; + alts[KTimer].c = nil; + alts[KTimer].op = CHANNOP; + } + if(nbrecv(keyboardctl->c, &r) > 0) + goto casekeyboard; + flushimage(display, 1); + break; + } + } +} + +void +mousethread(void *v) +{ + Text *t, *argt; + int but; + uint q0, q1; + Window *w; + Plumbmsg *pm; + Mouse m; + char *act; + enum { MResize, MMouse, MPlumb, NMALT }; + static Alt alts[NMALT+1]; + + USED(v); + threadsetname("mousethread"); + alts[MResize].c = mousectl->resizec; + alts[MResize].v = nil; + alts[MResize].op = CHANRCV; + alts[MMouse].c = mousectl->c; + alts[MMouse].v = &mousectl->m; + alts[MMouse].op = CHANRCV; + alts[MPlumb].c = cplumb; + alts[MPlumb].v = ± + alts[MPlumb].op = CHANRCV; + if(cplumb == nil) + alts[MPlumb].op = CHANNOP; + alts[NMALT].op = CHANEND; + + for(;;){ + switch(alt(alts)){ + case MResize: + if(getwindow(display, Refnone) < 0) + error("attach to window"); + draw(screen, screen->r, display->white, nil, ZP); + scrlresize(); + rowresize(&row, screen->clipr); + flushimage(display, 1); + break; + case MPlumb: + if(strcmp(pm->type, "text") == 0){ + act = plumblookup(pm->attr, "action"); + if(act==nil || strcmp(act, "showfile")==0) + plumblook(pm); + else if(strcmp(act, "showdata")==0) + plumbshow(pm); + } + flushimage(display, 1); + plumbfree(pm); + break; + case MMouse: + /* + * Make a copy so decisions are consistent; mousectl changes + * underfoot. Can't just receive into m because this introduces + * another race; see /sys/src/libdraw/mouse.c. + */ + m = mousectl->m; + qlock(&row.lk); + t = rowwhich(&row, m.xy); + if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){ + winlock(mousetext->w, 'M'); + mousetext->eq0 = ~0; + wincommit(mousetext->w, mousetext); + winunlock(mousetext->w); + } + mousetext = t; + if(t == nil) + goto Continue; + w = t->w; + if(t==nil || m.buttons==0) + goto Continue; + but = 0; + if(m.buttons == 1) + but = 1; + else if(m.buttons == 2) + but = 2; + else if(m.buttons == 4) + but = 3; + barttext = t; + if(t->what==Body && ptinrect(m.xy, t->scrollr)){ + if(but){ + winlock(w, 'M'); + t->eq0 = ~0; + textscroll(t, but); + winunlock(w); + } + goto Continue; + } + if(ptinrect(m.xy, t->scrollr)){ + if(but){ + if(t->what == Columntag) + rowdragcol(&row, t->col, but); + else if(t->what == Tag){ + coldragwin(t->col, t->w, but); + if(t->w) + barttext = &t->w->body; + } + if(t->col) + activecol = t->col; + } + goto Continue; + } + if(m.buttons){ + if(w) + winlock(w, 'M'); + t->eq0 = ~0; + if(w) + wincommit(w, t); + else + textcommit(t, TRUE); + if(m.buttons & 1){ + textselect(t); + if(w) + winsettag(w); + argtext = t; + seltext = t; + if(t->col) + activecol = t->col; /* button 1 only */ + if(t->w!=nil && t==&t->w->body) + activewin = t->w; + }else if(m.buttons & 2){ + if(textselect2(t, &q0, &q1, &argt)) + execute(t, q0, q1, FALSE, argt); + }else if(m.buttons & 4){ + if(textselect3(t, &q0, &q1)) + look3(t, q0, q1, FALSE); + } + if(w) + winunlock(w); + goto Continue; + } + Continue: + flushimage(display, 1); + qunlock(&row.lk); + break; + } + } +} + +/* + * There is a race between process exiting and our finding out it was ever created. + * This structure keeps a list of processes that have exited we haven't heard of. + */ +typedef struct Pid Pid; +struct Pid +{ + int pid; + char msg[ERRMAX]; + Pid *next; +}; + +void +waitthread(void *v) +{ + Waitmsg *w; + Command *c, *lc; + uint pid; + int found, ncmd; + Rune *cmd; + char *err; + Text *t; + Pid *pids, *p, *lastp; + enum { WErr, WKill, WWait, WCmd, NWALT }; + Alt alts[NWALT+1]; + + USED(v); + threadsetname("waitthread"); + pids = nil; + alts[WErr].c = cerr; + alts[WErr].v = &err; + alts[WErr].op = CHANRCV; + alts[WKill].c = ckill; + alts[WKill].v = &cmd; + alts[WKill].op = CHANRCV; + alts[WWait].c = cwait; + alts[WWait].v = &w; + alts[WWait].op = CHANRCV; + alts[WCmd].c = ccommand; + alts[WCmd].v = &c; + alts[WCmd].op = CHANRCV; + alts[NWALT].op = CHANEND; + + command = nil; + for(;;){ + switch(alt(alts)){ + case WErr: + qlock(&row.lk); + warning(nil, "%s", err); + free(err); + flushimage(display, 1); + qunlock(&row.lk); + break; + case WKill: + found = FALSE; + ncmd = runestrlen(cmd); + for(c=command; c; c=c->next){ + /* -1 for blank */ + if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){ + if(postnote(PNGROUP, c->pid, "kill") < 0) + warning(nil, "kill %S: %r\n", cmd); + found = TRUE; + } + } + if(!found) + warning(nil, "Kill: no process %S\n", cmd); + free(cmd); + break; + case WWait: + pid = w->pid; + lc = nil; + for(c=command; c; c=c->next){ + if(c->pid == pid){ + if(lc) + lc->next = c->next; + else + command = c->next; + break; + } + lc = c; + } + qlock(&row.lk); + t = &row.tag; + textcommit(t, TRUE); + if(c == nil){ + /* helper processes use this exit status */ + if(strncmp(w->msg, "libthread", 9) != 0){ + p = emalloc(sizeof(Pid)); + p->pid = pid; + strncpy(p->msg, w->msg, sizeof(p->msg)); + p->next = pids; + pids = p; + } + }else{ + if(search(t, c->name, c->nname)){ + textdelete(t, t->q0, t->q1, TRUE); + textsetselect(t, 0, 0); + } + if(w->msg[0]) + warning(c->md, "%s: %s\n", c->name, w->msg); + flushimage(display, 1); + } + qunlock(&row.lk); + free(w); + Freecmd: + if(c){ + if(c->iseditcmd) + sendul(cedit, 0); + free(c->text); + free(c->name); + fsysdelid(c->md); + free(c); + } + break; + case WCmd: + /* has this command already exited? */ + lastp = nil; + for(p=pids; p!=nil; p=p->next){ + if(p->pid == c->pid){ + if(p->msg[0]) + warning(c->md, "%s\n", p->msg); + if(lastp == nil) + pids = p->next; + else + lastp->next = p->next; + free(p); + goto Freecmd; + } + lastp = p; + } + c->next = command; + command = c; + qlock(&row.lk); + t = &row.tag; + textcommit(t, TRUE); + textinsert(t, 0, c->name, c->nname, TRUE); + textsetselect(t, 0, 0); + flushimage(display, 1); + qunlock(&row.lk); + break; + } + } +} + +void +xfidallocthread(void *v) +{ + Xfid *xfree, *x; + enum { Alloc, Free, N }; + static Alt alts[N+1]; + + USED(v); + threadsetname("xfidallocthread"); + alts[Alloc].c = cxfidalloc; + alts[Alloc].v = nil; + alts[Alloc].op = CHANRCV; + alts[Free].c = cxfidfree; + alts[Free].v = &x; + alts[Free].op = CHANRCV; + alts[N].op = CHANEND; + + xfree = nil; + for(;;){ + switch(alt(alts)){ + case Alloc: + x = xfree; + if(x) + xfree = x->next; + else{ + x = emalloc(sizeof(Xfid)); + x->c = chancreate(sizeof(void(*)(Xfid*)), 0); + x->arg = x; + threadcreate(xfidctl, x->arg, STACK); + } + sendp(cxfidalloc, x); + break; + case Free: + x->next = xfree; + xfree = x; + break; + } + } +} + +/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */ +void +newwindowthread(void *v) +{ + Window *w; + + USED(v); + threadsetname("newwindowthread"); + + for(;;){ + /* only fsysproc is talking to us, so synchronization is trivial */ + recvp(cnewwindow); + w = makenewwindow(nil); + winsettag(w); + sendp(cnewwindow, w); + } +} + +Reffont* +rfget(int fix, int save, int setfont, char *name) +{ + Reffont *r; + Font *f; + int i; + + r = nil; + if(name == nil){ + name = fontnames[fix]; + r = reffonts[fix]; + } + if(r == nil){ + for(i=0; i<nfontcache; i++) + if(strcmp(name, fontcache[i]->f->name) == 0){ + r = fontcache[i]; + goto Found; + } + f = openfont(display, name); + if(f == nil){ + warning(nil, "can't open font file %s: %r\n", name); + return nil; + } + r = emalloc(sizeof(Reffont)); + r->f = f; + fontcache = realloc(fontcache, (nfontcache+1)*sizeof(Reffont*)); + fontcache[nfontcache++] = r; + } + Found: + if(save){ + incref(&r->ref); + if(reffonts[fix]) + rfclose(reffonts[fix]); + reffonts[fix] = r; + free(fontnames[fix]); + fontnames[fix] = name; + } + if(setfont){ + reffont.f = r->f; + incref(&r->ref); + rfclose(reffonts[0]); + font = r->f; + reffonts[0] = r; + incref(&r->ref); + iconinit(); + } + incref(&r->ref); + return r; +} + +void +rfclose(Reffont *r) +{ + int i; + + if(decref(&r->ref) == 0){ + for(i=0; i<nfontcache; i++) + if(r == fontcache[i]) + break; + if(i >= nfontcache) + warning(nil, "internal error: can't find font in cache\n"); + else{ + nfontcache--; + memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*)); + } + freefont(r->f); + free(r); + } +} + +Cursor boxcursor = { + {-7, -7}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, + 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00} +}; + +void +iconinit(void) +{ + Rectangle r; + Image *tmp; + + /* Blue */ + tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite); + tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen); + tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue); + tagcols[TEXT] = display->black; + tagcols[HTEXT] = display->black; + + /* Yellow */ + textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite); + textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow); + textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen); + textcols[TEXT] = display->black; + textcols[HTEXT] = display->black; + + if(button){ + freeimage(button); + freeimage(modbutton); + freeimage(colbutton); + } + + r = Rect(0, 0, Scrollwid+2, font->height+1); + button = allocimage(display, r, screen->chan, 0, DNofill); + draw(button, r, tagcols[BACK], nil, r.min); + r.max.x -= 2; + border(button, r, 2, tagcols[BORD], ZP); + + r = button->r; + modbutton = allocimage(display, r, screen->chan, 0, DNofill); + draw(modbutton, r, tagcols[BACK], nil, r.min); + r.max.x -= 2; + border(modbutton, r, 2, tagcols[BORD], ZP); + r = insetrect(r, 2); + tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue); + draw(modbutton, r, tmp, nil, ZP); + freeimage(tmp); + + r = button->r; + colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue); + + but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF); + but3col = allocimage(display, r, screen->chan, 1, 0x006600FF); +} + +/* + * /dev/snarf updates when the file is closed, so we must open our own + * fd here rather than use snarffd + */ + +/* rio truncates larges snarf buffers, so this avoids using the + * service if the string is huge */ + +#define MAXSNARF 100*1024 + +void +acmeputsnarf(void) +{ + int fd, i, n; + + if(snarffd<0 || snarfbuf.nc==0) + return; + if(snarfbuf.nc > MAXSNARF) + return; + fd = open("/dev/snarf", OWRITE); + if(fd < 0) + return; + for(i=0; i<snarfbuf.nc; i+=n){ + n = snarfbuf.nc-i; + if(n >= NSnarf) + n = NSnarf; + bufread(&snarfbuf, i, snarfrune, n); + if(fprint(fd, "%.*S", n, snarfrune) < 0) + break; + } + close(fd); +} + +void +acmegetsnarf() +{ + int nulls; + + if(snarfbuf.nc > MAXSNARF) + return; + if(snarffd < 0) + return; + seek(snarffd, 0, 0); + bufreset(&snarfbuf); + bufload(&snarfbuf, 0, snarffd, &nulls); +} diff --git a/src/cmd/acme/addr.c b/src/cmd/acme/addr.c new file mode 100644 index 00000000..d64db61b --- /dev/null +++ b/src/cmd/acme/addr.c @@ -0,0 +1,269 @@ +#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 "fns.h" + +enum +{ + None = 0, + Fore = '+', + Back = '-', +}; + +enum +{ + Char, + Line, +}; + +int +isaddrc(int r) +{ + if(r && utfrune("0123456789+-/$.#,;", r)!=nil) + return TRUE; + return FALSE; +} + +/* + * quite hard: could be almost anything but white space, but we are a little conservative, + * aiming for regular expressions of alphanumerics and no white space + */ +int +isregexc(int r) +{ + if(r == 0) + return FALSE; + if(isalnum(r)) + return TRUE; + if(utfrune("^+-.*?#,;[]()$", r)!=nil) + return TRUE; + return FALSE; +} + +Range +number(Mntdir *md, Text *t, Range r, int line, int dir, int size, int *evalp) +{ + uint q0, q1; + + if(size == Char){ + if(dir == Fore) + line = r.q1+line; + else if(dir == Back){ + if(r.q0==0 && line>0) + r.q0 = t->file->b.nc; + line = r.q0 - line; + } + if(line<0 || line>t->file->b.nc) + goto Rescue; + *evalp = TRUE; + return (Range){line, line}; + } + q0 = r.q0; + q1 = r.q1; + switch(dir){ + case None: + q0 = 0; + q1 = 0; + Forward: + while(line>0 && q1<t->file->b.nc) + if(textreadc(t, q1++) == '\n' || q1==t->file->b.nc) + if(--line > 0) + q0 = q1; + if(line > 0) + goto Rescue; + break; + case Fore: + if(q1 > 0) + while(textreadc(t, q1-1) != '\n') + q1++; + q0 = q1; + goto Forward; + case Back: + if(q0 < t->file->b.nc) + while(q0>0 && textreadc(t, q0-1)!='\n') + q0--; + q1 = q0; + while(line>0 && q0>0){ + if(textreadc(t, q0-1) == '\n'){ + if(--line >= 0) + q1 = q0; + } + --q0; + } + if(line > 0) + goto Rescue; + while(q0>0 && textreadc(t, q0-1)!='\n') + --q0; + } + *evalp = TRUE; + return (Range){q0, q1}; + + Rescue: + if(md != nil) + warning(nil, "address out of range\n"); + *evalp = FALSE; + return r; +} + + +Range +regexp(Mntdir *md, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp) +{ + int found; + Rangeset sel; + int q; + + if(pat[0] == '\0' && rxnull()){ + warning(md, "no previous regular expression\n"); + *foundp = FALSE; + return r; + } + if(pat[0] && rxcompile(pat) == FALSE){ + *foundp = FALSE; + return r; + } + if(dir == Back) + found = rxbexecute(t, r.q0, &sel); + else{ + if(lim.q0 < 0) + q = Infinity; + else + q = lim.q1; + found = rxexecute(t, nil, r.q1, q, &sel); + } + if(!found && md==nil) + warning(nil, "no match for regexp\n"); + *foundp = found; + return sel.r[0]; +} + +Range +address(Mntdir *md, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint), int *evalp, uint *qp) +{ + int dir, size, npat; + int prevc, c, nc, n; + uint q; + Rune *pat; + Range r, nr; + + r = ar; + q = q0; + dir = None; + size = Line; + c = 0; + while(q < q1){ + prevc = c; + c = (*getc)(a, q++); + switch(c){ + default: + *qp = q-1; + return r; + case ';': + ar = r; + /* fall through */ + case ',': + if(prevc == 0) /* lhs defaults to 0 */ + r.q0 = 0; + if(q>=q1 && t!=nil && t->file!=nil) /* rhs defaults to $ */ + r.q1 = t->file->b.nc; + else{ + nr = address(md, t, lim, ar, a, q, q1, getc, evalp, &q); + r.q1 = nr.q1; + } + *qp = q; + return r; + case '+': + case '-': + if(*evalp && (prevc=='+' || prevc=='-')) + if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?') + r = number(md, t, r, 1, prevc, Line, evalp); /* do previous one */ + dir = c; + break; + case '.': + case '$': + if(q != q0+1){ + *qp = q-1; + return r; + } + if(*evalp) + if(c == '.') + r = ar; + else + r = (Range){t->file->b.nc, t->file->b.nc}; + if(q < q1) + dir = Fore; + else + dir = None; + break; + case '#': + if(q==q1 || (c=(*getc)(a, q++))<'0' || '9'<c){ + *qp = q-1; + return r; + } + size = Char; + /* fall through */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = c -'0'; + while(q<q1){ + c = (*getc)(a, q++); + if(c<'0' || '9'<c){ + q--; + break; + } + n = n*10+(c-'0'); + } + if(*evalp) + r = number(md, t, r, n, dir, size, evalp); + dir = None; + size = Line; + break; + case '?': + dir = Back; + /* fall through */ + case '/': + npat = 0; + pat = nil; + while(q<q1){ + c = (*getc)(a, q++); + switch(c){ + case '\n': + --q; + goto out; + case '\\': + pat = runerealloc(pat, npat+1); + pat[npat++] = c; + if(q == q1) + goto out; + c = (*getc)(a, q++); + break; + case '/': + goto out; + } + pat = runerealloc(pat, npat+1); + pat[npat++] = c; + } + out: + pat = runerealloc(pat, npat+1); + pat[npat] = 0; + if(*evalp) + r = regexp(md, t, lim, r, pat, dir, evalp); + free(pat); + dir = None; + size = Line; + break; + } + } + if(*evalp && dir != None) + r = number(md, t, r, 1, dir, Line, evalp); /* do previous one */ + *qp = q; + return r; +} diff --git a/src/cmd/acme/buff.c b/src/cmd/acme/buff.c new file mode 100644 index 00000000..39982f17 --- /dev/null +++ b/src/cmd/acme/buff.c @@ -0,0 +1,322 @@ +#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 "fns.h" + +enum +{ + Slop = 100, /* room to grow with reallocation */ +}; + +static +void +sizecache(Buffer *b, uint n) +{ + if(n <= b->cmax) + return; + b->cmax = n+Slop; + b->c = runerealloc(b->c, b->cmax); +} + +static +void +addblock(Buffer *b, uint i, uint n) +{ + if(i > b->nbl) + error("internal error: addblock"); + + b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]); + if(i < b->nbl) + memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*)); + b->bl[i] = disknewblock(disk, n); + b->nbl++; +} + +static +void +delblock(Buffer *b, uint i) +{ + if(i >= b->nbl) + error("internal error: delblock"); + + diskrelease(disk, b->bl[i]); + b->nbl--; + if(i < b->nbl) + memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*)); + b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]); +} + +/* + * Move cache so b->cq <= q0 < b->cq+b->cnc. + * If at very end, q0 will fall on end of cache block. + */ + +static +void +flush(Buffer *b) +{ + if(b->cdirty || b->cnc==0){ + if(b->cnc == 0) + delblock(b, b->cbi); + else + diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc); + b->cdirty = FALSE; + } +} + +static +void +setcache(Buffer *b, uint q0) +{ + Block **blp, *bl; + uint i, q; + + if(q0 > b->nc) + error("internal error: setcache"); + /* + * flush and reload if q0 is not in cache. + */ + if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc)) + return; + /* + * if q0 is at end of file and end of cache, continue to grow this block + */ + if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<Maxblock) + return; + flush(b); + /* find block */ + if(q0 < b->cq){ + q = 0; + i = 0; + }else{ + q = b->cq; + i = b->cbi; + } + blp = &b->bl[i]; + while(q+(*blp)->u.n <= q0 && q+(*blp)->u.n < b->nc){ + q += (*blp)->u.n; + i++; + blp++; + if(i >= b->nbl) + error("block not found"); + } + bl = *blp; + /* remember position */ + b->cbi = i; + b->cq = q; + sizecache(b, bl->u.n); + b->cnc = bl->u.n; + /*read block*/ + diskread(disk, bl, b->c, b->cnc); +} + +void +bufinsert(Buffer *b, uint q0, Rune *s, uint n) +{ + uint i, m, t, off; + + if(q0 > b->nc) + error("internal error: bufinsert"); + + while(n > 0){ + setcache(b, q0); + off = q0-b->cq; + if(b->cnc+n <= Maxblock){ + /* Everything fits in one block. */ + t = b->cnc+n; + m = n; + if(b->bl == nil){ /* allocate */ + if(b->cnc != 0) + error("internal error: bufinsert1 cnc!=0"); + addblock(b, 0, t); + b->cbi = 0; + } + sizecache(b, t); + runemove(b->c+off+m, b->c+off, b->cnc-off); + runemove(b->c+off, s, m); + b->cnc = t; + goto Tail; + } + /* + * We must make a new block. If q0 is at + * the very beginning or end of this block, + * just make a new block and fill it. + */ + if(q0==b->cq || q0==b->cq+b->cnc){ + if(b->cdirty) + flush(b); + m = min(n, Maxblock); + if(b->bl == nil){ /* allocate */ + if(b->cnc != 0) + error("internal error: bufinsert2 cnc!=0"); + i = 0; + }else{ + i = b->cbi; + if(q0 > b->cq) + i++; + } + addblock(b, i, m); + sizecache(b, m); + runemove(b->c, s, m); + b->cq = q0; + b->cbi = i; + b->cnc = m; + goto Tail; + } + /* + * Split the block; cut off the right side and + * let go of it. + */ + m = b->cnc-off; + if(m > 0){ + i = b->cbi+1; + addblock(b, i, m); + diskwrite(disk, &b->bl[i], b->c+off, m); + b->cnc -= m; + } + /* + * Now at end of block. Take as much input + * as possible and tack it on end of block. + */ + m = min(n, Maxblock-b->cnc); + sizecache(b, b->cnc+m); + runemove(b->c+b->cnc, s, m); + b->cnc += m; + Tail: + b->nc += m; + q0 += m; + s += m; + n -= m; + b->cdirty = TRUE; + } +} + +void +bufdelete(Buffer *b, uint q0, uint q1) +{ + uint m, n, off; + + if(!(q0<=q1 && q0<=b->nc && q1<=b->nc)) + error("internal error: bufdelete"); + while(q1 > q0){ + setcache(b, q0); + off = q0-b->cq; + if(q1 > b->cq+b->cnc) + n = b->cnc - off; + else + n = q1-q0; + m = b->cnc - (off+n); + if(m > 0) + runemove(b->c+off, b->c+off+n, m); + b->cnc -= n; + b->cdirty = TRUE; + q1 -= n; + b->nc -= n; + } +} + +static int +bufloader(void *v, uint q0, Rune *r, int nr) +{ + bufinsert(v, q0, r, nr); + return nr; +} + +uint +loadfile(int fd, uint q0, int *nulls, int(*f)(void*, uint, Rune*, int), void *arg) +{ + char *p; + Rune *r; + int l, m, n, nb, nr; + uint q1; + + p = emalloc((Maxblock+UTFmax+1)*sizeof p[0]); + r = runemalloc(Maxblock); + m = 0; + n = 1; + q1 = q0; + /* + * At top of loop, may have m bytes left over from + * last pass, possibly representing a partial rune. + */ + while(n > 0){ + n = read(fd, p+m, Maxblock); + if(n < 0){ + warning(nil, "read error in Buffer.load"); + break; + } + m += n; + p[m] = 0; + l = m; + if(n > 0) + l -= UTFmax; + cvttorunes(p, l, r, &nb, &nr, nulls); + memmove(p, p+nb, m-nb); + m -= nb; + q1 += (*f)(arg, q1, r, nr); + } + free(p); + free(r); + return q1-q0; +} + +uint +bufload(Buffer *b, uint q0, int fd, int *nulls) +{ + if(q0 > b->nc) + error("internal error: bufload"); + return loadfile(fd, q0, nulls, bufloader, b); +} + +void +bufread(Buffer *b, uint q0, Rune *s, uint n) +{ + uint m; + + if(!(q0<=b->nc && q0+n<=b->nc)) + error("bufread: internal error"); + + while(n > 0){ + setcache(b, q0); + m = min(n, b->cnc-(q0-b->cq)); + runemove(s, b->c+(q0-b->cq), m); + q0 += m; + s += m; + n -= m; + } +} + +void +bufreset(Buffer *b) +{ + int i; + + b->nc = 0; + b->cnc = 0; + b->cq = 0; + b->cdirty = 0; + b->cbi = 0; + /* delete backwards to avoid n² behavior */ + for(i=b->nbl-1; --i>=0; ) + delblock(b, i); +} + +void +bufclose(Buffer *b) +{ + bufreset(b); + free(b->c); + b->c = nil; + b->cnc = 0; + free(b->bl); + b->bl = nil; + b->nbl = 0; +} diff --git a/src/cmd/acme/cols.c b/src/cmd/acme/cols.c new file mode 100644 index 00000000..0e6ff409 --- /dev/null +++ b/src/cmd/acme/cols.c @@ -0,0 +1,556 @@ +#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 "fns.h" + +static Rune Lheader[] = { + 'N', 'e', 'w', ' ', + 'C', 'u', 't', ' ', + 'P', 'a', 's', 't', 'e', ' ', + 'S', 'n', 'a', 'r', 'f', ' ', + 'S', 'o', 'r', 't', ' ', + 'Z', 'e', 'r', 'o', 'x', ' ', + 'D', 'e', 'l', 'c', 'o', 'l', ' ', + 0 +}; + +void +colinit(Column *c, Rectangle r) +{ + Rectangle r1; + Text *t; + + draw(screen, r, display->white, nil, ZP); + c->r = r; + c->w = nil; + c->nw = 0; + t = &c->tag; + t->w = nil; + t->col = c; + r1 = r; + r1.max.y = r1.min.y + font->height; + textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols); + t->what = Columntag; + r1.min.y = r1.max.y; + r1.max.y += Border; + draw(screen, r1, display->black, nil, ZP); + textinsert(t, 0, Lheader, 38, TRUE); + textsetselect(t, t->file->b.nc, t->file->b.nc); + draw(screen, t->scrollr, colbutton, nil, colbutton->r.min); + c->safe = TRUE; +} + +Window* +coladd(Column *c, Window *w, Window *clone, int y) +{ + Rectangle r, r1; + Window *v; + int i, t; + + v = nil; + r = c->r; + r.min.y = c->tag.fr.r.max.y+Border; + if(y<r.min.y && c->nw>0){ /* steal half of last window by default */ + v = c->w[c->nw-1]; + y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2; + } + /* look for window we'll land on */ + for(i=0; i<c->nw; i++){ + v = c->w[i]; + if(y < v->r.max.y) + break; + } + if(c->nw > 0){ + if(i < c->nw) + i++; /* new window will go after v */ + /* + * if v's too small, grow it first. + */ + if(!c->safe || v->body.fr.maxlines<=3){ + colgrow(c, v, 1); + y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2; + } + r = v->r; + if(i == c->nw) + t = c->r.max.y; + else + t = c->w[i]->r.min.y-Border; + r.max.y = t; + draw(screen, r, textcols[BACK], nil, ZP); + r1 = r; + y = min(y, t-(v->tag.fr.font->height+v->body.fr.font->height+Border+1)); + r1.max.y = min(y, v->body.fr.r.min.y+v->body.fr.nlines*v->body.fr.font->height); + r1.min.y = winresize(v, r1, FALSE); + r1.max.y = r1.min.y+Border; + draw(screen, r1, display->black, nil, ZP); + r.min.y = r1.max.y; + } + if(w == nil){ + w = emalloc(sizeof(Window)); + w->col = c; + draw(screen, r, textcols[BACK], nil, ZP); + wininit(w, clone, r); + }else{ + w->col = c; + winresize(w, r, FALSE); + } + w->tag.col = c; + w->tag.row = c->row; + w->body.col = c; + w->body.row = c->row; + c->w = realloc(c->w, (c->nw+1)*sizeof(Window*)); + memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*)); + c->nw++; + c->w[i] = w; + savemouse(w); + /* near but not on the button */ + moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3))); + barttext = &w->body; + c->safe = TRUE; + return w; +} + +void +colclose(Column *c, Window *w, int dofree) +{ + Rectangle r; + int i; + + /* w is locked */ + if(!c->safe) + colgrow(c, w, 1); + for(i=0; i<c->nw; i++) + if(c->w[i] == w) + goto Found; + error("can't find window"); + Found: + r = w->r; + w->tag.col = nil; + w->body.col = nil; + w->col = nil; + restoremouse(w); + if(dofree){ + windelete(w); + winclose(w); + } + memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*)); + c->nw--; + c->w = realloc(c->w, c->nw*sizeof(Window*)); + if(c->nw == 0){ + draw(screen, r, display->white, nil, ZP); + return; + } + if(i == c->nw){ /* extend last window down */ + w = c->w[i-1]; + r.min.y = w->r.min.y; + r.max.y = c->r.max.y; + }else{ /* extend next window up */ + w = c->w[i]; + r.max.y = w->r.max.y; + } + draw(screen, r, textcols[BACK], nil, ZP); + if(c->safe) + winresize(w, r, FALSE); +} + +void +colcloseall(Column *c) +{ + int i; + Window *w; + + if(c == activecol) + activecol = nil; + textclose(&c->tag); + for(i=0; i<c->nw; i++){ + w = c->w[i]; + winclose(w); + } + c->nw = 0; + free(c->w); + free(c); + clearmouse(); +} + +void +colmousebut(Column *c) +{ + moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2)); +} + +void +colresize(Column *c, Rectangle r) +{ + int i; + Rectangle r1, r2; + Window *w; + + clearmouse(); + r1 = r; + r1.max.y = r1.min.y + c->tag.fr.font->height; + textresize(&c->tag, r1); + draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min); + r1.min.y = r1.max.y; + r1.max.y += Border; + draw(screen, r1, display->black, nil, ZP); + r1.max.y = r.max.y; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + w->maxlines = 0; + if(i == c->nw-1) + r1.max.y = r.max.y; + else + r1.max.y = r1.min.y+(Dy(w->r)+Border)*Dy(r)/Dy(c->r); + r2 = r1; + r2.max.y = r2.min.y+Border; + draw(screen, r2, display->black, nil, ZP); + r1.min.y = r2.max.y; + r1.min.y = winresize(w, r1, FALSE); + } + c->r = r; +} + +static +int +colcmp(const void *a, const void *b) +{ + Rune *r1, *r2; + int i, nr1, nr2; + + r1 = (*(Window**)a)->body.file->name; + nr1 = (*(Window**)a)->body.file->nname; + r2 = (*(Window**)b)->body.file->name; + nr2 = (*(Window**)b)->body.file->nname; + for(i=0; i<nr1 && i<nr2; i++){ + if(*r1 != *r2) + return *r1-*r2; + r1++; + r2++; + } + return nr1-nr2; +} + +void +colsort(Column *c) +{ + int i, y; + Rectangle r, r1, *rp; + Window **wp, *w; + + if(c->nw == 0) + return; + clearmouse(); + rp = emalloc(c->nw*sizeof(Rectangle)); + wp = emalloc(c->nw*sizeof(Window*)); + memmove(wp, c->w, c->nw*sizeof(Window*)); + qsort(wp, c->nw, sizeof(Window*), colcmp); + for(i=0; i<c->nw; i++) + rp[i] = wp[i]->r; + r = c->r; + r.min.y = c->tag.fr.r.max.y; + draw(screen, r, textcols[BACK], nil, ZP); + y = r.min.y; + for(i=0; i<c->nw; i++){ + w = wp[i]; + r.min.y = y; + if(i == c->nw-1) + r.max.y = c->r.max.y; + else + r.max.y = r.min.y+Dy(w->r)+Border; + r1 = r; + r1.max.y = r1.min.y+Border; + draw(screen, r1, display->black, nil, ZP); + r.min.y = r1.max.y; + y = winresize(w, r, FALSE); + } + free(rp); + free(c->w); + c->w = wp; +} + +void +colgrow(Column *c, Window *w, int but) +{ + Rectangle r, cr; + int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h; + Window *v; + + for(i=0; i<c->nw; i++) + if(c->w[i] == w) + goto Found; + error("can't find window"); + + Found: + cr = c->r; + if(but < 0){ /* make sure window fills its own space properly */ + r = w->r; + if(i==c->nw-1 || c->safe==FALSE) + r.max.y = cr.max.y; + else + r.max.y = c->w[i+1]->r.min.y; + winresize(w, r, FALSE); + return; + } + cr.min.y = c->w[0]->r.min.y; + if(but == 3){ /* full size */ + if(i != 0){ + v = c->w[0]; + c->w[0] = w; + c->w[i] = v; + } + draw(screen, cr, textcols[BACK], nil, ZP); + winresize(w, cr, FALSE); + for(i=1; i<c->nw; i++) + c->w[i]->body.fr.maxlines = 0; + c->safe = FALSE; + return; + } + /* store old #lines for each window */ + onl = w->body.fr.maxlines; + nl = emalloc(c->nw * sizeof(int)); + ny = emalloc(c->nw * sizeof(int)); + tot = 0; + for(j=0; j<c->nw; j++){ + l = c->w[j]->body.fr.maxlines; + nl[j] = l; + tot += l; + } + /* approximate new #lines for this window */ + if(but == 2){ /* as big as can be */ + memset(nl, 0, c->nw * sizeof(int)); + goto Pack; + } + nnl = min(onl + max(min(5, w->maxlines), onl/2), tot); + if(nnl < w->maxlines) + nnl = (w->maxlines+nnl)/2; + if(nnl == 0) + nnl = 2; + dnl = nnl - onl; + /* compute new #lines for each window */ + for(k=1; k<c->nw; k++){ + /* prune from later window */ + j = i+k; + if(j<c->nw && nl[j]){ + l = min(dnl, max(1, nl[j]/2)); + nl[j] -= l; + nl[i] += l; + dnl -= l; + } + /* prune from earlier window */ + j = i-k; + if(j>=0 && nl[j]){ + l = min(dnl, max(1, nl[j]/2)); + nl[j] -= l; + nl[i] += l; + dnl -= l; + } + } + Pack: + /* pack everyone above */ + y1 = cr.min.y; + for(j=0; j<i; j++){ + v = c->w[j]; + r = v->r; + r.min.y = y1; + r.max.y = y1+Dy(v->tag.all); + if(nl[j]) + r.max.y += 1 + nl[j]*v->body.fr.font->height; + if(!c->safe || !eqrect(v->r, r)){ + draw(screen, r, textcols[BACK], nil, ZP); + winresize(v, r, c->safe); + } + r.min.y = v->r.max.y; + r.max.y += Border; + draw(screen, r, display->black, nil, ZP); + y1 = r.max.y; + } + /* scan to see new size of everyone below */ + y2 = c->r.max.y; + for(j=c->nw-1; j>i; j--){ + v = c->w[j]; + r = v->r; + r.min.y = y2-Dy(v->tag.all); + if(nl[j]) + r.min.y -= 1 + nl[j]*v->body.fr.font->height; + r.min.y -= Border; + ny[j] = r.min.y; + y2 = r.min.y; + } + /* compute new size of window */ + r = w->r; + r.min.y = y1; + r.max.y = r.min.y+Dy(w->tag.all); + h = w->body.fr.font->height; + if(y2-r.max.y >= 1+h+Border){ + r.max.y += 1; + r.max.y += h*((y2-r.max.y)/h); + } + /* draw window */ + if(!c->safe || !eqrect(w->r, r)){ + draw(screen, r, textcols[BACK], nil, ZP); + winresize(w, r, c->safe); + } + if(i < c->nw-1){ + r.min.y = r.max.y; + r.max.y += Border; + draw(screen, r, display->black, nil, ZP); + for(j=i+1; j<c->nw; j++) + ny[j] -= (y2-r.max.y); + } + /* pack everyone below */ + y1 = r.max.y; + for(j=i+1; j<c->nw; j++){ + v = c->w[j]; + r = v->r; + r.min.y = y1; + r.max.y = y1+Dy(v->tag.all); + if(nl[j]) + r.max.y += 1 + nl[j]*v->body.fr.font->height; + if(!c->safe || !eqrect(v->r, r)){ + draw(screen, r, textcols[BACK], nil, ZP); + winresize(v, r, c->safe); + } + if(j < c->nw-1){ /* no border on last window */ + r.min.y = v->r.max.y; + r.max.y += Border; + draw(screen, r, display->black, nil, ZP); + } + y1 = r.max.y; + } + r = w->r; + r.min.y = y1; + r.max.y = c->r.max.y; + draw(screen, r, textcols[BACK], nil, ZP); + free(nl); + free(ny); + c->safe = TRUE; + winmousebut(w); +} + +void +coldragwin(Column *c, Window *w, int but) +{ + Rectangle r; + int i, b; + Point p, op; + Window *v; + Column *nc; + + clearmouse(); + setcursor(mousectl, &boxcursor); + b = mouse->buttons; + op = mouse->xy; + while(mouse->buttons == b) + readmouse(mousectl); + setcursor(mousectl, nil); + if(mouse->buttons){ + while(mouse->buttons) + readmouse(mousectl); + return; + } + + for(i=0; i<c->nw; i++) + if(c->w[i] == w) + goto Found; + error("can't find window"); + + Found: + p = mouse->xy; + if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){ + colgrow(c, w, but); + winmousebut(w); + return; + } + /* is it a flick to the right? */ + if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c) + p.x += Dx(w->r); /* yes: toss to next column */ + nc = rowwhichcol(c->row, p); + if(nc!=nil && nc!=c){ + colclose(c, w, FALSE); + coladd(nc, w, nil, p.y); + winmousebut(w); + return; + } + if(i==0 && c->nw==1) + return; /* can't do it */ + if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y) + || (i==0 && p.y>w->r.max.y)){ + /* shuffle */ + colclose(c, w, FALSE); + coladd(c, w, nil, p.y); + winmousebut(w); + return; + } + if(i == 0) + return; + v = c->w[i-1]; + if(p.y < v->tag.all.max.y) + p.y = v->tag.all.max.y; + if(p.y > w->r.max.y-Dy(w->tag.all)-Border) + p.y = w->r.max.y-Dy(w->tag.all)-Border; + r = v->r; + r.max.y = p.y; + if(r.max.y > v->body.fr.r.min.y){ + r.max.y -= (r.max.y-v->body.fr.r.min.y)%v->body.fr.font->height; + if(v->body.fr.r.min.y == v->body.fr.r.max.y) + r.max.y++; + } + if(!eqrect(v->r, r)){ + draw(screen, r, textcols[BACK], nil, ZP); + winresize(v, r, c->safe); + } + r.min.y = v->r.max.y; + r.max.y = r.min.y+Border; + draw(screen, r, display->black, nil, ZP); + r.min.y = r.max.y; + if(i == c->nw-1) + r.max.y = c->r.max.y; + else + r.max.y = c->w[i+1]->r.min.y-Border; + if(!eqrect(w->r, r)){ + draw(screen, r, textcols[BACK], nil, ZP); + winresize(w, r, c->safe); + } + c->safe = TRUE; + winmousebut(w); +} + +Text* +colwhich(Column *c, Point p) +{ + int i; + Window *w; + + if(!ptinrect(p, c->r)) + return nil; + if(ptinrect(p, c->tag.all)) + return &c->tag; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + if(ptinrect(p, w->r)){ + if(ptinrect(p, w->tag.all)) + return &w->tag; + return &w->body; + } + } + return nil; +} + +int +colclean(Column *c) +{ + int i, clean; + + clean = TRUE; + for(i=0; i<c->nw; i++) + clean &= winclean(c->w[i], TRUE); + return clean; +} diff --git a/src/cmd/acme/dat.h b/src/cmd/acme/dat.h new file mode 100644 index 00000000..b2a443da --- /dev/null +++ b/src/cmd/acme/dat.h @@ -0,0 +1,546 @@ +enum +{ + Qdir, + Qacme, + Qcons, + Qconsctl, + Qdraw, + Qeditout, + Qindex, + Qlabel, + Qnew, + + QWaddr, + QWbody, + QWctl, + QWdata, + QWeditout, + QWevent, + QWrdsel, + QWwrsel, + QWtag, + QMAX, +}; + +enum +{ + Blockincr = 256, + Maxblock = 8*1024, + NRange = 10, + Infinity = 0x7FFFFFFF, /* huge value for regexp address */ +}; + +typedef struct Block Block; +typedef struct Buffer Buffer; +typedef struct Command Command; +typedef struct Column Column; +typedef struct Dirlist Dirlist; +typedef struct Dirtab Dirtab; +typedef struct Disk Disk; +typedef struct Expand Expand; +typedef struct Fid Fid; +typedef struct File File; +typedef struct Elog Elog; +typedef struct Mntdir Mntdir; +typedef struct Range Range; +typedef struct Rangeset Rangeset; +typedef struct Reffont Reffont; +typedef struct Row Row; +typedef struct Runestr Runestr; +typedef struct Text Text; +typedef struct Timer Timer; +typedef struct Window Window; +typedef struct Xfid Xfid; + +struct Runestr +{ + Rune *r; + int nr; +}; + +struct Range +{ + int q0; + int q1; +}; + +struct Block +{ + uint addr; /* disk address in bytes */ + union + { + uint n; /* number of used runes in block */ + Block *next; /* pointer to next in free list */ + } u; +}; + +struct Disk +{ + int fd; + uint addr; /* length of temp file */ + Block *free[Maxblock/Blockincr+1]; +}; + +Disk* diskinit(void); +Block* disknewblock(Disk*, uint); +void diskrelease(Disk*, Block*); +void diskread(Disk*, Block*, Rune*, uint); +void diskwrite(Disk*, Block**, Rune*, uint); + +struct Buffer +{ + uint nc; + Rune *c; /* cache */ + uint cnc; /* bytes in cache */ + uint cmax; /* size of allocated cache */ + uint cq; /* position of cache */ + int cdirty; /* cache needs to be written */ + uint cbi; /* index of cache Block */ + Block **bl; /* array of blocks */ + uint nbl; /* number of blocks */ +}; +void bufinsert(Buffer*, uint, Rune*, uint); +void bufdelete(Buffer*, uint, uint); +uint bufload(Buffer*, uint, int, int*); +void bufread(Buffer*, uint, Rune*, uint); +void bufclose(Buffer*); +void bufreset(Buffer*); + +struct Elog +{ + short type; /* Delete, Insert, Filename */ + uint q0; /* location of change (unused in f) */ + uint nd; /* number of deleted characters */ + uint nr; /* # runes in string or file name */ + Rune *r; +}; +void elogterm(File*); +void elogclose(File*); +void eloginsert(File*, int, Rune*, int); +void elogdelete(File*, int, int); +void elogreplace(File*, int, int, Rune*, int); +void elogapply(File*); + +struct File +{ + Buffer b; /* the data */ + Buffer delta; /* transcript of changes */ + Buffer epsilon; /* inversion of delta for redo */ + Buffer *elogbuf; /* log of pending editor changes */ + Elog elog; /* current pending change */ + Rune *name; /* name of associated file */ + int nname; /* size of name */ + uvlong qidpath; /* of file when read */ + uint mtime; /* of file when read */ + int dev; /* of file when read */ + int unread; /* file has not been read from disk */ + int editclean; /* mark clean after edit command */ + + int seq; /* if seq==0, File acts like Buffer */ + int mod; + Text *curtext; /* most recently used associated text */ + Text **text; /* list of associated texts */ + int ntext; + int dumpid; /* used in dumping zeroxed windows */ +}; +File* fileaddtext(File*, Text*); +void fileclose(File*); +void filedelete(File*, uint, uint); +void filedeltext(File*, Text*); +void fileinsert(File*, uint, Rune*, uint); +uint fileload(File*, uint, int, int*); +void filemark(File*); +void filereset(File*); +void filesetname(File*, Rune*, int); +void fileundelete(File*, Buffer*, uint, uint); +void fileuninsert(File*, Buffer*, uint, uint); +void fileunsetname(File*, Buffer*); +void fileundo(File*, int, uint*, uint*); +uint fileredoseq(File*); + +enum /* Text.what */ +{ + Columntag, + Rowtag, + Tag, + Body, +}; + +struct Text +{ + File *file; + Frame fr; + Reffont *reffont; + uint org; + uint q0; + uint q1; + int what; + int tabstop; + Window *w; + Rectangle scrollr; + Rectangle lastsr; + Rectangle all; + Row *row; + Column *col; + + uint eq0; /* start of typing for ESC */ + uint cq0; /* cache position */ + int ncache; /* storage for insert */ + int ncachealloc; + Rune *cache; + int nofill; +}; + +uint textbacknl(Text*, uint, uint); +uint textbsinsert(Text*, uint, Rune*, uint, int, int*); +int textbswidth(Text*, Rune); +int textclickmatch(Text*, int, int, int, uint*); +void textclose(Text*); +void textcolumnate(Text*, Dirlist**, int); +void textcommit(Text*, int); +void textconstrain(Text*, uint, uint, uint*, uint*); +void textdelete(Text*, uint, uint, int); +void textdoubleclick(Text*, uint*, uint*); +void textfill(Text*); +void textframescroll(Text*, int); +void textinit(Text*, File*, Rectangle, Reffont*, Image**); +void textinsert(Text*, uint, Rune*, uint, int); +uint textload(Text*, uint, char*, int); +Rune textreadc(Text*, uint); +void textredraw(Text*, Rectangle, Font*, Image*, int); +void textreset(Text*); +int textresize(Text*, Rectangle); +void textscrdraw(Text*); +void textscroll(Text*, int); +void textselect(Text*); +int textselect2(Text*, uint*, uint*, Text**); +int textselect23(Text*, uint*, uint*, Image*, int); +int textselect3(Text*, uint*, uint*); +void textsetorigin(Text*, uint, int); +void textsetselect(Text*, uint, uint); +void textshow(Text*, uint, uint, int); +void texttype(Text*, Rune); + +struct Window +{ + QLock lk; + Ref ref; + Text tag; + Text body; + Rectangle r; + uchar isdir; + uchar isscratch; + uchar filemenu; + uchar dirty; + int id; + Range addr; + Range limit; + uchar nopen[QMAX]; + uchar nomark; + uchar noscroll; + Range wrselrange; + int rdselfd; + int neditwrsel; + Column *col; + Xfid *eventx; + char *events; + int nevents; + int owner; + int maxlines; + Dirlist **dlp; + int ndl; + int putseq; + int nincl; + Rune **incl; + Reffont *reffont; + QLock ctllock; + uint ctlfid; + char *dumpstr; + char *dumpdir; + int dumpid; + int utflastqid; + int utflastboff; + int utflastq; +}; + +void wininit(Window*, Window*, Rectangle); +void winlock(Window*, int); +void winlock1(Window*, int); +void winunlock(Window*); +void wintype(Window*, Text*, Rune); +void winundo(Window*, int); +void winsetname(Window*, Rune*, int); +void winsettag(Window*); +void winsettag1(Window*); +void wincommit(Window*, Text*); +int winresize(Window*, Rectangle, int); +void winclose(Window*); +void windelete(Window*); +int winclean(Window*, int); +void windirfree(Window*); +void winevent(Window*, char*, ...); +void winmousebut(Window*); +void winaddincl(Window*, Rune*, int); +void wincleartag(Window*); +void winctlprint(Window*, char*, int); + +struct Column +{ + Rectangle r; + Text tag; + Row *row; + Window **w; + int nw; + int safe; +}; + +void colinit(Column*, Rectangle); +Window* coladd(Column*, Window*, Window*, int); +void colclose(Column*, Window*, int); +void colcloseall(Column*); +void colresize(Column*, Rectangle); +Text* colwhich(Column*, Point); +void coldragwin(Column*, Window*, int); +void colgrow(Column*, Window*, int); +int colclean(Column*); +void colsort(Column*); +void colmousebut(Column*); + +struct Row +{ + QLock lk; + Rectangle r; + Text tag; + Column **col; + int ncol; + +}; + +void rowinit(Row*, Rectangle); +Column* rowadd(Row*, Column *c, int); +void rowclose(Row*, Column*, int); +Text* rowwhich(Row*, Point); +Column* rowwhichcol(Row*, Point); +void rowresize(Row*, Rectangle); +Text* rowtype(Row*, Rune, Point); +void rowdragcol(Row*, Column*, int but); +int rowclean(Row*); +void rowdump(Row*, char*); +void rowload(Row*, char*, int); +void rowloadfonts(char*); + +struct Timer +{ + int dt; + int cancel; + Channel *c; /* chan(int) */ + Timer *next; +}; + +struct Command +{ + int pid; + Rune *name; + int nname; + char *text; + char **av; + int iseditcmd; + Mntdir *md; + Command *next; +}; + +struct Dirtab +{ + char *name; + uchar type; + uint qid; + uint perm; +}; + +struct Mntdir +{ + int id; + int ref; + Rune *dir; + int ndir; + Mntdir *next; + int nincl; + Rune **incl; +}; + +struct Fid +{ + int fid; + int busy; + int open; + Qid qid; + Window *w; + Dirtab *dir; + Fid *next; + Mntdir *mntdir; + int nrpart; + uchar rpart[UTFmax]; +}; + + +struct Xfid +{ + void *arg; /* args to xfidinit */ + Fcall fcall; + Xfid *next; + Channel *c; /* chan(void(*)(Xfid*)) */ + Fid *f; + uchar *buf; + int flushed; + +}; + +void xfidctl(void *); +void xfidflush(Xfid*); +void xfidopen(Xfid*); +void xfidclose(Xfid*); +void xfidread(Xfid*); +void xfidwrite(Xfid*); +void xfidctlwrite(Xfid*, Window*); +void xfideventread(Xfid*, Window*); +void xfideventwrite(Xfid*, Window*); +void xfidindexread(Xfid*); +void xfidutfread(Xfid*, Text*, uint, int); +int xfidruneread(Xfid*, Text*, uint, uint); + +struct Reffont +{ + Ref ref; + Font *f; + +}; +Reffont *rfget(int, int, int, char*); +void rfclose(Reffont*); + +struct Rangeset +{ + Range r[NRange]; +}; + +struct Dirlist +{ + Rune *r; + int nr; + int wid; +}; + +struct Expand +{ + uint q0; + uint q1; + Rune *name; + int nname; + char *bname; + int jump; + union{ + Text *at; + Rune *ar; + } u; + int (*agetc)(void*, uint); + int a0; + int a1; +}; + +enum +{ + /* fbufalloc() guarantees room off end of BUFSIZE */ + BUFSIZE = Maxblock+IOHDRSZ, /* size from fbufalloc() */ + RBUFSIZE = BUFSIZE/sizeof(Rune), + EVENTSIZE = 256, + Scrollwid = 12, /* width of scroll bar */ + Scrollgap = 4, /* gap right of scroll bar */ + Margin = 4, /* margin around text */ + Border = 2, /* line between rows, cols, windows */ +}; + +#define QID(w,q) ((w<<8)|(q)) +#define WIN(q) ((((ulong)(q).path)>>8) & 0xFFFFFF) +#define FILE(q) ((q).path & 0xFF) + +enum +{ + FALSE, + TRUE, + XXX, +}; + +enum +{ + Empty = 0, + Null = '-', + Delete = 'd', + Insert = 'i', + Replace = 'r', + Filename = 'f', +}; + +enum /* editing */ +{ + Inactive = 0, + Inserting, + Collecting, +}; + +uint seq; +uint maxtab; /* size of a tab, in units of the '0' character */ + +Display *display; +Image *screen; +Font *font; +Mouse *mouse; +Mousectl *mousectl; +Keyboardctl *keyboardctl; +Reffont reffont; +Image *modbutton; +Image *colbutton; +Image *button; +Image *but2col; +Image *but3col; +Cursor boxcursor; +Row row; +int timerpid; +Disk *disk; +Text *seltext; +Text *argtext; +Text *mousetext; /* global because Text.close needs to clear it */ +Text *typetext; /* global because Text.close needs to clear it */ +Text *barttext; /* shared between mousetask and keyboardthread */ +int bartflag; +Window *activewin; +Column *activecol; +Buffer snarfbuf; +Rectangle nullrect; +int fsyspid; +char *cputype; +char *objtype; +char *home; +char *fontnames[2]; +Image *tagcols[NCOL]; +Image *textcols[NCOL]; +int plumbsendfd; +int plumbeditfd; +extern char wdir[]; +int editing; +int erroutfd; +int messagesize; /* negotiated in 9P version setup */ + +Channel *ckeyboard; /* chan(Rune)[10] */ +Channel *cplumb; /* chan(Plumbmsg*) */ +Channel *cwait; /* chan(Waitmsg) */ +Channel *ccommand; /* chan(Command*) */ +Channel *ckill; /* chan(Rune*) */ +Channel *cxfidalloc; /* chan(Xfid*) */ +Channel *cxfidfree; /* chan(Xfid*) */ +Channel *cnewwindow; /* chan(Channel*) */ +Channel *mouseexit0; /* chan(int) */ +Channel *mouseexit1; /* chan(int) */ +Channel *cexit; /* chan(int) */ +Channel *cerr; /* chan(char*) */ +Channel *cedit; /* chan(int) */ + +#define STACK 32768 diff --git a/src/cmd/acme/disk.c b/src/cmd/acme/disk.c new file mode 100644 index 00000000..857d9329 --- /dev/null +++ b/src/cmd/acme/disk.c @@ -0,0 +1,129 @@ +#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 "fns.h" + +static Block *blist; + +int +tempfile(void) +{ + char buf[128]; + int i, fd; + + snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), getuser()); + for(i='A'; i<='Z'; i++){ + buf[5] = i; + if(access(buf, AEXIST) == 0) + continue; + fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600); + if(fd >= 0) + return fd; + } + return -1; +} + +Disk* +diskinit() +{ + Disk *d; + + d = emalloc(sizeof(Disk)); + d->fd = tempfile(); + if(d->fd < 0){ + fprint(2, "acme: can't create temp file: %r\n"); + threadexitsall("diskinit"); + } + return d; +} + +static +uint +ntosize(uint n, uint *ip) +{ + uint size; + + if(n > Maxblock) + error("internal error: ntosize"); + size = n; + if(size & (Blockincr-1)) + size += Blockincr - (size & (Blockincr-1)); + /* last bucket holds blocks of exactly Maxblock */ + if(ip) + *ip = size/Blockincr; + return size * sizeof(Rune); +} + +Block* +disknewblock(Disk *d, uint n) +{ + uint i, j, size; + Block *b; + + size = ntosize(n, &i); + b = d->free[i]; + if(b) + d->free[i] = b->u.next; + else{ + /* allocate in chunks to reduce malloc overhead */ + if(blist == nil){ + blist = emalloc(100*sizeof(Block)); + for(j=0; j<100-1; j++) + blist[j].u.next = &blist[j+1]; + } + b = blist; + blist = b->u.next; + b->addr = d->addr; + d->addr += size; + } + b->u.n = n; + return b; +} + +void +diskrelease(Disk *d, Block *b) +{ + uint i; + + ntosize(b->u.n, &i); + b->u.next = d->free[i]; + d->free[i] = b; +} + +void +diskwrite(Disk *d, Block **bp, Rune *r, uint n) +{ + int size, nsize; + Block *b; + + b = *bp; + size = ntosize(b->u.n, nil); + nsize = ntosize(n, nil); + if(size != nsize){ + diskrelease(d, b); + b = disknewblock(d, n); + *bp = b; + } + if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune)) + error("write error to temp file"); + b->u.n = n; +} + +void +diskread(Disk *d, Block *b, Rune *r, uint n) +{ + if(n > b->u.n) + error("internal error: diskread"); + + ntosize(b->u.n, nil); + if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune)) + error("read error from temp file"); +} 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; +} diff --git a/src/cmd/acme/edit.c b/src/cmd/acme/edit.c new file mode 100644 index 00000000..e052430e --- /dev/null +++ b/src/cmd/acme/edit.c @@ -0,0 +1,682 @@ +#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" + +static char linex[]="\n"; +static char wordx[]=" \t\n"; +struct cmdtab cmdtab[]={ +/* cmdc text regexp addr defcmd defaddr count token fn */ + '\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd, + 'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd, + 'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd, + 'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd, + 'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd, + 'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd, + 'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd, + 'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, + 'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd, + 'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd, + 'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd, + 'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd, + 's', 0, 1, 0, 0, aDot, 1, 0, s_cmd, + 't', 0, 0, 1, 0, aDot, 0, 0, m_cmd, + 'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd, + 'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, + 'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd, + 'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, + 'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, + '=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd, + 'B', 0, 0, 0, 0, aNo, 0, linex, B_cmd, + 'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd, + 'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, + 'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, + '<', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, + '|', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, + '>', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, +/* deliberately unimplemented: + 'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd, + 'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd, + 'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd, + '!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd, + */ + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +Cmd *parsecmd(int); +Addr *compoundaddr(void); +Addr *simpleaddr(void); +void freecmd(void); +void okdelim(int); + +Rune *cmdstartp; +Rune *cmdendp; +Rune *cmdp; +Channel *editerrc; + +String *lastpat; +int patset; + +List cmdlist; +List addrlist; +List stringlist; +Text *curtext; +int editing = Inactive; + +String* newstring(int); + +void +editthread(void *v) +{ + Cmd *cmdp; + + USED(v); + threadsetname("editthread"); + while((cmdp=parsecmd(0)) != 0){ +// ocurfile = curfile; +// loaded = curfile && !curfile->unread; + if(cmdexec(curtext, cmdp) == 0) + break; + freecmd(); + } + sendp(editerrc, nil); +} + +void +allelogterm(Window *w, void *x) +{ + USED(x); + elogterm(w->body.file); +} + +void +alleditinit(Window *w, void *x) +{ + USED(x); + textcommit(&w->tag, TRUE); + textcommit(&w->body, TRUE); + w->body.file->editclean = FALSE; +} + +void +allupdate(Window *w, void *x) +{ + Text *t; + int i; + File *f; + + USED(x); + t = &w->body; + f = t->file; + if(f->curtext != t) /* do curtext only */ + return; + if(f->elog.type == Null) + elogterm(f); + else if(f->elog.type != Empty){ + elogapply(f); + if(f->editclean){ + f->mod = FALSE; + for(i=0; i<f->ntext; i++) + f->text[i]->w->dirty = FALSE; + } + } + textsetselect(t, t->q0, t->q1); + textscrdraw(t); + winsettag(w); +} + +void +editerror(char *fmt, ...) +{ + va_list arg; + char *s; + + va_start(arg, fmt); + s = vsmprint(fmt, arg); + va_end(arg); + freecmd(); + allwindows(allelogterm, nil); /* truncate the edit logs */ + sendp(editerrc, s); + threadexits(nil); +} + +void +editcmd(Text *ct, Rune *r, uint n) +{ + char *err; + + if(n == 0) + return; + if(2*n > RBUFSIZE){ + warning(nil, "string too long\n"); + return; + } + + allwindows(alleditinit, nil); + if(cmdstartp) + free(cmdstartp); + cmdstartp = runemalloc(n+2); + runemove(cmdstartp, r, n); + if(r[n] != '\n') + cmdstartp[n++] = '\n'; + cmdstartp[n] = '\0'; + cmdendp = cmdstartp+n; + cmdp = cmdstartp; + if(ct->w == nil) + curtext = nil; + else + curtext = &ct->w->body; + resetxec(); + if(editerrc == nil){ + editerrc = chancreate(sizeof(char*), 0); + lastpat = allocstring(0); + } + threadcreate(editthread, nil, STACK); + err = recvp(editerrc); + editing = Inactive; + if(err != nil){ + if(err[0] != '\0') + warning(nil, "Edit: %s\n", err); + free(err); + } + + /* update everyone whose edit log has data */ + allwindows(allupdate, nil); +} + +int +getch(void) +{ + if(*cmdp == *cmdendp) + return -1; + return *cmdp++; +} + +int +nextc(void) +{ + if(*cmdp == *cmdendp) + return -1; + return *cmdp; +} + +void +ungetch(void) +{ + if(--cmdp < cmdstartp) + error("ungetch"); +} + +long +getnum(int signok) +{ + long n; + int c, sign; + + n = 0; + sign = 1; + if(signok>1 && nextc()=='-'){ + sign = -1; + getch(); + } + if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */ + return sign; + while('0'<=(c=getch()) && c<='9') + n = n*10 + (c-'0'); + ungetch(); + return sign*n; +} + +int +cmdskipbl(void) +{ + int c; + do + c = getch(); + while(c==' ' || c=='\t'); + if(c >= 0) + ungetch(); + return c; +} + +/* + * Check that list has room for one more element. + */ +void +growlist(List *l) +{ + if(l->u.listptr==0 || l->nalloc==0){ + l->nalloc = INCR; + l->u.listptr = emalloc(INCR*sizeof(long)); + l->nused = 0; + }else if(l->nused == l->nalloc){ + l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(long)); + memset((void*)(l->u.longptr+l->nalloc), 0, INCR*sizeof(long)); + l->nalloc += INCR; + } +} + +/* + * Remove the ith element from the list + */ +void +dellist(List *l, int i) +{ + memmove(&l->u.longptr[i], &l->u.longptr[i+1], (l->nused-(i+1))*sizeof(long)); + l->nused--; +} + +/* + * Add a new element, whose position is i, to the list + */ +void +inslist(List *l, int i, long val) +{ + growlist(l); + memmove(&l->u.longptr[i+1], &l->u.longptr[i], (l->nused-i)*sizeof(long)); + l->u.longptr[i] = val; + l->nused++; +} + +void +listfree(List *l) +{ + free(l->u.listptr); + free(l); +} + +String* +allocstring(int n) +{ + String *s; + + s = emalloc(sizeof(String)); + s->n = n; + s->nalloc = n+10; + s->r = emalloc(s->nalloc*sizeof(Rune)); + s->r[n] = '\0'; + return s; +} + +void +freestring(String *s) +{ + free(s->r); + free(s); +} + +Cmd* +newcmd(void){ + Cmd *p; + + p = emalloc(sizeof(Cmd)); + inslist(&cmdlist, cmdlist.nused, (long)p); + return p; +} + +String* +newstring(int n) +{ + String *p; + + p = allocstring(n); + inslist(&stringlist, stringlist.nused, (long)p); + return p; +} + +Addr* +newaddr(void) +{ + Addr *p; + + p = emalloc(sizeof(Addr)); + inslist(&addrlist, addrlist.nused, (long)p); + return p; +} + +void +freecmd(void) +{ + int i; + + while(cmdlist.nused > 0) + free(cmdlist.u.ucharptr[--cmdlist.nused]); + while(addrlist.nused > 0) + free(addrlist.u.ucharptr[--addrlist.nused]); + while(stringlist.nused>0){ + i = --stringlist.nused; + freestring(stringlist.u.stringptr[i]); + } +} + +void +okdelim(int c) +{ + if(c=='\\' || ('a'<=c && c<='z') + || ('A'<=c && c<='Z') || ('0'<=c && c<='9')) + editerror("bad delimiter %c\n", c); +} + +void +atnl(void) +{ + int c; + + cmdskipbl(); + c = getch(); + if(c != '\n') + editerror("newline expected (saw %C)", c); +} + +void +Straddc(String *s, int c) +{ + if(s->n+1 >= s->nalloc){ + s->nalloc += 10; + s->r = erealloc(s->r, s->nalloc*sizeof(Rune)); + } + s->r[s->n++] = c; + s->r[s->n] = '\0'; +} + +void +getrhs(String *s, int delim, int cmd) +{ + int c; + + while((c = getch())>0 && c!=delim && c!='\n'){ + if(c == '\\'){ + if((c=getch()) <= 0) + error("bad right hand side"); + if(c == '\n'){ + ungetch(); + c='\\'; + }else if(c == 'n') + c='\n'; + else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */ + Straddc(s, '\\'); + } + Straddc(s, c); + } + ungetch(); /* let client read whether delimiter, '\n' or whatever */ +} + +String * +collecttoken(char *end) +{ + String *s = newstring(0); + int c; + + while((c=nextc())==' ' || c=='\t') + Straddc(s, getch()); /* blanks significant for getname() */ + while((c=getch())>0 && utfrune(end, c)==0) + Straddc(s, c); + if(c != '\n') + atnl(); + return s; +} + +String * +collecttext(void) +{ + String *s; + int begline, i, c, delim; + + s = newstring(0); + if(cmdskipbl()=='\n'){ + getch(); + i = 0; + do{ + begline = i; + while((c = getch())>0 && c!='\n') + i++, Straddc(s, c); + i++, Straddc(s, '\n'); + if(c < 0) + goto Return; + }while(s->r[begline]!='.' || s->r[begline+1]!='\n'); + s->r[s->n-2] = '\0'; + }else{ + okdelim(delim = getch()); + getrhs(s, delim, 'a'); + if(nextc()==delim) + getch(); + atnl(); + } + Return: + return s; +} + +int +cmdlookup(int c) +{ + int i; + + for(i=0; cmdtab[i].cmdc; i++) + if(cmdtab[i].cmdc == c) + return i; + return -1; +} + +Cmd* +parsecmd(int nest) +{ + int i, c; + struct cmdtab *ct; + Cmd *cp, *ncp; + Cmd cmd; + + cmd.next = cmd.u.cmd = 0; + cmd.re = 0; + cmd.flag = cmd.num = 0; + cmd.addr = compoundaddr(); + if(cmdskipbl() == -1) + return 0; + if((c=getch())==-1) + return 0; + cmd.cmdc = c; + if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */ + getch(); /* the 'd' */ + cmd.cmdc='c'|0x100; + } + i = cmdlookup(cmd.cmdc); + if(i >= 0){ + if(cmd.cmdc == '\n') + goto Return; /* let nl_cmd work it all out */ + ct = &cmdtab[i]; + if(ct->defaddr==aNo && cmd.addr) + editerror("command takes no address"); + if(ct->count) + cmd.num = getnum(ct->count); + if(ct->regexp){ + /* x without pattern -> .*\n, indicated by cmd.re==0 */ + /* X without pattern is all files */ + if((ct->cmdc!='x' && ct->cmdc!='X') || + ((c = nextc())!=' ' && c!='\t' && c!='\n')){ + cmdskipbl(); + if((c = getch())=='\n' || c<0) + editerror("no address"); + okdelim(c); + cmd.re = getregexp(c); + if(ct->cmdc == 's'){ + cmd.u.text = newstring(0); + getrhs(cmd.u.text, c, 's'); + if(nextc() == c){ + getch(); + if(nextc() == 'g') + cmd.flag = getch(); + } + + } + } + } + if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0) + editerror("bad address"); + if(ct->defcmd){ + if(cmdskipbl() == '\n'){ + getch(); + cmd.u.cmd = newcmd(); + cmd.u.cmd->cmdc = ct->defcmd; + }else if((cmd.u.cmd = parsecmd(nest))==0) + error("defcmd"); + }else if(ct->text) + cmd.u.text = collecttext(); + else if(ct->token) + cmd.u.text = collecttoken(ct->token); + else + atnl(); + }else + switch(cmd.cmdc){ + case '{': + cp = 0; + do{ + if(cmdskipbl()=='\n') + getch(); + ncp = parsecmd(nest+1); + if(cp) + cp->next = ncp; + else + cmd.u.cmd = ncp; + }while(cp = ncp); + break; + case '}': + atnl(); + if(nest==0) + editerror("right brace with no left brace"); + return 0; + default: + editerror("unknown command %c", cmd.cmdc); + } + Return: + cp = newcmd(); + *cp = cmd; + return cp; +} + +String* +getregexp(int delim) +{ + String *buf, *r; + int i, c; + + buf = allocstring(0); + for(i=0; ; i++){ + if((c = getch())=='\\'){ + if(nextc()==delim) + c = getch(); + else if(nextc()=='\\'){ + Straddc(buf, c); + c = getch(); + } + }else if(c==delim || c=='\n') + break; + if(i >= RBUFSIZE) + editerror("regular expression too long"); + Straddc(buf, c); + } + if(c!=delim && c) + ungetch(); + if(buf->n > 0){ + patset = TRUE; + freestring(lastpat); + lastpat = buf; + }else + freestring(buf); + if(lastpat->n == 0) + editerror("no regular expression defined"); + r = newstring(lastpat->n); + runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */ + return r; +} + +Addr * +simpleaddr(void) +{ + Addr addr; + Addr *ap, *nap; + + addr.next = 0; + addr.u.left = 0; + switch(cmdskipbl()){ + case '#': + addr.type = getch(); + addr.num = getnum(1); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + addr.num = getnum(1); + addr.type='l'; + break; + case '/': case '?': case '"': + addr.u.re = getregexp(addr.type = getch()); + break; + case '.': + case '$': + case '+': + case '-': + case '\'': + addr.type = getch(); + break; + default: + return 0; + } + if(addr.next = simpleaddr()) + switch(addr.next->type){ + case '.': + case '$': + case '\'': + if(addr.type!='"') + case '"': + editerror("bad address syntax"); + break; + case 'l': + case '#': + if(addr.type=='"') + break; + /* fall through */ + case '/': + case '?': + if(addr.type!='+' && addr.type!='-'){ + /* insert the missing '+' */ + nap = newaddr(); + nap->type='+'; + nap->next = addr.next; + addr.next = nap; + } + break; + case '+': + case '-': + break; + default: + error("simpleaddr"); + } + ap = newaddr(); + *ap = addr; + return ap; +} + +Addr * +compoundaddr(void) +{ + Addr addr; + Addr *ap, *next; + + addr.u.left = simpleaddr(); + if((addr.type = cmdskipbl())!=',' && addr.type!=';') + return addr.u.left; + getch(); + next = addr.next = compoundaddr(); + if(next && (next->type==',' || next->type==';') && next->u.left==0) + editerror("bad address syntax"); + ap = newaddr(); + *ap = addr; + return ap; +} diff --git a/src/cmd/acme/edit.h b/src/cmd/acme/edit.h new file mode 100644 index 00000000..efa0b02e --- /dev/null +++ b/src/cmd/acme/edit.h @@ -0,0 +1,101 @@ +/*#pragma varargck argpos editerror 1*/ + +typedef struct Addr Addr; +typedef struct Address Address; +typedef struct Cmd Cmd; +typedef struct List List; +typedef struct String String; + +struct String +{ + int n; /* excludes NUL */ + Rune *r; /* includes NUL */ + int nalloc; +}; + +struct Addr +{ + char type; /* # (char addr), l (line addr), / ? . $ + - , ; */ + union{ + String *re; + Addr *left; /* left side of , and ; */ + } u; + ulong num; + Addr *next; /* or right side of , and ; */ +}; + +struct Address +{ + Range r; + File *f; +}; + +struct Cmd +{ + Addr *addr; /* address (range of text) */ + String *re; /* regular expression for e.g. 'x' */ + union{ + Cmd *cmd; /* target of x, g, {, etc. */ + String *text; /* text of a, c, i; rhs of s */ + Addr *mtaddr; /* address for m, t */ + } u; + Cmd *next; /* pointer to next element in {} */ + short num; + ushort flag; /* whatever */ + ushort cmdc; /* command character; 'x' etc. */ +}; + +extern struct cmdtab{ + ushort cmdc; /* command character */ + uchar text; /* takes a textual argument? */ + uchar regexp; /* takes a regular expression? */ + uchar addr; /* takes an address (m or t)? */ + uchar defcmd; /* default command; 0==>none */ + uchar defaddr; /* default address */ + uchar count; /* takes a count e.g. s2/// */ + char *token; /* takes text terminated by one of these */ + int (*fn)(Text*, Cmd*); /* function to call with parse tree */ +}cmdtab[]; + +#define INCR 25 /* delta when growing list */ + +struct List /* code depends on a long being able to hold a pointer */ +{ + int nalloc; + int nused; + union{ + void *listptr; + Block *blkptr; + long *longptr; + uchar* *ucharptr; + String* *stringptr; + File* *fileptr; + } u; +}; + +enum Defaddr{ /* default addresses */ + aNo, + aDot, + aAll, +}; + +int nl_cmd(Text*, Cmd*), a_cmd(Text*, Cmd*), b_cmd(Text*, Cmd*); +int c_cmd(Text*, Cmd*), d_cmd(Text*, Cmd*); +int B_cmd(Text*, Cmd*), D_cmd(Text*, Cmd*), e_cmd(Text*, Cmd*); +int f_cmd(Text*, Cmd*), g_cmd(Text*, Cmd*), i_cmd(Text*, Cmd*); +int k_cmd(Text*, Cmd*), m_cmd(Text*, Cmd*), n_cmd(Text*, Cmd*); +int p_cmd(Text*, Cmd*); +int s_cmd(Text*, Cmd*), u_cmd(Text*, Cmd*), w_cmd(Text*, Cmd*); +int x_cmd(Text*, Cmd*), X_cmd(Text*, Cmd*), pipe_cmd(Text*, Cmd*); +int eq_cmd(Text*, Cmd*); + +String *allocstring(int); +void freestring(String*); +String *getregexp(int); +Addr *newaddr(void); +Address cmdaddress(Addr*, Address, int); +int cmdexec(Text*, Cmd*); +void editerror(char*, ...); +int cmdlookup(int); +void resetxec(void); +void Straddc(String*, int); diff --git a/src/cmd/acme/elog.c b/src/cmd/acme/elog.c new file mode 100644 index 00000000..e86af6ec --- /dev/null +++ b/src/cmd/acme/elog.c @@ -0,0 +1,350 @@ +#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 "fns.h" +#include "edit.h" + +static char Wsequence[] = "warning: changes out of sequence\n"; +static int warned = FALSE; + +/* + * Log of changes made by editing commands. Three reasons for this: + * 1) We want addresses in commands to apply to old file, not file-in-change. + * 2) It's difficult to track changes correctly as things move, e.g. ,x m$ + * 3) This gives an opportunity to optimize by merging adjacent changes. + * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a + * separate implementation. To do this well, we use Replace as well as + * Insert and Delete + */ + +typedef struct Buflog Buflog; +struct Buflog +{ + short type; /* Replace, Filename */ + uint q0; /* location of change (unused in f) */ + uint nd; /* # runes to delete */ + uint nr; /* # runes in string or file name */ +}; + +enum +{ + Buflogsize = sizeof(Buflog)/sizeof(Rune), +}; + +/* + * Minstring shouldn't be very big or we will do lots of I/O for small changes. + * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r. + */ +enum +{ + Minstring = 16, /* distance beneath which we merge changes */ + Maxstring = RBUFSIZE, /* maximum length of change we will merge into one */ +}; + +void +eloginit(File *f) +{ + if(f->elog.type != Empty) + return; + f->elog.type = Null; + if(f->elogbuf == nil) + f->elogbuf = emalloc(sizeof(Buffer)); + if(f->elog.r == nil) + f->elog.r = fbufalloc(); + bufreset(f->elogbuf); +} + +void +elogclose(File *f) +{ + if(f->elogbuf){ + bufclose(f->elogbuf); + free(f->elogbuf); + f->elogbuf = nil; + } +} + +void +elogreset(File *f) +{ + f->elog.type = Null; + f->elog.nd = 0; + f->elog.nr = 0; +} + +void +elogterm(File *f) +{ + elogreset(f); + if(f->elogbuf) + bufreset(f->elogbuf); + f->elog.type = Empty; + fbuffree(f->elog.r); + f->elog.r = nil; + warned = FALSE; +} + +void +elogflush(File *f) +{ + Buflog b; + + b.type = f->elog.type; + b.q0 = f->elog.q0; + b.nd = f->elog.nd; + b.nr = f->elog.nr; + switch(f->elog.type){ + default: + warning(nil, "unknown elog type 0x%ux\n", f->elog.type); + break; + case Null: + break; + case Insert: + case Replace: + if(f->elog.nr > 0) + bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr); + /* fall through */ + case Delete: + bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize); + break; + } + elogreset(f); +} + +void +elogreplace(File *f, int q0, int q1, Rune *r, int nr) +{ + uint gap; + + if(q0==q1 && nr==0) + return; + eloginit(f); + if(f->elog.type!=Null && q0<f->elog.q0){ + if(warned++ == 0) + warning(nil, Wsequence); + elogflush(f); + } + /* try to merge with previous */ + gap = q0 - (f->elog.q0+f->elog.nd); /* gap between previous and this */ + if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){ + if(gap < Minstring){ + if(gap > 0){ + bufread(&f->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap); + f->elog.nr += gap; + } + f->elog.nd += gap + q1-q0; + runemove(f->elog.r+f->elog.nr, r, nr); + f->elog.nr += nr; + return; + } + } + elogflush(f); + f->elog.type = Replace; + f->elog.q0 = q0; + f->elog.nd = q1-q0; + f->elog.nr = nr; + if(nr > RBUFSIZE) + editerror("internal error: replacement string too large(%d)", nr); + runemove(f->elog.r, r, nr); +} + +void +eloginsert(File *f, int q0, Rune *r, int nr) +{ + int n; + + if(nr == 0) + return; + eloginit(f); + if(f->elog.type!=Null && q0<f->elog.q0){ + if(warned++ == 0) + warning(nil, Wsequence); + elogflush(f); + } + /* try to merge with previous */ + if(f->elog.type==Insert && q0==f->elog.q0 && (q0+nr)-f->elog.q0<Maxstring){ + runemove(f->elog.r+f->elog.nr, r, nr); + f->elog.nr += nr; + return; + } + while(nr > 0){ + elogflush(f); + f->elog.type = Insert; + f->elog.q0 = q0; + n = nr; + if(n > RBUFSIZE) + n = RBUFSIZE; + f->elog.nr = n; + runemove(f->elog.r, r, n); + r += n; + nr -= n; + } +} + +void +elogdelete(File *f, int q0, int q1) +{ + if(q0 == q1) + return; + eloginit(f); + if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){ + if(warned++ == 0) + warning(nil, Wsequence); + elogflush(f); + } + /* try to merge with previous */ + if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){ + f->elog.nd += q1-q0; + return; + } + elogflush(f); + f->elog.type = Delete; + f->elog.q0 = q0; + f->elog.nd = q1-q0; +} + +#define tracelog 0 +void +elogapply(File *f) +{ + Buflog b; + Rune *buf; + uint i, n, up, mod; + uint q0, q1, tq0, tq1; + Buffer *log; + Text *t; + + elogflush(f); + log = f->elogbuf; + t = f->curtext; + + buf = fbufalloc(); + mod = FALSE; + + /* + * The edit commands have already updated the selection in t->q0, t->q1. + * (At least, they are supposed to have updated them. + * We still keep finding commands that don't do it right.) + * The textinsert and textdelete calls below will update it again, so save the + * current setting and restore it at the end. + */ + q0 = t->q0; + q1 = t->q1; + /* + * We constrain the addresses in here (with textconstrain()) because + * overlapping changes will generate bogus addresses. We will warn + * about changes out of sequence but proceed anyway; here we must + * keep things in range. + */ + + while(log->nc > 0){ + up = log->nc-Buflogsize; + bufread(log, up, (Rune*)&b, Buflogsize); + switch(b.type){ + default: + fprint(2, "elogapply: 0x%ux\n", b.type); + abort(); + break; + + case Replace: + if(tracelog) + warning(nil, "elog replace %d %d\n", + b.q0, b.q0+b.nd); + if(!mod){ + mod = TRUE; + filemark(f); + } + textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); + textdelete(t, tq0, tq1, TRUE); + up -= b.nr; + for(i=0; i<b.nr; i+=n){ + n = b.nr - i; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(log, up+i, buf, n); + textinsert(t, tq0+i, buf, n, TRUE); + } + break; + + case Delete: + if(tracelog) + warning(nil, "elog delete %d %d\n", + b.q0, b.q0+b.nd); + if(!mod){ + mod = TRUE; + filemark(f); + } + textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); + textdelete(t, tq0, tq1, TRUE); + break; + + case Insert: + if(tracelog) + warning(nil, "elog insert %d %d\n", + b.q0, b.q0+b.nr); + if(!mod){ + mod = TRUE; + filemark(f); + } + textconstrain(t, b.q0, b.q0, &tq0, &tq1); + up -= b.nr; + for(i=0; i<b.nr; i+=n){ + n = b.nr - i; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(log, up+i, buf, n); + textinsert(t, tq0+i, buf, n, TRUE); + } + break; + +/* case Filename: + f->seq = u.seq; + fileunsetname(f, epsilon); + f->mod = u.mod; + up -= u.n; + free(f->name); + if(u.n == 0) + f->name = nil; + else + f->name = runemalloc(u.n); + bufread(delta, up, f->name, u.n); + f->nname = u.n; + break; +*/ + } + bufdelete(log, up, log->nc); + } + fbuffree(buf); + if(warned){ + /* + * Changes were out of order, so the q0 and q1 + * computed while generating those changes are not + * to be trusted. + */ + q1 = min(q1, f->b.nc); + q0 = min(q0, q1); + } + elogterm(f); + + /* + * The q0 and q1 are supposed to be fine (see comment + * above, where we saved them), but bad addresses + * will cause bufload to crash, so double check. + */ + if(q0 > f->b.nc || q1 > f->b.nc || q0 > q1){ + warning(nil, "elogapply: can't happen %d %d %d\n", q0, q1, f->b.nc); + q1 = min(q1, f->b.nc); + q0 = min(q0, q1); + } + + t->q0 = q0; + t->q1 = q1; +} diff --git a/src/cmd/acme/exec.c b/src/cmd/acme/exec.c new file mode 100644 index 00000000..9c29dc1a --- /dev/null +++ b/src/cmd/acme/exec.c @@ -0,0 +1,1491 @@ +#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> +#define Fid FsFid +#include <fs.h> +#undef Fid +#include "dat.h" +#include "fns.h" + +Buffer snarfbuf; + +void del(Text*, Text*, Text*, int, int, Rune*, int); +void delcol(Text*, Text*, Text*, int, int, Rune*, int); +void dump(Text*, Text*, Text*, int, int, Rune*, int); +void edit(Text*, Text*, Text*, int, int, Rune*, int); +void xexit(Text*, Text*, Text*, int, int, Rune*, int); +void fontx(Text*, Text*, Text*, int, int, Rune*, int); +void get(Text*, Text*, Text*, int, int, Rune*, int); +void id(Text*, Text*, Text*, int, int, Rune*, int); +void incl(Text*, Text*, Text*, int, int, Rune*, int); +void xkill(Text*, Text*, Text*, int, int, Rune*, int); +void local(Text*, Text*, Text*, int, int, Rune*, int); +void look(Text*, Text*, Text*, int, int, Rune*, int); +void newcol(Text*, Text*, Text*, int, int, Rune*, int); +void paste(Text*, Text*, Text*, int, int, Rune*, int); +void put(Text*, Text*, Text*, int, int, Rune*, int); +void putall(Text*, Text*, Text*, int, int, Rune*, int); +void sendx(Text*, Text*, Text*, int, int, Rune*, int); +void sort(Text*, Text*, Text*, int, int, Rune*, int); +void tab(Text*, Text*, Text*, int, int, Rune*, int); +void zeroxx(Text*, Text*, Text*, int, int, Rune*, int); + +typedef struct Exectab Exectab; +struct Exectab +{ + Rune *name; + void (*fn)(Text*, Text*, Text*, int, int, Rune*, int); + int mark; + int flag1; + int flag2; +}; + +static Rune LCut[] = { 'C', 'u', 't', 0 }; +static Rune LDel[] = { 'D', 'e', 'l', 0 }; +static Rune LDelcol[] = { 'D', 'e', 'l', 'c', 'o', 'l', 0 }; +static Rune LDelete[] = { 'D', 'e', 'l', 'e', 't', 'e', 0 }; +static Rune LDump[] = { 'D', 'u', 'm', 'p', 0 }; +static Rune LEdit[] = { 'E', 'd', 'i', 't', 0 }; +static Rune LExit[] = { 'E', 'x', 'i', 't', 0 }; +static Rune LFont[] = { 'F', 'o', 'n', 't', 0 }; +static Rune LGet[] = { 'G', 'e', 't', 0 }; +static Rune LID[] = { 'I', 'D', 0 }; +static Rune LIncl[] = { 'I', 'n', 'c', 'l', 0 }; +static Rune LKill[] = { 'K', 'i', 'l', 'l', 0 }; +static Rune LLoad[] = { 'L', 'o', 'a', 'd', 0 }; +static Rune LLocal[] = { 'L', 'o', 'c', 'a', 'l', 0 }; +static Rune LLook[] = { 'L', 'o', 'o', 'k', 0 }; +static Rune LNew[] = { 'N', 'e', 'w', 0 }; +static Rune LNewcol[] = { 'N', 'e', 'w', 'c', 'o', 'l', 0 }; +static Rune LPaste[] = { 'P', 'a', 's', 't', 'e', 0 }; +static Rune LPut[] = { 'P', 'u', 't', 0 }; +static Rune LPutall[] = { 'P', 'u', 't', 'a', 'l', 'l', 0 }; +static Rune LRedo[] = { 'R', 'e', 'd', 'o', 0 }; +static Rune LSend[] = { 'S', 'e', 'n', 'd', 0 }; +static Rune LSnarf[] = { 'S', 'n', 'a', 'r', 'f', 0 }; +static Rune LSort[] = { 'S', 'o', 'r', 't', 0 }; +static Rune LTab[] = { 'T', 'a', 'b', 0 }; +static Rune LUndo[] = { 'U', 'n', 'd', 'o', 0 }; +static Rune LZerox[] = { 'Z', 'e', 'r', 'o', 'x', 0 }; + +Exectab exectab[] = { + { LCut, cut, TRUE, TRUE, TRUE }, + { LDel, del, FALSE, FALSE, XXX }, + { LDelcol, delcol, FALSE, XXX, XXX }, + { LDelete, del, FALSE, TRUE, XXX }, + { LDump, dump, FALSE, TRUE, XXX }, + { LEdit, edit, FALSE, XXX, XXX }, + { LExit, xexit, FALSE, XXX, XXX }, + { LFont, fontx, FALSE, XXX, XXX }, + { LGet, get, FALSE, TRUE, XXX }, + { LID, id, FALSE, XXX, XXX }, + { LIncl, incl, FALSE, XXX, XXX }, + { LKill, xkill, FALSE, XXX, XXX }, + { LLoad, dump, FALSE, FALSE, XXX }, + { LLocal, local, FALSE, XXX, XXX }, + { LLook, look, FALSE, XXX, XXX }, + { LNew, new, FALSE, XXX, XXX }, + { LNewcol, newcol, FALSE, XXX, XXX }, + { LPaste, paste, TRUE, TRUE, XXX }, + { LPut, put, FALSE, XXX, XXX }, + { LPutall, putall, FALSE, XXX, XXX }, + { LRedo, undo, FALSE, FALSE, XXX }, + { LSend, sendx, TRUE, XXX, XXX }, + { LSnarf, cut, FALSE, TRUE, FALSE }, + { LSort, sort, FALSE, XXX, XXX }, + { LTab, tab, FALSE, XXX, XXX }, + { LUndo, undo, FALSE, TRUE, XXX }, + { LZerox, zeroxx, FALSE, XXX, XXX }, + { nil, nil, 0, 0, 0 }, +}; + +Exectab* +lookup(Rune *r, int n) +{ + Exectab *e; + int nr; + + r = skipbl(r, n, &n); + if(n == 0) + return nil; + findbl(r, n, &nr); + nr = n-nr; + for(e=exectab; e->name; e++) + if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE) + return e; + return nil; +} + +int +isexecc(int c) +{ + if(isfilec(c)) + return 1; + return c=='<' || c=='|' || c=='>'; +} + +void +execute(Text *t, uint aq0, uint aq1, int external, Text *argt) +{ + uint q0, q1; + Rune *r, *s; + char *b, *a, *aa; + Exectab *e; + int c, n, f; + Runestr dir; + + q0 = aq0; + q1 = aq1; + if(q1 == q0){ /* expand to find word (actually file name) */ + /* if in selection, choose selection */ + if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ + q0 = t->q0; + q1 = t->q1; + }else{ + while(q1<t->file->b.nc && isexecc(c=textreadc(t, q1)) && c!=':') + q1++; + while(q0>0 && isexecc(c=textreadc(t, q0-1)) && c!=':') + q0--; + if(q1 == q0) + return; + } + } + r = runemalloc(q1-q0); + bufread(&t->file->b, q0, r, q1-q0); + e = lookup(r, q1-q0); + if(!external && t->w!=nil && t->w->nopen[QWevent]>0){ + f = 0; + if(e) + f |= 1; + if(q0!=aq0 || q1!=aq1){ + bufread(&t->file->b, aq0, r, aq1-aq0); + f |= 2; + } + aa = getbytearg(argt, TRUE, TRUE, &a); + if(a){ + if(strlen(a) > EVENTSIZE){ /* too big; too bad */ + free(aa); + free(a); + warning(nil, "`argument string too long\n"); + return; + } + f |= 8; + } + c = 'x'; + if(t->what == Body) + c = 'X'; + n = aq1-aq0; + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d %d %d %.*S\n", c, aq0, aq1, f, n, n, r); + else + winevent(t->w, "%c%d %d %d 0 \n", c, aq0, aq1, f, n); + if(q0!=aq0 || q1!=aq1){ + n = q1-q0; + bufread(&t->file->b, q0, r, n); + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q1, n, n, r); + else + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1, n); + } + if(a){ + winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(a), a); + if(aa) + winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(aa), aa); + else + winevent(t->w, "%c0 0 0 0 \n", c); + } + free(r); + free(aa); + free(a); + return; + } + if(e){ + if(e->mark && seltext!=nil) + if(seltext->what == Body){ + seq++; + filemark(seltext->w->body.file); + } + s = skipbl(r, q1-q0, &n); + s = findbl(s, n, &n); + s = skipbl(s, n, &n); + (*e->fn)(t, seltext, argt, e->flag1, e->flag2, s, n); + free(r); + return; + } + + b = runetobyte(r, q1-q0); + free(r); + dir = dirname(t, nil, 0); + if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */ + free(dir.r); + dir.r = nil; + dir.nr = 0; + } + aa = getbytearg(argt, TRUE, TRUE, &a); + if(t->w) + incref(&t->w->ref); + run(t->w, b, dir.r, dir.nr, TRUE, aa, a, FALSE); +} + +char* +printarg(Text *argt, uint q0, uint q1) +{ + char *buf; + + if(argt->what!=Body || argt->file->name==nil) + return nil; + buf = emalloc(argt->file->nname+32); + if(q0 == q1) + sprint(buf, "%.*S:#%d", argt->file->nname, argt->file->name, q0); + else + sprint(buf, "%.*S:#%d,#%d", argt->file->nname, argt->file->name, q0, q1); + return buf; +} + +char* +getarg(Text *argt, int doaddr, int dofile, Rune **rp, int *nrp) +{ + int n; + Expand e; + char *a; + + *rp = nil; + *nrp = 0; + if(argt == nil) + return nil; + a = nil; + textcommit(argt, TRUE); + if(expand(argt, argt->q0, argt->q1, &e)){ + free(e.bname); + if(e.nname && dofile){ + e.name = runerealloc(e.name, e.nname+1); + if(doaddr) + a = printarg(argt, e.q0, e.q1); + *rp = e.name; + *nrp = e.nname; + return a; + } + free(e.name); + }else{ + e.q0 = argt->q0; + e.q1 = argt->q1; + } + n = e.q1 - e.q0; + *rp = runemalloc(n+1); + bufread(&argt->file->b, e.q0, *rp, n); + if(doaddr) + a = printarg(argt, e.q0, e.q1); + *nrp = n; + return a; +} + +char* +getbytearg(Text *argt, int doaddr, int dofile, char **bp) +{ + Rune *r; + int n; + char *aa; + + *bp = nil; + aa = getarg(argt, doaddr, dofile, &r, &n); + if(r == nil) + return nil; + *bp = runetobyte(r, n); + free(r); + return aa; +} + +void +newcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5) +{ + Column *c; + + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + USED(_5); + + c = rowadd(et->row, nil, -1); + if(c) + winsettag(coladd(c, nil, nil, -1)); +} + +void +delcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5) +{ + int i; + Column *c; + Window *w; + + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + USED(_5); + + c = et->col; + if(c==nil || colclean(c)==0) + return; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + if(w->nopen[QWevent]+w->nopen[QWaddr]+w->nopen[QWdata] > 0){ + warning(nil, "can't delete column; %.*S is running an external command\n", w->body.file->nname, w->body.file->name); + return; + } + } + rowclose(et->col->row, et->col, TRUE); +} + +void +del(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4) +{ + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + + if(et->col==nil || et->w == nil) + return; + if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE)) + colclose(et->col, et->w, TRUE); +} + +void +sort(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5) +{ + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + USED(_5); + + if(et->col) + colsort(et->col); +} + +uint +seqof(Window *w, int isundo) +{ + /* if it's undo, see who changed with us */ + if(isundo) + return w->body.file->seq; + /* if it's redo, see who we'll be sync'ed up with */ + return fileredoseq(w->body.file); +} + +void +undo(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4) +{ + int i, j; + Column *c; + Window *w; + uint seq; + + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + + if(et==nil || et->w== nil) + return; + seq = seqof(et->w, flag1); + if(seq == 0){ + /* nothing to undo */ + return; + } + /* + * Undo the executing window first. Its display will update. other windows + * in the same file will not call show() and jump to a different location in the file. + * Simultaneous changes to other files will be chaotic, however. + */ + winundo(et->w, flag1); + for(i=0; i<row.ncol; i++){ + c = row.col[i]; + for(j=0; j<c->nw; j++){ + w = c->w[j]; + if(w == et->w) + continue; + if(seqof(w, flag1) == seq) + winundo(w, flag1); + } + } +} + +char* +getname(Text *t, Text *argt, Rune *arg, int narg, int isput) +{ + char *s; + Rune *r; + int i, n, promote; + Runestr dir; + + getarg(argt, FALSE, TRUE, &r, &n); + promote = FALSE; + if(r == nil) + promote = TRUE; + else if(isput){ + /* if are doing a Put, want to synthesize name even for non-existent file */ + /* best guess is that file name doesn't contain a slash */ + promote = TRUE; + for(i=0; i<n; i++) + if(r[i] == '/'){ + promote = FALSE; + break; + } + if(promote){ + t = argt; + arg = r; + narg = n; + } + } + if(promote){ + n = narg; + if(n <= 0){ + s = runetobyte(t->file->name, t->file->nname); + return s; + } + /* prefix with directory name if necessary */ + dir.r = nil; + dir.nr = 0; + if(n>0 && arg[0]!='/'){ + dir = dirname(t, nil, 0); + if(n==1 && dir.r[0]=='.'){ /* sigh */ + free(dir.r); + dir.r = nil; + dir.nr = 0; + } + } + if(dir.r){ + r = runemalloc(dir.nr+n+1); + runemove(r, dir.r, dir.nr); + free(dir.r); + runemove(r+dir.nr, arg, n); + n += dir.nr; + }else{ + r = runemalloc(n+1); + runemove(r, arg, n); + } + } + s = runetobyte(r, n); + free(r); + if(strlen(s) == 0){ + free(s); + s = nil; + } + return s; +} + +void +zeroxx(Text *et, Text *t, Text *_1, int _2, int _3, Rune *_4, int _5) +{ + Window *nw; + int c, locked; + + USED(_1); + USED(_2); + USED(_3); + USED(_4); + USED(_5); + + locked = FALSE; + if(t!=nil && t->w!=nil && t->w!=et->w){ + locked = TRUE; + c = 'M'; + if(et->w) + c = et->w->owner; + winlock(t->w, c); + } + if(t == nil) + t = et; + if(t==nil || t->w==nil) + return; + t = &t->w->body; + if(t->w->isdir) + warning(nil, "%.*S is a directory; Zerox illegal\n", t->file->nname, t->file->name); + else{ + nw = coladd(t->w->col, nil, t->w, -1); + /* ugly: fix locks so w->unlock works */ + winlock1(nw, t->w->owner); + } + if(locked) + winunlock(t->w); +} + +void +get(Text *et, Text *t, Text *argt, int flag1, int _0, Rune *arg, int narg) +{ + char *name; + Rune *r; + int i, n, dirty, samename, isdir; + Window *w; + Text *u; + Dir *d; + + USED(_0); + + if(flag1) + if(et==nil || et->w==nil) + return; + if(!et->w->isdir && (et->w->body.file->b.nc>0 && !winclean(et->w, TRUE))) + return; + w = et->w; + t = &w->body; + name = getname(t, argt, arg, narg, FALSE); + if(name == nil){ + warning(nil, "no file name\n"); + return; + } + if(t->file->ntext>1){ + d = dirstat(name); + isdir = (d!=nil && (d->qid.type & QTDIR)); + free(d); + if(isdir) + warning(nil, "%s is a directory; can't read with multiple windows on it\n", name); + return; + } + r = bytetorune(name, &n); + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + /* second and subsequent calls with zero an already empty buffer, but OK */ + textreset(u); + windirfree(u->w); + } + samename = runeeq(r, n, t->file->name, t->file->nname); + textload(t, 0, name, samename); + if(samename){ + t->file->mod = FALSE; + dirty = FALSE; + }else{ + t->file->mod = TRUE; + dirty = TRUE; + } + for(i=0; i<t->file->ntext; i++) + t->file->text[i]->w->dirty = dirty; + free(name); + free(r); + winsettag(w); + t->file->unread = FALSE; + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + textsetselect(&u->w->tag, u->w->tag.file->b.nc, u->w->tag.file->b.nc); + textscrdraw(u); + } +} + +void +putfile(File *f, int q0, int q1, Rune *namer, int nname) +{ + uint n, m; + Rune *r; + char *s, *name; + int i, fd, q; + Dir *d, *d1; + Window *w; + int isapp; + + w = f->curtext->w; + name = runetobyte(namer, nname); + d = dirstat(name); + if(d!=nil && runeeq(namer, nname, f->name, f->nname)){ + /* f->mtime+1 because when talking over NFS it's often off by a second */ + if(f->dev!=d->dev || f->qidpath!=d->qid.path || f->mtime+1<d->mtime){ + f->dev = d->dev; + f->qidpath = d->qid.path; + f->mtime = d->mtime; + if(f->unread) + warningew(w, nil, "%s not written; file already exists\n", name); + else + warningew(w, nil, "%s modified%s%s since last read\n", name, d->muid[0]?" by ":"", d->muid); + goto Rescue1; + } + } + fd = create(name, OWRITE, 0666); + if(fd < 0){ + warningew(w, nil, "can't create file %s: %r\n", name); + goto Rescue1; + } + r = fbufalloc(); + s = fbufalloc(); + free(d); + d = dirfstat(fd); + isapp = (d!=nil && d->length>0 && (d->qid.type&QTAPPEND)); + if(isapp){ + warningew(w, nil, "%s not written; file is append only\n", name); + goto Rescue2; + } + + for(q=q0; q<q1; q+=n){ + n = q1 - q; + if(n > BUFSIZE/UTFmax) + n = BUFSIZE/UTFmax; + bufread(&f->b, q, r, n); + m = snprint(s, BUFSIZE+1, "%.*S", n, r); + if(write(fd, s, m) != m){ + warningew(w, nil, "can't write file %s: %r\n", name); + goto Rescue2; + } + } + if(runeeq(namer, nname, f->name, f->nname)){ + if(q0!=0 || q1!=f->b.nc){ + f->mod = TRUE; + w->dirty = TRUE; + f->unread = TRUE; + }else{ + d1 = dirfstat(fd); + if(d1 != nil){ + free(d); + d = d1; + } + f->qidpath = d->qid.path; + f->dev = d->dev; + f->mtime = d->mtime; + f->mod = FALSE; + w->dirty = FALSE; + f->unread = FALSE; + } + for(i=0; i<f->ntext; i++){ + f->text[i]->w->putseq = f->seq; + f->text[i]->w->dirty = w->dirty; + } + } + fbuffree(s); + fbuffree(r); + free(d); + free(namer); + free(name); + close(fd); + winsettag(w); + return; + + Rescue2: + fbuffree(s); + fbuffree(r); + close(fd); + /* fall through */ + + Rescue1: + free(d); + free(namer); + free(name); +} + +void +put(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg) +{ + int nname; + Rune *namer; + Window *w; + File *f; + char *name; + + USED(_0); + USED(_1); + USED(_2); + + if(et==nil || et->w==nil || et->w->isdir) + return; + w = et->w; + f = w->body.file; + name = getname(&w->body, argt, arg, narg, TRUE); + if(name == nil){ + warningew(w, nil, "no file name\n"); + return; + } + namer = bytetorune(name, &nname); + putfile(f, 0, f->b.nc, namer, nname); + free(name); +} + +void +dump(Text *_0, Text *_1, Text *argt, int isdump, int _2, Rune *arg, int narg) +{ + char *name; + + USED(_0); + USED(_1); + USED(_2); + + if(narg) + name = runetobyte(arg, narg); + else + getbytearg(argt, FALSE, TRUE, &name); + if(isdump) + rowdump(&row, name); + else + rowload(&row, name, FALSE); + free(name); +} + +void +cut(Text *et, Text *t, Text *_0, int dosnarf, int docut, Rune *_2, int _3) +{ + uint q0, q1, n, locked, c; + Rune *r; + + USED(_0); + USED(_2); + USED(_3); + + /* use current window if snarfing and its selection is non-null */ + if(et!=t && dosnarf && et->w!=nil){ + if(et->w->body.q1>et->w->body.q0){ + t = &et->w->body; + if(docut) + filemark(t->file); /* seq has been incremented by execute */ + }else if(et->w->tag.q1>et->w->tag.q0) + t = &et->w->tag; + } + if(t == nil){ + /* can only happen if seltext == nil */ + return; + } + locked = FALSE; + if(t->w!=nil && et->w!=t->w){ + locked = TRUE; + c = 'M'; + if(et->w) + c = et->w->owner; + winlock(t->w, c); + } + if(t->q0 == t->q1){ + if(locked) + winunlock(t->w); + return; + } + if(dosnarf){ + q0 = t->q0; + q1 = t->q1; + bufdelete(&snarfbuf, 0, snarfbuf.nc); + r = fbufalloc(); + while(q0 < q1){ + n = q1 - q0; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(&t->file->b, q0, r, n); + bufinsert(&snarfbuf, snarfbuf.nc, r, n); + q0 += n; + } + fbuffree(r); + acmeputsnarf(); + } + if(docut){ + textdelete(t, t->q0, t->q1, TRUE); + textsetselect(t, t->q0, t->q0); + if(t->w){ + textscrdraw(t); + winsettag(t->w); + } + }else if(dosnarf) /* Snarf command */ + argtext = t; + if(locked) + winunlock(t->w); +} + +void +paste(Text *et, Text *t, Text *_0, int selectall, int tobody, Rune *_1, int _2) +{ + int c; + uint q, q0, q1, n; + Rune *r; + + USED(_0); + USED(_1); + USED(_2); + + /* if(tobody), use body of executing window (Paste or Send command) */ + if(tobody && et!=nil && et->w!=nil){ + t = &et->w->body; + filemark(t->file); /* seq has been incremented by execute */ + } + if(t == nil) + return; + + acmegetsnarf(); + if(t==nil || snarfbuf.nc==0) + return; + if(t->w!=nil && et->w!=t->w){ + c = 'M'; + if(et->w) + c = et->w->owner; + winlock(t->w, c); + } + cut(t, t, nil, FALSE, TRUE, nil, 0); + q = 0; + q0 = t->q0; + q1 = t->q0+snarfbuf.nc; + r = fbufalloc(); + while(q0 < q1){ + n = q1 - q0; + if(n > RBUFSIZE) + n = RBUFSIZE; + if(r == nil) + r = runemalloc(n); + bufread(&snarfbuf, q, r, n); + textinsert(t, q0, r, n, TRUE); + q += n; + q0 += n; + } + fbuffree(r); + if(selectall) + textsetselect(t, t->q0, q1); + else + textsetselect(t, q1, q1); + if(t->w){ + textscrdraw(t); + winsettag(t->w); + } + if(t->w!=nil && et->w!=t->w) + winunlock(t->w); +} + +void +look(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg) +{ + Rune *r; + int n; + + USED(_0); + USED(_1); + + if(et && et->w){ + t = &et->w->body; + if(narg > 0){ + search(t, arg, narg); + return; + } + getarg(argt, FALSE, FALSE, &r, &n); + if(r == nil){ + n = t->q1-t->q0; + r = runemalloc(n); + bufread(&t->file->b, t->q0, r, n); + } + search(t, r, n); + free(r); + } +} + +static Rune Lnl[] = { '\n', 0 }; + +void +sendx(Text *et, Text *t, Text *_0, int _1, int _2, Rune *_3, int _4) +{ + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + + if(et->w==nil) + return; + t = &et->w->body; + if(t->q0 != t->q1) + cut(t, t, nil, TRUE, FALSE, nil, 0); + textsetselect(t, t->file->b.nc, t->file->b.nc); + paste(t, t, nil, TRUE, TRUE, nil, 0); + if(textreadc(t, t->file->b.nc-1) != '\n'){ + textinsert(t, t->file->b.nc, Lnl, 1, TRUE); + textsetselect(t, t->file->b.nc, t->file->b.nc); + } +} + +void +edit(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg) +{ + Rune *r; + int len; + + USED(_0); + USED(_1); + USED(_2); + + if(et == nil) + return; + getarg(argt, FALSE, TRUE, &r, &len); + seq++; + if(r != nil){ + editcmd(et, r, len); + free(r); + }else + editcmd(et, arg, narg); +} + +void +xexit(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5) +{ + USED(et); + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + USED(_5); + + if(rowclean(&row)){ + sendul(cexit, 0); + threadexits(nil); + } +} + +void +putall(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5) +{ + int i, j, e; + Window *w; + Column *c; + char *a; + + USED(et); + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + USED(_5); + + for(i=0; i<row.ncol; i++){ + c = row.col[i]; + for(j=0; j<c->nw; j++){ + w = c->w[j]; + if(w->isscratch || w->isdir || w->body.file->nname==0) + continue; + if(w->nopen[QWevent] > 0) + continue; + a = runetobyte(w->body.file->name, w->body.file->nname); + e = access(a, 0); + if(w->body.file->mod || w->body.ncache) + if(e < 0) + warning(nil, "no auto-Put of %s: %r\n", a); + else{ + wincommit(w, &w->body); + put(&w->body, nil, nil, XXX, XXX, nil, 0); + } + free(a); + } + } +} + + +void +id(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5) +{ + USED(et); + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + USED(_5); + + if(et && et->w) + warning(nil, "/mnt/acme/%d/\n", et->w->id); +} + +void +local(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg) +{ + char *a, *aa; + Runestr dir; + + USED(_0); + USED(_1); + USED(_2); + + aa = getbytearg(argt, TRUE, TRUE, &a); + + dir = dirname(et, nil, 0); + if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */ + free(dir.r); + dir.r = nil; + dir.nr = 0; + } + run(nil, runetobyte(arg, narg), dir.r, dir.nr, FALSE, aa, a, FALSE); +} + +void +xkill(Text *_0, Text *_1, Text *argt, int _2, int _3, Rune *arg, int narg) +{ + Rune *a, *cmd, *r; + int na; + + USED(_0); + USED(_1); + USED(_2); + USED(_3); + + getarg(argt, FALSE, FALSE, &r, &na); + if(r) + xkill(nil, nil, nil, 0, 0, r, na); + /* loop condition: *arg is not a blank */ + for(;;){ + a = findbl(arg, narg, &na); + if(a == arg) + break; + cmd = runemalloc(narg-na+1); + runemove(cmd, arg, narg-na); + sendp(ckill, cmd); + arg = skipbl(a, na, &narg); + } +} + +static Rune Lfix[] = { 'f', 'i', 'x', 0 }; +static Rune Lvar[] = { 'v', 'a', 'r', 0 }; + +void +fontx(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg) +{ + Rune *a, *r, *flag, *file; + int na, nf; + char *aa; + Reffont *newfont; + Dirlist *dp; + int i, fix; + + USED(_0); + USED(_1); + + if(et==nil || et->w==nil) + return; + t = &et->w->body; + flag = nil; + file = nil; + /* loop condition: *arg is not a blank */ + nf = 0; + for(;;){ + a = findbl(arg, narg, &na); + if(a == arg) + break; + r = runemalloc(narg-na+1); + runemove(r, arg, narg-na); + if(runeeq(r, narg-na, Lfix, 3) || runeeq(r, narg-na, Lvar, 3)){ + free(flag); + flag = r; + }else{ + free(file); + file = r; + nf = narg-na; + } + arg = skipbl(a, na, &narg); + } + getarg(argt, FALSE, TRUE, &r, &na); + if(r) + if(runeeq(r, na, Lfix, 3) || runeeq(r, na, Lvar, 3)){ + free(flag); + flag = r; + }else{ + free(file); + file = r; + nf = na; + } + fix = 1; + if(flag) + fix = runeeq(flag, runestrlen(flag), Lfix, 3); + else if(file == nil){ + newfont = rfget(FALSE, FALSE, FALSE, nil); + if(newfont) + fix = strcmp(newfont->f->name, t->fr.font->name)==0; + } + if(file){ + aa = runetobyte(file, nf); + newfont = rfget(fix, flag!=nil, FALSE, aa); + free(aa); + }else + newfont = rfget(fix, FALSE, FALSE, nil); + if(newfont){ + draw(screen, t->w->r, textcols[BACK], nil, ZP); + rfclose(t->reffont); + t->reffont = newfont; + t->fr.font = newfont->f; + frinittick(&t->fr); + if(t->w->isdir){ + t->all.min.x++; /* force recolumnation; disgusting! */ + for(i=0; i<t->w->ndl; i++){ + dp = t->w->dlp[i]; + aa = runetobyte(dp->r, dp->nr); + dp->wid = stringwidth(newfont->f, aa); + free(aa); + } + } + /* avoid shrinking of window due to quantization */ + colgrow(t->w->col, t->w, -1); + } + free(file); + free(flag); +} + +void +incl(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg) +{ + Rune *a, *r; + Window *w; + int na, n, len; + + USED(_0); + USED(_1); + USED(_2); + + if(et==nil || et->w==nil) + return; + w = et->w; + n = 0; + getarg(argt, FALSE, TRUE, &r, &len); + if(r){ + n++; + winaddincl(w, r, len); + } + /* loop condition: *arg is not a blank */ + for(;;){ + a = findbl(arg, narg, &na); + if(a == arg) + break; + r = runemalloc(narg-na+1); + runemove(r, arg, narg-na); + n++; + winaddincl(w, r, narg-na); + arg = skipbl(a, na, &narg); + } + if(n==0 && w->nincl){ + for(n=w->nincl; --n>=0; ) + warning(nil, "%S ", w->incl[n]); + warning(nil, "\n"); + } +} + +void +tab(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg) +{ + Rune *a, *r; + Window *w; + int na, len, tab; + char *p; + + USED(_0); + USED(_1); + USED(_2); + + if(et==nil || et->w==nil) + return; + w = et->w; + getarg(argt, FALSE, TRUE, &r, &len); + tab = 0; + if(r!=nil && len>0){ + p = runetobyte(r, len); + if('0'<=p[0] && p[0]<='9') + tab = atoi(p); + free(p); + }else{ + a = findbl(arg, narg, &na); + if(a != arg){ + p = runetobyte(arg, narg-na); + if('0'<=p[0] && p[0]<='9') + tab = atoi(p); + free(p); + } + } + if(tab > 0){ + if(w->body.tabstop != tab){ + w->body.tabstop = tab; + winresize(w, w->r, 1); + } + }else + warning(nil, "%.*S: Tab %d\n", w->body.file->nname, w->body.file->name, w->body.tabstop); +} + +void +runproc(void *argvp) +{ + /* args: */ + Window *win; + char *s; + Rune *rdir; + int ndir; + int newns; + char *argaddr; + char *arg; + Command *c; + Channel *cpid; + int iseditcmd; + /* end of args */ + char *e, *t, *name, *filename, *dir, **av, *news; + Rune r, **incl; + int ac, w, inarg, i, n, fd, nincl, winid; + int sfd[3]; + int pipechar; + char buf[512]; + //static void *parg[2]; + void **argv; + Fsys *fs; + + argv = argvp; + win = argv[0]; + s = argv[1]; + rdir = argv[2]; + ndir = (int)argv[3]; + newns = (int)argv[4]; + argaddr = argv[5]; + arg = argv[6]; + c = argv[7]; + cpid = argv[8]; + iseditcmd = (int)argv[9]; + free(argv); + + t = s; + while(*t==' ' || *t=='\n' || *t=='\t') + t++; + for(e=t; *e; e++) + if(*e==' ' || *e=='\n' || *e=='\t' ) + break; + name = emalloc((e-t)+2); + memmove(name, t, e-t); + name[e-t] = 0; + e = utfrrune(name, '/'); + if(e) + strcpy(name, e+1); + strcat(name, " "); /* add blank here for ease in waittask */ + c->name = bytetorune(name, &c->nname); + free(name); + pipechar = 0; + if(*t=='<' || *t=='|' || *t=='>') + pipechar = *t++; + c->iseditcmd = iseditcmd; + c->text = s; + if(rdir != nil){ + dir = runetobyte(rdir, ndir); + chdir(dir); /* ignore error: probably app. window */ + free(dir); + } + if(newns){ + nincl = 0; + incl = nil; + if(win){ + filename = smprint("%.*S", win->body.file->nname, win->body.file->name); + nincl = win->nincl; + if(nincl > 0){ + incl = emalloc(nincl*sizeof(Rune*)); + for(i=0; i<nincl; i++){ + n = runestrlen(win->incl[i]); + incl[i] = runemalloc(n+1); + runemove(incl[i], win->incl[i], n); + } + } + winid = win->id; + }else{ + filename = nil; + winid = 0; + if(activewin) + winid = activewin->id; + } + rfork(RFNAMEG|RFENVG|RFFDG|RFNOTEG); + sprint(buf, "%d", winid); + putenv("winid", buf); + + if(filename){ + putenv("%", filename); + free(filename); + } + c->md = fsysmount(rdir, ndir, incl, nincl); + if(c->md == nil){ + fprint(2, "child: can't allocate mntdir: %r\n"); + threadexits("fsysmount"); + } + sprint(buf, "%d", c->md->id); + if((fs = nsmount("acme", buf)) == nil){ + fprint(2, "child: can't mount acme: %r\n"); + fsysdelid(c->md); + c->md = nil; + threadexits("nsmount"); + } + if(winid>0 && (pipechar=='|' || pipechar=='>')){ + sprint(buf, "%d/rdsel", winid); + sfd[0] = fsopenfd(fs, buf, OREAD); + }else + sfd[0] = open("/dev/null", OREAD); + if((winid>0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){ + if(iseditcmd){ + if(winid > 0) + sprint(buf, "%d/editout", winid); + else + sprint(buf, "editout"); + }else + sprint(buf, "%d/wrsel", winid); + sfd[1] = fsopenfd(fs, buf, OWRITE); + sfd[2] = fsopenfd(fs, "cons", OWRITE); + }else{ + sfd[1] = fsopenfd(fs, "cons", OWRITE); + sfd[2] = sfd[1]; + } + fsunmount(fs); + }else{ + rfork(RFFDG|RFNOTEG); + fsysclose(); + sfd[0] = open("/dev/null", OREAD); + sfd[1] = open("/dev/null", OWRITE); + sfd[2] = dup(erroutfd, -1); + } + if(win) + winclose(win); + + if(argaddr) + putenv("acmeaddr", argaddr); + if(strlen(t) > sizeof buf-10) /* may need to print into stack */ + goto Hard; + inarg = FALSE; + for(e=t; *e; e+=w){ + w = chartorune(&r, e); + if(r==' ' || r=='\t') + continue; + if(r < ' ') + goto Hard; + if(utfrune("#;&|^$=`'{}()<>[]*?^~`", r)) + goto Hard; + inarg = TRUE; + } + if(!inarg) + goto Fail; + + ac = 0; + av = nil; + inarg = FALSE; + for(e=t; *e; e+=w){ + w = chartorune(&r, e); + if(r==' ' || r=='\t'){ + inarg = FALSE; + *e = 0; + continue; + } + if(!inarg){ + inarg = TRUE; + av = realloc(av, (ac+1)*sizeof(char**)); + av[ac++] = e; + } + } + av = realloc(av, (ac+2)*sizeof(char**)); + av[ac++] = arg; + av[ac] = nil; + c->av = av; + procexec(cpid, sfd, av[0], av); +/* libthread uses execvp so no need to do this */ +#if 0 + e = av[0]; + if(e[0]=='/' || (e[0]=='.' && e[1]=='/')) + goto Fail; + if(cputype){ + sprint(buf, "%s/%s", cputype, av[0]); + procexec(cpid, sfd, buf, av); + } + sprint(buf, "/bin/%s", av[0]); + procexec(cpid, sfd, buf, av); +#endif + goto Fail; + +Hard: + + /* + * ugly: set path = (. $cputype /bin) + * should honor $path if unusual. + */ + if(cputype){ + n = 0; + memmove(buf+n, ".", 2); + n += 2; + i = strlen(cputype)+1; + memmove(buf+n, cputype, i); + n += i; + memmove(buf+n, "/bin", 5); + n += 5; + fd = create("/env/path", OWRITE, 0666); + write(fd, buf, n); + close(fd); + } + + if(arg){ + news = emalloc(strlen(t) + 1 + 1 + strlen(arg) + 1 + 1); + if(news){ + sprint(news, "%s '%s'", t, arg); /* BUG: what if quote in arg? */ + free(s); + t = news; + c->text = news; + } + } + procexecl(cpid, sfd, "rc", "rc", "-c", t, nil); + + Fail: + /* procexec hasn't happened, so send a zero */ + close(sfd[0]); + close(sfd[1]); + if(sfd[2] != sfd[1]) + close(sfd[2]); + sendul(cpid, 0); + threadexits(nil); +} + +void +runwaittask(void *v) +{ + Command *c; + Channel *cpid; + void **a; + + threadsetname("runwaittask"); + a = v; + c = a[0]; + cpid = a[1]; + free(a); + do + c->pid = recvul(cpid); + while(c->pid == ~0); + free(c->av); + if(c->pid != 0) /* successful exec */ + sendp(ccommand, c); + else{ + if(c->iseditcmd) + sendul(cedit, 0); + free(c->name); + free(c->text); + free(c); + } + chanfree(cpid); +} + +void +run(Window *win, char *s, Rune *rdir, int ndir, int newns, char *argaddr, char *xarg, int iseditcmd) +{ + void **arg; + Command *c; + Channel *cpid; + + if(s == nil) + return; + + arg = emalloc(10*sizeof(void*)); + c = emalloc(sizeof *c); + cpid = chancreate(sizeof(ulong), 0); + arg[0] = win; + arg[1] = s; + arg[2] = rdir; + arg[3] = (void*)ndir; + arg[4] = (void*)newns; + arg[5] = argaddr; + arg[6] = xarg; + arg[7] = c; + arg[8] = cpid; + arg[9] = (void*)iseditcmd; + proccreate(runproc, arg, STACK); + /* mustn't block here because must be ready to answer mount() call in run() */ + arg = emalloc(2*sizeof(void*)); + arg[0] = c; + arg[1] = cpid; + threadcreate(runwaittask, arg, STACK); +} diff --git a/src/cmd/acme/file.c b/src/cmd/acme/file.c new file mode 100644 index 00000000..cca91b2e --- /dev/null +++ b/src/cmd/acme/file.c @@ -0,0 +1,310 @@ +#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 "fns.h" + +/* + * Structure of Undo list: + * The Undo structure follows any associated data, so the list + * can be read backwards: read the structure, then read whatever + * data is associated (insert string, file name) and precedes it. + * The structure includes the previous value of the modify bit + * and a sequence number; successive Undo structures with the + * same sequence number represent simultaneous changes. + */ + +typedef struct Undo Undo; +struct Undo +{ + short type; /* Delete, Insert, Filename */ + short mod; /* modify bit */ + uint seq; /* sequence number */ + uint p0; /* location of change (unused in f) */ + uint n; /* # runes in string or file name */ +}; + +enum +{ + Undosize = sizeof(Undo)/sizeof(Rune), +}; + +File* +fileaddtext(File *f, Text *t) +{ + if(f == nil){ + f = emalloc(sizeof(File)); + f->unread = TRUE; + } + f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*)); + f->text[f->ntext++] = t; + f->curtext = t; + return f; +} + +void +filedeltext(File *f, Text *t) +{ + int i; + + for(i=0; i<f->ntext; i++) + if(f->text[i] == t) + goto Found; + error("can't find text in filedeltext"); + + Found: + f->ntext--; + if(f->ntext == 0){ + fileclose(f); + return; + } + memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*)); + if(f->curtext == t) + f->curtext = f->text[0]; +} + +void +fileinsert(File *f, uint p0, Rune *s, uint ns) +{ + if(p0 > f->b.nc) + error("internal error: fileinsert"); + if(f->seq > 0) + fileuninsert(f, &f->delta, p0, ns); + bufinsert(&f->b, p0, s, ns); + if(ns) + f->mod = TRUE; +} + +void +fileuninsert(File *f, Buffer *delta, uint p0, uint ns) +{ + Undo u; + + /* undo an insertion by deleting */ + u.type = Delete; + u.mod = f->mod; + u.seq = f->seq; + u.p0 = p0; + u.n = ns; + bufinsert(delta, delta->nc, (Rune*)&u, Undosize); +} + +void +filedelete(File *f, uint p0, uint p1) +{ + if(!(p0<=p1 && p0<=f->b.nc && p1<=f->b.nc)) + error("internal error: filedelete"); + if(f->seq > 0) + fileundelete(f, &f->delta, p0, p1); + bufdelete(&f->b, p0, p1); + if(p1 > p0) + f->mod = TRUE; +} + +void +fileundelete(File *f, Buffer *delta, uint p0, uint p1) +{ + Undo u; + Rune *buf; + uint i, n; + + /* undo a deletion by inserting */ + u.type = Insert; + u.mod = f->mod; + u.seq = f->seq; + u.p0 = p0; + u.n = p1-p0; + buf = fbufalloc(); + for(i=p0; i<p1; i+=n){ + n = p1 - i; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(&f->b, i, buf, n); + bufinsert(delta, delta->nc, buf, n); + } + fbuffree(buf); + bufinsert(delta, delta->nc, (Rune*)&u, Undosize); + +} + +void +filesetname(File *f, Rune *name, int n) +{ + if(f->seq > 0) + fileunsetname(f, &f->delta); + free(f->name); + f->name = runemalloc(n); + runemove(f->name, name, n); + f->nname = n; + f->unread = TRUE; +} + +void +fileunsetname(File *f, Buffer *delta) +{ + Undo u; + + /* undo a file name change by restoring old name */ + u.type = Filename; + u.mod = f->mod; + u.seq = f->seq; + u.p0 = 0; /* unused */ + u.n = f->nname; + if(f->nname) + bufinsert(delta, delta->nc, f->name, f->nname); + bufinsert(delta, delta->nc, (Rune*)&u, Undosize); +} + +uint +fileload(File *f, uint p0, int fd, int *nulls) +{ + if(f->seq > 0) + error("undo in file.load unimplemented"); + return bufload(&f->b, p0, fd, nulls); +} + +/* return sequence number of pending redo */ +uint +fileredoseq(File *f) +{ + Undo u; + Buffer *delta; + + delta = &f->epsilon; + if(delta->nc == 0) + return 0; + bufread(delta, delta->nc-Undosize, (Rune*)&u, Undosize); + return u.seq; +} + +void +fileundo(File *f, int isundo, uint *q0p, uint *q1p) +{ + Undo u; + Rune *buf; + uint i, j, n, up; + uint stop; + Buffer *delta, *epsilon; + + if(isundo){ + /* undo; reverse delta onto epsilon, seq decreases */ + delta = &f->delta; + epsilon = &f->epsilon; + stop = f->seq; + }else{ + /* redo; reverse epsilon onto delta, seq increases */ + delta = &f->epsilon; + epsilon = &f->delta; + stop = 0; /* don't know yet */ + } + + buf = fbufalloc(); + while(delta->nc > 0){ + up = delta->nc-Undosize; + bufread(delta, up, (Rune*)&u, Undosize); + if(isundo){ + if(u.seq < stop){ + f->seq = u.seq; + goto Return; + } + }else{ + if(stop == 0) + stop = u.seq; + if(u.seq > stop) + goto Return; + } + switch(u.type){ + default: + fprint(2, "undo: 0x%ux\n", u.type); + abort(); + break; + + case Delete: + f->seq = u.seq; + fileundelete(f, epsilon, u.p0, u.p0+u.n); + f->mod = u.mod; + bufdelete(&f->b, u.p0, u.p0+u.n); + for(j=0; j<f->ntext; j++) + textdelete(f->text[j], u.p0, u.p0+u.n, FALSE); + *q0p = u.p0; + *q1p = u.p0; + break; + + case Insert: + f->seq = u.seq; + fileuninsert(f, epsilon, u.p0, u.n); + f->mod = u.mod; + up -= u.n; + for(i=0; i<u.n; i+=n){ + n = u.n - i; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(delta, up+i, buf, n); + bufinsert(&f->b, u.p0+i, buf, n); + for(j=0; j<f->ntext; j++) + textinsert(f->text[j], u.p0+i, buf, n, FALSE); + } + *q0p = u.p0; + *q1p = u.p0+u.n; + break; + + case Filename: + f->seq = u.seq; + fileunsetname(f, epsilon); + f->mod = u.mod; + up -= u.n; + free(f->name); + if(u.n == 0) + f->name = nil; + else + f->name = runemalloc(u.n); + bufread(delta, up, f->name, u.n); + f->nname = u.n; + break; + } + bufdelete(delta, up, delta->nc); + } + if(isundo) + f->seq = 0; + Return: + fbuffree(buf); +} + +void +filereset(File *f) +{ + bufreset(&f->delta); + bufreset(&f->epsilon); + f->seq = 0; +} + +void +fileclose(File *f) +{ + free(f->name); + f->nname = 0; + f->name = nil; + free(f->text); + f->ntext = 0; + f->text = nil; + bufclose(&f->b); + bufclose(&f->delta); + bufclose(&f->epsilon); + elogclose(f); + free(f); +} + +void +filemark(File *f) +{ + if(f->epsilon.nc) + bufdelete(&f->epsilon, 0, f->epsilon.nc); + f->seq = seq; +} diff --git a/src/cmd/acme/fns.h b/src/cmd/acme/fns.h new file mode 100644 index 00000000..69bbbb3b --- /dev/null +++ b/src/cmd/acme/fns.h @@ -0,0 +1,92 @@ +/* +#pragma varargck argpos warning 2 +#pragma varargck argpos warningew 2 +*/ + +void warning(Mntdir*, char*, ...); +void warningew(Window*, Mntdir*, char*, ...); + +#define fbufalloc() emalloc(BUFSIZE) +#define fbuffree(x) free(x) + +void plumblook(Plumbmsg*m); +void plumbshow(Plumbmsg*m); +void acmeputsnarf(void); +void acmegetsnarf(void); +int tempfile(void); +void scrlresize(void); +Font* getfont(int, int, char*); +char* getarg(Text*, int, int, Rune**, int*); +char* getbytearg(Text*, int, int, char**); +void new(Text*, Text*, Text*, int, int, Rune*, int); +void undo(Text*, Text*, Text*, int, int, Rune*, int); +void scrsleep(uint); +void savemouse(Window*); +void restoremouse(Window*); +void clearmouse(void); +void allwindows(void(*)(Window*, void*), void*); +uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*); + +Window* errorwin(Mntdir*, int, Window*); +Runestr cleanrname(Runestr); +void run(Window*, char*, Rune*, int, int, char*, char*, int); +void fsysclose(void); +void setcurtext(Text*, int); +int isfilec(Rune); +void rxinit(void); +int rxnull(void); +Runestr dirname(Text*, Rune*, int); +void error(char*); +void cvttorunes(char*, int, Rune*, int*, int*, int*); +void* tmalloc(uint); +void tfree(void); +void killprocs(void); +void killtasks(void); +int runeeq(Rune*, uint, Rune*, uint); +int ALEF_tid(void); +void iconinit(void); +Timer* timerstart(int); +void timerstop(Timer*); +void timercancel(Timer*); +void timerinit(void); +void cut(Text*, Text*, Text*, int, int, Rune*, int); +void paste(Text*, Text*, Text*, int, int, Rune*, int); +void get(Text*, Text*, Text*, int, int, Rune*, int); +void put(Text*, Text*, Text*, int, int, Rune*, int); +void putfile(File*, int, int, Rune*, int); +void fontx(Text*, Text*, Text*, int, int, Rune*, int); +int isalnum(Rune); +void execute(Text*, uint, uint, int, Text*); +int search(Text*, Rune*, uint); +void look3(Text*, uint, uint, int); +void editcmd(Text*, Rune*, uint); +uint min(uint, uint); +uint max(uint, uint); +Window* lookfile(Rune*, int); +Window* lookid(int, int); +char* runetobyte(Rune*, int); +Rune* bytetorune(char*, int*); +void fsysinit(void); +Mntdir* fsysmount(Rune*, int, Rune**, int); +void fsysdelid(Mntdir*); +Xfid* respond(Xfid*, Fcall*, char*); +int rxcompile(Rune*); +int rgetc(void*, uint); +int tgetc(void*, uint); +int isaddrc(int); +int isregexc(int); +void *emalloc(uint); +void *erealloc(void*, uint); +char *estrdup(char*); +Range address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*); +int rxexecute(Text*, Rune*, uint, uint, Rangeset*); +int rxbexecute(Text*, uint, Rangeset*); +Window* makenewwindow(Text *t); +int expand(Text*, uint, uint, Expand*); +Rune* skipbl(Rune*, int, int*); +Rune* findbl(Rune*, int, int*); +char* edittext(Window*, int, Rune*, int); + +#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune)) +#define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune)) +#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune)) diff --git a/src/cmd/acme/fsys.c b/src/cmd/acme/fsys.c new file mode 100644 index 00000000..cd333dc7 --- /dev/null +++ b/src/cmd/acme/fsys.c @@ -0,0 +1,717 @@ +#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 "fns.h" + +static int sfd; + +enum +{ + Nhash = 16, + DEBUG = 0 +}; + +static Fid *fids[Nhash]; + +Fid *newfid(int); + +static Xfid* fsysflush(Xfid*, Fid*); +static Xfid* fsysauth(Xfid*, Fid*); +static Xfid* fsysversion(Xfid*, Fid*); +static Xfid* fsysattach(Xfid*, Fid*); +static Xfid* fsyswalk(Xfid*, Fid*); +static Xfid* fsysopen(Xfid*, Fid*); +static Xfid* fsyscreate(Xfid*, Fid*); +static Xfid* fsysread(Xfid*, Fid*); +static Xfid* fsyswrite(Xfid*, Fid*); +static Xfid* fsysclunk(Xfid*, Fid*); +static Xfid* fsysremove(Xfid*, Fid*); +static Xfid* fsysstat(Xfid*, Fid*); +static Xfid* fsyswstat(Xfid*, Fid*); + +Xfid* (*fcall[Tmax])(Xfid*, Fid*) = +{ + [Tflush] = fsysflush, + [Tversion] = fsysversion, + [Tauth] = fsysauth, + [Tattach] = fsysattach, + [Twalk] = fsyswalk, + [Topen] = fsysopen, + [Tcreate] = fsyscreate, + [Tread] = fsysread, + [Twrite] = fsyswrite, + [Tclunk] = fsysclunk, + [Tremove]= fsysremove, + [Tstat] = fsysstat, + [Twstat] = fsyswstat, +}; + +char Eperm[] = "permission denied"; +char Eexist[] = "file does not exist"; +char Enotdir[] = "not a directory"; + +Dirtab dirtab[]= +{ + { ".", QTDIR, Qdir, 0500|DMDIR }, + { "acme", QTDIR, Qacme, 0500|DMDIR }, + { "cons", QTFILE, Qcons, 0600 }, + { "consctl", QTFILE, Qconsctl, 0000 }, + { "draw", QTDIR, Qdraw, 0000|DMDIR }, /* to suppress graphics progs started in acme */ + { "editout", QTFILE, Qeditout, 0200 }, + { "index", QTFILE, Qindex, 0400 }, + { "label", QTFILE, Qlabel, 0600 }, + { "new", QTDIR, Qnew, 0500|DMDIR }, + { nil, } +}; + +Dirtab dirtabw[]= +{ + { ".", QTDIR, Qdir, 0500|DMDIR }, + { "addr", QTFILE, QWaddr, 0600 }, + { "body", QTAPPEND, QWbody, 0600|DMAPPEND }, + { "ctl", QTFILE, QWctl, 0600 }, + { "data", QTFILE, QWdata, 0600 }, + { "editout", QTFILE, QWeditout, 0200 }, + { "event", QTFILE, QWevent, 0600 }, + { "rdsel", QTFILE, QWrdsel, 0400 }, + { "wrsel", QTFILE, QWwrsel, 0200 }, + { "tag", QTAPPEND, QWtag, 0600|DMAPPEND }, + { nil, } +}; + +typedef struct Mnt Mnt; +struct Mnt +{ + QLock lk; + int id; + Mntdir *md; +}; + +Mnt mnt; + +Xfid* respond(Xfid*, Fcall*, char*); +int dostat(int, Dirtab*, uchar*, int, uint); +uint getclock(void); + +char *user = "Wile E. Coyote"; +static int closing = 0; +int messagesize = Maxblock+IOHDRSZ; /* good start */ + +void fsysproc(void *); + +void +fsysinit(void) +{ + int p[2]; + int n, fd; + char buf[256], *u; + + if(pipe(p) < 0) + error("can't create pipe"); + if(post9pservice(p[0], "acme") < 0) + error("can't post service"); + sfd = p[1]; + fmtinstall('F', fcallfmt); + if((u = getuser()) != nil) + user = estrdup(u); + proccreate(fsysproc, nil, STACK); +} + +void +fsysproc(void *v) +{ + int n; + Xfid *x; + Fid *f; + Fcall t; + uchar *buf; + + USED(v); + x = nil; + for(;;){ + buf = emalloc(messagesize+UTFmax); /* overflow for appending partial rune in xfidwrite */ + n = read9pmsg(sfd, buf, messagesize); + if(n <= 0){ + if(closing) + break; + error("i/o error on server channel"); + } + if(x == nil){ + sendp(cxfidalloc, nil); + x = recvp(cxfidalloc); + } + x->buf = buf; + if(convM2S(buf, n, &x->fcall) != n) + error("convert error in convM2S"); + if(DEBUG) + fprint(2, "%F\n", &x->fcall); + if(fcall[x->fcall.type] == nil) + x = respond(x, &t, "bad fcall type"); + else{ + if(x->fcall.type==Tversion || x->fcall.type==Tauth) + f = nil; + else + f = newfid(x->fcall.fid); + x->f = f; + x = (*fcall[x->fcall.type])(x, f); + } + } +} + +Mntdir* +fsysaddid(Rune *dir, int ndir, Rune **incl, int nincl) +{ + Mntdir *m; + int id; + + qlock(&mnt.lk); + id = ++mnt.id; + m = emalloc(sizeof *m); + m->id = id; + m->dir = dir; + m->ref = 1; /* one for Command, one will be incremented in attach */ + m->ndir = ndir; + m->next = mnt.md; + m->incl = incl; + m->nincl = nincl; + mnt.md = m; + qunlock(&mnt.lk); + return m; +} + +void +fsysdelid(Mntdir *idm) +{ + Mntdir *m, *prev; + int i; + char buf[64]; + + if(idm == nil) + return; + qlock(&mnt.lk); + if(--idm->ref > 0){ + qunlock(&mnt.lk); + return; + } + prev = nil; + for(m=mnt.md; m; m=m->next){ + if(m == idm){ + if(prev) + prev->next = m->next; + else + mnt.md = m->next; + for(i=0; i<m->nincl; i++) + free(m->incl[i]); + free(m->incl); + free(m->dir); + free(m); + qunlock(&mnt.lk); + return; + } + prev = m; + } + qunlock(&mnt.lk); + sprint(buf, "fsysdelid: can't find id %d\n", idm->id); + sendp(cerr, estrdup(buf)); +} + +/* + * Called only in exec.c:/^run(), from a different FD group + */ +Mntdir* +fsysmount(Rune *dir, int ndir, Rune **incl, int nincl) +{ + return fsysaddid(dir, ndir, incl, nincl); +} + +void +fsysclose(void) +{ + closing = 1; + close(sfd); +} + +Xfid* +respond(Xfid *x, Fcall *t, char *err) +{ + int n; + + if(err){ + t->type = Rerror; + t->ename = err; + }else + t->type = x->fcall.type+1; + t->fid = x->fcall.fid; + t->tag = x->fcall.tag; + if(x->buf == nil) + x->buf = emalloc(messagesize); + n = convS2M(t, x->buf, messagesize); + if(n <= 0) + error("convert error in convS2M"); + if(write(sfd, x->buf, n) != n) + error("write error in respond"); + free(x->buf); + x->buf = nil; + if(DEBUG) + fprint(2, "r: %F\n", t); + return x; +} + +static +Xfid* +fsysversion(Xfid *x, Fid *f) +{ + Fcall t; + + USED(f); + if(x->fcall.msize < 256) + return respond(x, &t, "version: message size too small"); + messagesize = x->fcall.msize; + t.msize = messagesize; + if(strncmp(x->fcall.version, "9P2000", 6) != 0) + return respond(x, &t, "unrecognized 9P version"); + t.version = "9P2000"; + return respond(x, &t, nil); +} + +static +Xfid* +fsysauth(Xfid *x, Fid *f) +{ + USED(f); + return respond(x, nil, "acme: authentication not required"); +} + +static +Xfid* +fsysflush(Xfid *x, Fid *f) +{ + USED(f); + sendp(x->c, xfidflush); + return nil; +} + +static +Xfid* +fsysattach(Xfid *x, Fid *f) +{ + Fcall t; + int id; + Mntdir *m; + + if(strcmp(x->fcall.uname, user) != 0) + return respond(x, &t, Eperm); + f->busy = TRUE; + f->open = FALSE; + f->qid.path = Qdir; + f->qid.type = QTDIR; + f->qid.vers = 0; + f->dir = dirtab; + f->nrpart = 0; + f->w = nil; + t.qid = f->qid; + f->mntdir = nil; + id = atoi(x->fcall.aname); + qlock(&mnt.lk); + for(m=mnt.md; m; m=m->next) + if(m->id == id){ + f->mntdir = m; + m->ref++; + break; + } + if(m == nil) + sendp(cerr, estrdup("unknown id in attach")); + qunlock(&mnt.lk); + return respond(x, &t, nil); +} + +static +Xfid* +fsyswalk(Xfid *x, Fid *f) +{ + Fcall t; + int c, i, j, id; + Qid q; + uchar type; + ulong path; + Fid *nf; + Dirtab *d, *dir; + Window *w; + char *err; + + nf = nil; + w = nil; + if(f->open) + return respond(x, &t, "walk of open file"); + if(x->fcall.fid != x->fcall.newfid){ + nf = newfid(x->fcall.newfid); + if(nf->busy) + return respond(x, &t, "newfid already in use"); + nf->busy = TRUE; + nf->open = FALSE; + nf->mntdir = f->mntdir; + if(f->mntdir) + f->mntdir->ref++; + nf->dir = f->dir; + nf->qid = f->qid; + nf->w = f->w; + nf->nrpart = 0; /* not open, so must be zero */ + if(nf->w) + incref(&nf->w->ref); + f = nf; /* walk f */ + } + + t.nwqid = 0; + err = nil; + dir = nil; + id = WIN(f->qid); + q = f->qid; + + if(x->fcall.nwname > 0){ + for(i=0; i<x->fcall.nwname; i++){ + if((q.type & QTDIR) == 0){ + err = Enotdir; + break; + } + + if(strcmp(x->fcall.wname[i], "..") == 0){ + type = QTDIR; + path = Qdir; + id = 0; + if(w){ + winclose(w); + w = nil; + } + Accept: + if(i == MAXWELEM){ + err = "name too long"; + break; + } + q.type = type; + q.vers = 0; + q.path = QID(id, path); + t.wqid[t.nwqid++] = q; + continue; + } + + /* is it a numeric name? */ + for(j=0; (c=x->fcall.wname[i][j]); j++) + if(c<'0' || '9'<c) + goto Regular; + /* yes: it's a directory */ + if(w) /* name has form 27/23; get out before losing w */ + break; + id = atoi(x->fcall.wname[i]); + qlock(&row.lk); + w = lookid(id, FALSE); + if(w == nil){ + qunlock(&row.lk); + break; + } + incref(&w->ref); /* we'll drop reference at end if there's an error */ + path = Qdir; + type = QTDIR; + qunlock(&row.lk); + dir = dirtabw; + goto Accept; + + Regular: +// if(FILE(f->qid) == Qacme) /* empty directory */ +// break; + if(strcmp(x->fcall.wname[i], "new") == 0){ + if(w) + error("w set in walk to new"); + sendp(cnewwindow, nil); /* signal newwindowthread */ + w = recvp(cnewwindow); /* receive new window */ + incref(&w->ref); + type = QTDIR; + path = QID(w->id, Qdir); + id = w->id; + dir = dirtabw; + goto Accept; + } + + if(id == 0) + d = dirtab; + else + d = dirtabw; + d++; /* skip '.' */ + for(; d->name; d++) + if(strcmp(x->fcall.wname[i], d->name) == 0){ + path = d->qid; + type = d->type; + dir = d; + goto Accept; + } + + break; /* file not found */ + } + + if(i==0 && err == nil) + err = Eexist; + } + + if(err!=nil || t.nwqid<x->fcall.nwname){ + if(nf){ + nf->busy = FALSE; + fsysdelid(nf->mntdir); + } + }else if(t.nwqid == x->fcall.nwname){ + if(w){ + f->w = w; + w = nil; /* don't drop the reference */ + } + if(dir) + f->dir = dir; + f->qid = q; + } + + if(w != nil) + winclose(w); + + return respond(x, &t, err); +} + +static +Xfid* +fsysopen(Xfid *x, Fid *f) +{ + Fcall t; + int m; + + /* can't truncate anything, so just disregard */ + x->fcall.mode &= ~(OTRUNC|OCEXEC); + /* can't execute or remove anything */ + if(x->fcall.mode==OEXEC || (x->fcall.mode&ORCLOSE)) + goto Deny; + switch(x->fcall.mode){ + default: + goto Deny; + case OREAD: + m = 0400; + break; + case OWRITE: + m = 0200; + break; + case ORDWR: + m = 0600; + break; + } + if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m) + goto Deny; + + sendp(x->c, xfidopen); + return nil; + + Deny: + return respond(x, &t, Eperm); +} + +static +Xfid* +fsyscreate(Xfid *x, Fid *f) +{ + Fcall t; + + USED(f); + return respond(x, &t, Eperm); +} + +static +int +idcmp(const void *a, const void *b) +{ + return *(int*)a - *(int*)b; +} + +static +Xfid* +fsysread(Xfid *x, Fid *f) +{ + Fcall t; + uchar *b; + int i, id, n, o, e, j, k, *ids, nids; + Dirtab *d, dt; + Column *c; + uint clock, len; + char buf[16]; + + if(f->qid.type & QTDIR){ + if(FILE(f->qid) == Qacme){ /* empty dir */ + t.data = nil; + t.count = 0; + respond(x, &t, nil); + return x; + } + o = x->fcall.offset; + e = x->fcall.offset+x->fcall.count; + clock = getclock(); + b = emalloc(messagesize); + id = WIN(f->qid); + n = 0; + if(id > 0) + d = dirtabw; + else + d = dirtab; + d++; /* first entry is '.' */ + for(i=0; d->name!=nil && i<e; i+=len){ + len = dostat(WIN(x->f->qid), d, b+n, x->fcall.count-n, clock); + if(len <= BIT16SZ) + break; + if(i >= o) + n += len; + d++; + } + if(id == 0){ + qlock(&row.lk); + nids = 0; + ids = nil; + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(k=0; k<c->nw; k++){ + ids = realloc(ids, (nids+1)*sizeof(int)); + ids[nids++] = c->w[k]->id; + } + } + qunlock(&row.lk); + qsort(ids, nids, sizeof ids[0], idcmp); + j = 0; + dt.name = buf; + for(; j<nids && i<e; i+=len){ + k = ids[j]; + sprint(dt.name, "%d", k); + dt.qid = QID(k, Qdir); + dt.type = QTDIR; + dt.perm = DMDIR|0700; + len = dostat(k, &dt, b+n, x->fcall.count-n, clock); + if(len == 0) + break; + if(i >= o) + n += len; + j++; + } + free(ids); + } + t.data = (char*)b; + t.count = n; + respond(x, &t, nil); + free(b); + return x; + } + sendp(x->c, xfidread); + return nil; +} + +static +Xfid* +fsyswrite(Xfid *x, Fid *f) +{ + USED(f); + sendp(x->c, xfidwrite); + return nil; +} + +static +Xfid* +fsysclunk(Xfid *x, Fid *f) +{ + fsysdelid(f->mntdir); + sendp(x->c, xfidclose); + return nil; +} + +static +Xfid* +fsysremove(Xfid *x, Fid *f) +{ + Fcall t; + + USED(f); + return respond(x, &t, Eperm); +} + +static +Xfid* +fsysstat(Xfid *x, Fid *f) +{ + Fcall t; + + t.stat = emalloc(messagesize-IOHDRSZ); + t.nstat = dostat(WIN(x->f->qid), f->dir, t.stat, messagesize-IOHDRSZ, getclock()); + x = respond(x, &t, nil); + free(t.stat); + return x; +} + +static +Xfid* +fsyswstat(Xfid *x, Fid *f) +{ + Fcall t; + + USED(f); + return respond(x, &t, Eperm); +} + +Fid* +newfid(int fid) +{ + Fid *f, *ff, **fh; + + ff = nil; + fh = &fids[fid&(Nhash-1)]; + for(f=*fh; f; f=f->next) + if(f->fid == fid) + return f; + else if(ff==nil && f->busy==FALSE) + ff = f; + if(ff){ + ff->fid = fid; + return ff; + } + f = emalloc(sizeof *f); + f->fid = fid; + f->next = *fh; + *fh = f; + return f; +} + +uint +getclock(void) +{ +/* + char buf[32]; + + buf[0] = '\0'; + pread(clockfd, buf, sizeof buf, 0); + return atoi(buf); +*/ + return time(0); +} + +int +dostat(int id, Dirtab *dir, uchar *buf, int nbuf, uint clock) +{ + Dir d; + + d.qid.path = QID(id, dir->qid); + d.qid.vers = 0; + d.qid.type = dir->type; + d.mode = dir->perm; + d.length = 0; /* would be nice to do better */ + d.name = dir->name; + d.uid = user; + d.gid = user; + d.muid = user; + d.atime = clock; + d.mtime = clock; + return convD2M(&d, buf, nbuf); +} diff --git a/src/cmd/acme/look.c b/src/cmd/acme/look.c new file mode 100644 index 00000000..9f54f9e4 --- /dev/null +++ b/src/cmd/acme/look.c @@ -0,0 +1,772 @@ +#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 <regexp.h> +#include <plumb.h> +#include "dat.h" +#include "fns.h" + +Window* openfile(Text*, Expand*); + +int nuntitled; + +void +look3(Text *t, uint q0, uint q1, int external) +{ + int n, c, f, expanded; + Text *ct; + Expand e; + Rune *r; + uint p; + Plumbmsg *m; + Runestr dir; + char buf[32]; + + ct = seltext; + if(ct == nil) + seltext = t; + expanded = expand(t, q0, q1, &e); + if(!external && t->w!=nil && t->w->nopen[QWevent]>0){ + /* send alphanumeric expansion to external client */ + if(expanded == FALSE) + return; + f = 0; + if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil)) + f = 1; /* acme can do it without loading a file */ + if(q0!=e.q0 || q1!=e.q1) + f |= 2; /* second (post-expand) message follows */ + if(e.nname) + f |= 4; /* it's a file name */ + c = 'l'; + if(t->what == Body) + c = 'L'; + n = q1-q0; + if(n <= EVENTSIZE){ + r = runemalloc(n); + bufread(&t->file->b, q0, r, n); + winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r); + free(r); + }else + winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n); + if(q0==e.q0 && q1==e.q1) + return; + if(e.nname){ + n = e.nname; + if(e.a1 > e.a0) + n += 1+(e.a1-e.a0); + r = runemalloc(n); + runemove(r, e.name, e.nname); + if(e.a1 > e.a0){ + r[e.nname] = ':'; + bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0); + } + }else{ + n = e.q1 - e.q0; + r = runemalloc(n); + bufread(&t->file->b, e.q0, r, n); + } + f &= ~2; + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r); + else + winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n); + free(r); + goto Return; + } + if(plumbsendfd >= 0){ + /* send whitespace-delimited word to plumber */ + m = emalloc(sizeof(Plumbmsg)); + m->src = estrdup("acme"); + m->dst = nil; + dir = dirname(t, nil, 0); + if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */ + free(dir.r); + dir.r = nil; + dir.nr = 0; + } + if(dir.nr == 0) + m->wdir = estrdup(wdir); + else + m->wdir = runetobyte(dir.r, dir.nr); + free(dir.r); + m->type = estrdup("text"); + m->attr = nil; + buf[0] = '\0'; + if(q1 == q0){ + if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ + q0 = t->q0; + q1 = t->q1; + }else{ + p = q0; + while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n') + q0--; + while(q1<t->file->b.nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n') + q1++; + if(q1 == q0){ + plumbfree(m); + goto Return; + } + sprint(buf, "click=%d", p-q0); + m->attr = plumbunpackattr(buf); + } + } + r = runemalloc(q1-q0); + bufread(&t->file->b, q0, r, q1-q0); + m->data = runetobyte(r, q1-q0); + m->ndata = strlen(m->data); + free(r); + if(m->ndata<messagesize-1024 && plumbsend(plumbsendfd, m) >= 0){ + plumbfree(m); + goto Return; + } + plumbfree(m); + /* plumber failed to match; fall through */ + } + + /* interpret alphanumeric string ourselves */ + if(expanded == FALSE) + return; + if(e.name || e.u.at) + openfile(t, &e); + else{ + if(t->w == nil) + return; + ct = &t->w->body; + if(t->w != ct->w) + winlock(ct->w, 'M'); + if(t == ct) + textsetselect(ct, e.q1, e.q1); + n = e.q1 - e.q0; + r = runemalloc(n); + bufread(&t->file->b, e.q0, r, n); + if(search(ct, r, n) && e.jump) + moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4))); + if(t->w != ct->w) + winunlock(ct->w); + free(r); + } + + Return: + free(e.name); + free(e.bname); +} + +int +plumbgetc(void *a, uint n) +{ + Rune *r; + + r = a; + if(n<0 || n>runestrlen(r)) + return 0; + return r[n]; +} + +void +plumblook(Plumbmsg *m) +{ + Expand e; + char *addr; + + if(m->ndata >= BUFSIZE){ + warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data); + return; + } + e.q0 = 0; + e.q1 = 0; + if(m->data[0] == '\0') + return; + e.u.ar = nil; + e.bname = m->data; + e.name = bytetorune(e.bname, &e.nname); + e.jump = TRUE; + e.a0 = 0; + e.a1 = 0; + addr = plumblookup(m->attr, "addr"); + if(addr != nil){ + e.u.ar = bytetorune(addr, &e.a1); + e.agetc = plumbgetc; + } + openfile(nil, &e); + free(e.name); + free(e.u.at); +} + +void +plumbshow(Plumbmsg *m) +{ + Window *w; + Rune rb[256], *r; + int nb, nr; + Runestr rs; + char *name, *p, namebuf[16]; + + w = makenewwindow(nil); + name = plumblookup(m->attr, "filename"); + if(name == nil){ + name = namebuf; + nuntitled++; + snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled); + } + p = nil; + if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){ + nb = strlen(m->wdir) + 1 + strlen(name) + 1; + p = emalloc(nb); + snprint(p, nb, "%s/%s", m->wdir, name); + name = p; + } + cvttorunes(name, strlen(name), rb, &nb, &nr, nil); + free(p); + rs = cleanrname((Runestr){rb, nr}); + winsetname(w, rs.r, rs.nr); + r = runemalloc(m->ndata); + cvttorunes(m->data, m->ndata, r, &nb, &nr, nil); + textinsert(&w->body, 0, r, nr, TRUE); + free(r); + w->body.file->mod = FALSE; + w->dirty = FALSE; + winsettag(w); + textscrdraw(&w->body); + textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc); +} + +int +search(Text *ct, Rune *r, uint n) +{ + uint q, nb, maxn; + int around; + Rune *s, *b, *c; + + if(n==0 || n>ct->file->b.nc) + return FALSE; + if(2*n > RBUFSIZE){ + warning(nil, "string too long\n"); + return FALSE; + } + maxn = max(2*n, RBUFSIZE); + s = fbufalloc(); + b = s; + nb = 0; + b[nb] = 0; + around = 0; + q = ct->q1; + for(;;){ + if(q >= ct->file->b.nc){ + q = 0; + around = 1; + nb = 0; + b[nb] = 0; + } + if(nb > 0){ + c = runestrchr(b, r[0]); + if(c == nil){ + q += nb; + nb = 0; + b[nb] = 0; + if(around && q>=ct->q1) + break; + continue; + } + q += (c-b); + nb -= (c-b); + b = c; + } + /* reload if buffer covers neither string nor rest of file */ + if(nb<n && nb!=ct->file->b.nc-q){ + nb = ct->file->b.nc-q; + if(nb >= maxn) + nb = maxn-1; + bufread(&ct->file->b, q, s, nb); + b = s; + b[nb] = '\0'; + } + /* this runeeq is fishy but the null at b[nb] makes it safe */ + if(runeeq(b, n, r, n)==TRUE){ + if(ct->w){ + textshow(ct, q, q+n, 1); + winsettag(ct->w); + }else{ + ct->q0 = q; + ct->q1 = q+n; + } + seltext = ct; + fbuffree(s); + return TRUE; + } + if(around && q>=ct->q1) + break; + --nb; + b++; + q++; + } + fbuffree(s); + return FALSE; +} + +int +isfilec(Rune r) +{ + static Rune Lx[] = { '.', '-', '+', '/', ':', 0 }; + if(isalnum(r)) + return TRUE; + if(runestrchr(Lx, r)) + return TRUE; + return FALSE; +} + +Runestr +cleanrname(Runestr rs) +{ + int i, j, found; + Rune *b; + int n; + static Rune Lslashdotdot[] = { '/', '.', '.', 0 }; + + b = rs.r; + n = rs.nr; + + /* compress multiple slashes */ + for(i=0; i<n-1; i++) + if(b[i]=='/' && b[i+1]=='/'){ + runemove(b+i, b+i+1, n-i-1); + --n; + --i; + } + /* eliminate ./ */ + for(i=0; i<n-1; i++) + if(b[i]=='.' && b[i+1]=='/' && (i==0 || b[i-1]=='/')){ + runemove(b+i, b+i+2, n-i-2); + n -= 2; + --i; + } + /* eliminate trailing . */ + if(n>=2 && b[n-2]=='/' && b[n-1]=='.') + --n; + do{ + /* compress xx/.. */ + found = FALSE; + for(i=1; i<=n-3; i++) + if(runeeq(b+i, 3, Lslashdotdot, 3)){ + if(i==n-3 || b[i+3]=='/'){ + found = TRUE; + break; + } + } + if(found) + for(j=i-1; j>=0; --j) + if(j==0 || b[j-1]=='/'){ + i += 3; /* character beyond .. */ + if(i<n && b[i]=='/') + ++i; + runemove(b+j, b+i, n-i); + n -= (i-j); + break; + } + }while(found); + if(n == 0){ + *b = '.'; + n = 1; + } + return (Runestr){b, n}; +} + +Runestr +includefile(Rune *dir, Rune *file, int nfile) +{ + int m, n; + char *a; + Rune *r; + static Rune Lslash[] = { '/', 0 }; + + m = runestrlen(dir); + a = emalloc((m+1+nfile)*UTFmax+1); + sprint(a, "%S/%.*S", dir, nfile, file); + n = access(a, 0); + free(a); + if(n < 0) + return (Runestr){nil, 0}; + r = runemalloc(m+1+nfile); + runemove(r, dir, m); + runemove(r+m, Lslash, 1); + runemove(r+m+1, file, nfile); + free(file); + return cleanrname((Runestr){r, m+1+nfile}); +} + +static Rune *objdir; + +Runestr +includename(Text *t, Rune *r, int n) +{ + Window *w; + char buf[128]; + Rune Lsysinclude[] = { '/', 's', 'y', 's', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 }; + Runestr file; + int i; + + if(objdir==nil && objtype!=nil){ + sprint(buf, "/%s/include", objtype); + objdir = bytetorune(buf, &i); + objdir = runerealloc(objdir, i+1); + objdir[i] = '\0'; + } + + w = t->w; + if(n==0 || r[0]=='/' || w==nil) + goto Rescue; + if(n>2 && r[0]=='.' && r[1]=='/') + goto Rescue; + file.r = nil; + file.nr = 0; + for(i=0; i<w->nincl && file.r==nil; i++) + file = includefile(w->incl[i], r, n); + + if(file.r == nil) + file = includefile(Lsysinclude, r, n); + if(file.r==nil && objdir!=nil) + file = includefile(objdir, r, n); + if(file.r == nil) + goto Rescue; + return file; + + Rescue: + return (Runestr){r, n}; +} + +Runestr +dirname(Text *t, Rune *r, int n) +{ + Rune *b, c; + uint m, nt; + int slash; + Runestr tmp; + + b = nil; + if(t==nil || t->w==nil) + goto Rescue; + nt = t->w->tag.file->b.nc; + if(nt == 0) + goto Rescue; + if(n>=1 && r[0]=='/') + goto Rescue; + b = runemalloc(nt+n+1); + bufread(&t->w->tag.file->b, 0, b, nt); + slash = -1; + for(m=0; m<nt; m++){ + c = b[m]; + if(c == '/') + slash = m; + if(c==' ' || c=='\t') + break; + } + if(slash < 0) + goto Rescue; + runemove(b+slash+1, r, n); + free(r); + return cleanrname((Runestr){b, slash+1+n}); + + Rescue: + free(b); + tmp = (Runestr){r, n}; + if(r) + return cleanrname(tmp); + return tmp; +} + +int +expandfile(Text *t, uint q0, uint q1, Expand *e) +{ + int i, n, nname, colon, eval; + uint amin, amax; + Rune *r, c; + Window *w; + Runestr rs; + + amax = q1; + if(q1 == q0){ + colon = -1; + while(q1<t->file->b.nc && isfilec(c=textreadc(t, q1))){ + if(c == ':'){ + colon = q1; + break; + } + q1++; + } + while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){ + q0--; + if(colon<0 && c==':') + colon = q0; + } + /* + * if it looks like it might begin file: , consume address chars after : + * otherwise terminate expansion at : + */ + if(colon >= 0){ + q1 = colon; + if(colon<t->file->b.nc-1 && isaddrc(textreadc(t, colon+1))){ + q1 = colon+1; + while(q1<t->file->b.nc-1 && isaddrc(textreadc(t, q1))) + q1++; + } + } + if(q1 > q0) + if(colon >= 0){ /* stop at white space */ + for(amax=colon+1; amax<t->file->b.nc; amax++) + if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n') + break; + }else + amax = t->file->b.nc; + } + amin = amax; + e->q0 = q0; + e->q1 = q1; + n = q1-q0; + if(n == 0) + return FALSE; + /* see if it's a file name */ + r = runemalloc(n); + bufread(&t->file->b, q0, r, n); + /* first, does it have bad chars? */ + nname = -1; + for(i=0; i<n; i++){ + c = r[i]; + if(c==':' && nname<0){ + if(q0+i+1<t->file->b.nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1)))) + amin = q0+i; + else + goto Isntfile; + nname = i; + } + } + if(nname == -1) + nname = n; + for(i=0; i<nname; i++) + if(!isfilec(r[i])) + goto Isntfile; + /* + * See if it's a file name in <>, and turn that into an include + * file name if so. Should probably do it for "" too, but that's not + * restrictive enough syntax and checking for a #include earlier on the + * line would be silly. + */ + if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->b.nc && textreadc(t, q1)=='>'){ + rs = includename(t, r, nname); + r = rs.r; + nname = rs.nr; + } + else if(amin == q0) + goto Isfile; + else{ + rs = dirname(t, r, nname); + r = rs.r; + nname = rs.nr; + } + e->bname = runetobyte(r, nname); + /* if it's already a window name, it's a file */ + w = lookfile(r, nname); + if(w != nil) + goto Isfile; + /* if it's the name of a file, it's a file */ + if(access(e->bname, 0) < 0){ + free(e->bname); + e->bname = nil; + goto Isntfile; + } + + Isfile: + e->name = r; + e->nname = nname; + e->u.at = t; + e->a0 = amin+1; + eval = FALSE; + address(nil, nil, (Range){-1,-1}, (Range){0, 0}, t, e->a0, amax, tgetc, &eval, (uint*)&e->a1); + return TRUE; + + Isntfile: + free(r); + return FALSE; +} + +int +expand(Text *t, uint q0, uint q1, Expand *e) +{ + memset(e, 0, sizeof *e); + e->agetc = tgetc; + /* if in selection, choose selection */ + e->jump = TRUE; + if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ + q0 = t->q0; + q1 = t->q1; + if(t->what == Tag) + e->jump = FALSE; + } + + if(expandfile(t, q0, q1, e)) + return TRUE; + + if(q0 == q1){ + while(q1<t->file->b.nc && isalnum(textreadc(t, q1))) + q1++; + while(q0>0 && isalnum(textreadc(t, q0-1))) + q0--; + } + e->q0 = q0; + e->q1 = q1; + return q1 > q0; +} + +Window* +lookfile(Rune *s, int n) +{ + int i, j, k; + Window *w; + Column *c; + Text *t; + + /* avoid terminal slash on directories */ + if(n>1 && s[n-1] == '/') + --n; + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + t = &w->body; + k = t->file->nname; + if(k>1 && t->file->name[k-1] == '/') + k--; + if(runeeq(t->file->name, k, s, n)){ + w = w->body.file->curtext->w; + if(w->col != nil) /* protect against race deleting w */ + return w; + } + } + } + return nil; +} + +Window* +lookid(int id, int dump) +{ + int i, j; + Window *w; + Column *c; + + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + if(dump && w->dumpid == id) + return w; + if(!dump && w->id == id) + return w; + } + } + return nil; +} + + +Window* +openfile(Text *t, Expand *e) +{ + Range r; + Window *w, *ow; + int eval, i, n; + Rune *rp; + uint dummy; + + if(e->nname == 0){ + w = t->w; + if(w == nil) + return nil; + }else + w = lookfile(e->name, e->nname); + if(w){ + t = &w->body; + if(!t->col->safe && t->fr.maxlines==0) /* window is obscured by full-column window */ + colgrow(t->col, t->col->w[0], 1); + }else{ + ow = nil; + if(t) + ow = t->w; + w = makenewwindow(t); + t = &w->body; + winsetname(w, e->name, e->nname); + textload(t, 0, e->bname, 1); + t->file->mod = FALSE; + t->w->dirty = FALSE; + winsettag(t->w); + textsetselect(&t->w->tag, t->w->tag.file->b.nc, t->w->tag.file->b.nc); + if(ow != nil) + for(i=ow->nincl; --i>=0; ){ + n = runestrlen(ow->incl[i]); + rp = runemalloc(n); + runemove(rp, ow->incl[i], n); + winaddincl(w, rp, n); + } + } + if(e->a1 == e->a0) + eval = FALSE; + else{ + eval = TRUE; + r = address(nil, t, (Range){-1, -1}, (Range){t->q0, t->q1}, e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy); + if(eval == FALSE) + e->jump = FALSE; /* don't jump if invalid address */ + } + if(eval == FALSE){ + r.q0 = t->q0; + r.q1 = t->q1; + } + textshow(t, r.q0, r.q1, 1); + winsettag(t->w); + seltext = t; + if(e->jump) + moveto(mousectl, addpt(frptofchar(&t->fr, t->fr.p0), Pt(4, font->height-4))); + return w; +} + +void +new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg) +{ + int ndone; + Rune *a, *f; + int na, nf; + Expand e; + Runestr rs; + + getarg(argt, FALSE, TRUE, &a, &na); + if(a){ + new(et, t, nil, flag1, flag2, a, na); + if(narg == 0) + return; + } + /* loop condition: *arg is not a blank */ + for(ndone=0; ; ndone++){ + a = findbl(arg, narg, &na); + if(a == arg){ + if(ndone==0 && et->col!=nil) + winsettag(coladd(et->col, nil, nil, -1)); + break; + } + nf = narg-na; + f = runemalloc(nf); + runemove(f, arg, nf); + rs = dirname(et, f, nf); + f = rs.r; + nf = rs.nr; + memset(&e, 0, sizeof e); + e.name = f; + e.nname = nf; + e.bname = runetobyte(f, nf); + e.jump = TRUE; + openfile(et, &e); + free(f); + free(e.bname); + arg = skipbl(a, na, &narg); + } +} diff --git a/src/cmd/acme/mkfile b/src/cmd/acme/mkfile new file mode 100644 index 00000000..84149eb9 --- /dev/null +++ b/src/cmd/acme/mkfile @@ -0,0 +1,41 @@ +PLAN9=../../.. +<$PLAN9/src/mkhdr + +TARG=acme + +OFILES=\ + acme.$O\ + addr.$O\ + buff.$O\ + cols.$O\ + disk.$O\ + ecmd.$O\ + edit.$O\ + elog.$O\ + exec.$O\ + file.$O\ + fsys.$O\ + look.$O\ + regx.$O\ + rows.$O\ + scrl.$O\ + text.$O\ + time.$O\ + util.$O\ + wind.$O\ + xfid.$O\ + +HFILES=dat.h\ + edit.h\ + fns.h\ + +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + +<$PLAN9/src/mkone + +LDFLAGS=$LDFLAGS -lfs -lmux -lplumb -lthread -lframe -ldraw -lbio -l9 -lfmt -lutf -L$X11/lib -lX11 + +edit.$O ecmd.$O elog.$O: edit.h diff --git a/src/cmd/acme/regx.c b/src/cmd/acme/regx.c new file mode 100644 index 00000000..f934187b --- /dev/null +++ b/src/cmd/acme/regx.c @@ -0,0 +1,835 @@ +#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 "fns.h" + +Rangeset sel; +Rune *lastregexp; + +/* + * Machine Information + */ +typedef struct Inst Inst; +struct Inst +{ + uint type; /* < 0x10000 ==> literal, otherwise action */ + union { + int sid; + int subid; + int class; + Inst *other; + Inst *right; + } u; + union{ + Inst *left; + Inst *next; + } u1; +}; + +#define NPROG 1024 +Inst program[NPROG]; +Inst *progp; +Inst *startinst; /* First inst. of program; might not be program[0] */ +Inst *bstartinst; /* same for backwards machine */ +Channel *rechan; /* chan(Inst*) */ + +typedef struct Ilist Ilist; +struct Ilist +{ + Inst *inst; /* Instruction of the thread */ + Rangeset se; + uint startp; /* first char of match */ +}; + +#define NLIST 128 + +Ilist *tl, *nl; /* This list, next list */ +Ilist list[2][NLIST]; +static Rangeset sempty; + +/* + * Actions and Tokens + * + * 0x100xx are operators, value == precedence + * 0x200xx are tokens, i.e. operands for operators + */ +#define OPERATOR 0x10000 /* Bitmask of all operators */ +#define START 0x10000 /* Start, used for marker on stack */ +#define RBRA 0x10001 /* Right bracket, ) */ +#define LBRA 0x10002 /* Left bracket, ( */ +#define OR 0x10003 /* Alternation, | */ +#define CAT 0x10004 /* Concatentation, implicit operator */ +#define STAR 0x10005 /* Closure, * */ +#define PLUS 0x10006 /* a+ == aa* */ +#define QUEST 0x10007 /* a? == a|nothing, i.e. 0 or 1 a's */ +#define ANY 0x20000 /* Any character but newline, . */ +#define NOP 0x20001 /* No operation, internal use only */ +#define BOL 0x20002 /* Beginning of line, ^ */ +#define EOL 0x20003 /* End of line, $ */ +#define CCLASS 0x20004 /* Character class, [] */ +#define NCCLASS 0x20005 /* Negated character class, [^] */ +#define END 0x20077 /* Terminate: match found */ + +#define ISATOR 0x10000 +#define ISAND 0x20000 + +/* + * Parser Information + */ +typedef struct Node Node; +struct Node +{ + Inst *first; + Inst *last; +}; + +#define NSTACK 20 +Node andstack[NSTACK]; +Node *andp; +int atorstack[NSTACK]; +int *atorp; +int lastwasand; /* Last token was operand */ +int cursubid; +int subidstack[NSTACK]; +int *subidp; +int backwards; +int nbra; +Rune *exprp; /* pointer to next character in source expression */ +#define DCLASS 10 /* allocation increment */ +int nclass; /* number active */ +int Nclass; /* high water mark */ +Rune **class; +int negateclass; + +void addinst(Ilist *l, Inst *inst, Rangeset *sep); +void newmatch(Rangeset*); +void bnewmatch(Rangeset*); +void pushand(Inst*, Inst*); +void pushator(int); +Node *popand(int); +int popator(void); +void startlex(Rune*); +int lex(void); +void operator(int); +void operand(int); +void evaluntil(int); +void optimize(Inst*); +void bldcclass(void); + +void +rxinit(void) +{ + rechan = chancreate(sizeof(Inst*), 0); + lastregexp = runemalloc(1); +} + +void +regerror(char *e) +{ + lastregexp[0] = 0; + warning(nil, "regexp: %s\n", e); + sendp(rechan, nil); + threadexits(nil); +} + +Inst * +newinst(int t) +{ + if(progp >= &program[NPROG]) + regerror("expression too long"); + progp->type = t; + progp->u1.left = nil; + progp->u.right = nil; + return progp++; +} + +void +realcompile(void *arg) +{ + int token; + Rune *s; + + threadsetname("regcomp"); + s = arg; + startlex(s); + atorp = atorstack; + andp = andstack; + subidp = subidstack; + cursubid = 0; + lastwasand = FALSE; + /* Start with a low priority operator to prime parser */ + pushator(START-1); + while((token=lex()) != END){ + if((token&ISATOR) == OPERATOR) + operator(token); + else + operand(token); + } + /* Close with a low priority operator */ + evaluntil(START); + /* Force END */ + operand(END); + evaluntil(START); + if(nbra) + regerror("unmatched `('"); + --andp; /* points to first and only operand */ + sendp(rechan, andp->first); + threadexits(nil); +} + +/* r is null terminated */ +int +rxcompile(Rune *r) +{ + int i, nr; + Inst *oprogp; + + nr = runestrlen(r)+1; + if(runeeq(lastregexp, runestrlen(lastregexp)+1, r, nr)==TRUE) + return TRUE; + lastregexp[0] = 0; + for(i=0; i<nclass; i++) + free(class[i]); + nclass = 0; + progp = program; + backwards = FALSE; + bstartinst = nil; + threadcreate(realcompile, r, STACK); + startinst = recvp(rechan); + if(startinst == nil) + return FALSE; + optimize(program); + oprogp = progp; + backwards = TRUE; + threadcreate(realcompile, r, STACK); + bstartinst = recvp(rechan); + if(bstartinst == nil) + return FALSE; + optimize(oprogp); + lastregexp = runerealloc(lastregexp, nr); + runemove(lastregexp, r, nr); + return TRUE; +} + +void +operand(int t) +{ + Inst *i; + if(lastwasand) + operator(CAT); /* catenate is implicit */ + i = newinst(t); + if(t == CCLASS){ + if(negateclass) + i->type = NCCLASS; /* UGH */ + i->u.class = nclass-1; /* UGH */ + } + pushand(i, i); + lastwasand = TRUE; +} + +void +operator(int t) +{ + if(t==RBRA && --nbra<0) + regerror("unmatched `)'"); + if(t==LBRA){ + cursubid++; /* silently ignored */ + nbra++; + if(lastwasand) + operator(CAT); + }else + evaluntil(t); + if(t!=RBRA) + pushator(t); + lastwasand = FALSE; + if(t==STAR || t==QUEST || t==PLUS || t==RBRA) + lastwasand = TRUE; /* these look like operands */ +} + +void +pushand(Inst *f, Inst *l) +{ + if(andp >= &andstack[NSTACK]) + error("operand stack overflow"); + andp->first = f; + andp->last = l; + andp++; +} + +void +pushator(int t) +{ + if(atorp >= &atorstack[NSTACK]) + error("operator stack overflow"); + *atorp++=t; + if(cursubid >= NRange) + *subidp++= -1; + else + *subidp++=cursubid; +} + +Node * +popand(int op) +{ + char buf[64]; + + if(andp <= &andstack[0]) + if(op){ + sprint(buf, "missing operand for %c", op); + regerror(buf); + }else + regerror("malformed regexp"); + return --andp; +} + +int +popator() +{ + if(atorp <= &atorstack[0]) + error("operator stack underflow"); + --subidp; + return *--atorp; +} + +void +evaluntil(int pri) +{ + Node *op1, *op2, *t; + Inst *inst1, *inst2; + + while(pri==RBRA || atorp[-1]>=pri){ + switch(popator()){ + case LBRA: + op1 = popand('('); + inst2 = newinst(RBRA); + inst2->u.subid = *subidp; + op1->last->u1.next = inst2; + inst1 = newinst(LBRA); + inst1->u.subid = *subidp; + inst1->u1.next = op1->first; + pushand(inst1, inst2); + return; /* must have been RBRA */ + default: + error("unknown regexp operator"); + break; + case OR: + op2 = popand('|'); + op1 = popand('|'); + inst2 = newinst(NOP); + op2->last->u1.next = inst2; + op1->last->u1.next = inst2; + inst1 = newinst(OR); + inst1->u.right = op1->first; + inst1->u1.left = op2->first; + pushand(inst1, inst2); + break; + case CAT: + op2 = popand(0); + op1 = popand(0); + if(backwards && op2->first->type!=END){ + t = op1; + op1 = op2; + op2 = t; + } + op1->last->u1.next = op2->first; + pushand(op1->first, op2->last); + break; + case STAR: + op2 = popand('*'); + inst1 = newinst(OR); + op2->last->u1.next = inst1; + inst1->u.right = op2->first; + pushand(inst1, inst1); + break; + case PLUS: + op2 = popand('+'); + inst1 = newinst(OR); + op2->last->u1.next = inst1; + inst1->u.right = op2->first; + pushand(op2->first, inst1); + break; + case QUEST: + op2 = popand('?'); + inst1 = newinst(OR); + inst2 = newinst(NOP); + inst1->u1.left = inst2; + inst1->u.right = op2->first; + op2->last->u1.next = inst2; + pushand(inst1, inst2); + break; + } + } +} + + +void +optimize(Inst *start) +{ + Inst *inst, *target; + + for(inst=start; inst->type!=END; inst++){ + target = inst->u1.next; + while(target->type == NOP) + target = target->u1.next; + inst->u1.next = target; + } +} + +void +startlex(Rune *s) +{ + exprp = s; + nbra = 0; +} + + +int +lex(void){ + int c; + + c = *exprp++; + switch(c){ + case '\\': + if(*exprp) + if((c= *exprp++)=='n') + c='\n'; + break; + case 0: + c = END; + --exprp; /* In case we come here again */ + break; + case '*': + c = STAR; + break; + case '?': + c = QUEST; + break; + case '+': + c = PLUS; + break; + case '|': + c = OR; + break; + case '.': + c = ANY; + break; + case '(': + c = LBRA; + break; + case ')': + c = RBRA; + break; + case '^': + c = BOL; + break; + case '$': + c = EOL; + break; + case '[': + c = CCLASS; + bldcclass(); + break; + } + return c; +} + +int +nextrec(void) +{ + if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0)) + regerror("malformed `[]'"); + if(exprp[0] == '\\'){ + exprp++; + if(*exprp=='n'){ + exprp++; + return '\n'; + } + return *exprp++|0x10000; + } + return *exprp++; +} + +void +bldcclass(void) +{ + int c1, c2, n, na; + Rune *classp; + + classp = runemalloc(DCLASS); + n = 0; + na = DCLASS; + /* we have already seen the '[' */ + if(*exprp == '^'){ + classp[n++] = '\n'; /* don't match newline in negate case */ + negateclass = TRUE; + exprp++; + }else + negateclass = FALSE; + while((c1 = nextrec()) != ']'){ + if(c1 == '-'){ + Error: + free(classp); + regerror("malformed `[]'"); + } + if(n+4 >= na){ /* 3 runes plus NUL */ + na += DCLASS; + classp = runerealloc(classp, na); + } + if(*exprp == '-'){ + exprp++; /* eat '-' */ + if((c2 = nextrec()) == ']') + goto Error; + classp[n+0] = 0xFFFF; + classp[n+1] = c1; + classp[n+2] = c2; + n += 3; + }else + classp[n++] = c1; + } + classp[n] = 0; + if(nclass == Nclass){ + Nclass += DCLASS; + class = realloc(class, Nclass*sizeof(Rune*)); + } + class[nclass++] = classp; +} + +int +classmatch(int classno, int c, int negate) +{ + Rune *p; + + p = class[classno]; + while(*p){ + if(*p == 0xFFFF){ + if(p[1]<=c && c<=p[2]) + return !negate; + p += 3; + }else if(*p++ == c) + return !negate; + } + return negate; +} + +/* + * Note optimization in addinst: + * *l must be pending when addinst called; if *l has been looked + * at already, the optimization is a bug. + */ +void +addinst(Ilist *l, Inst *inst, Rangeset *sep) +{ + Ilist *p; + + for(p = l; p->inst; p++){ + if(p->inst==inst){ + if((sep)->r[0].q0 < p->se.r[0].q0) + p->se= *sep; /* this would be bug */ + return; /* It's already there */ + } + } + p->inst = inst; + p->se= *sep; + (p+1)->inst = nil; +} + +int +rxnull(void) +{ + return startinst==nil || bstartinst==nil; +} + +/* either t!=nil or r!=nil, and we match the string in the appropriate place */ +int +rxexecute(Text *t, Rune *r, uint startp, uint eof, Rangeset *rp) +{ + int flag; + Inst *inst; + Ilist *tlp; + uint p; + int nnl, ntl; + int nc, c; + int wrapped; + int startchar; + + flag = 0; + p = startp; + startchar = 0; + wrapped = 0; + nnl = 0; + if(startinst->type<OPERATOR) + startchar = startinst->type; + list[0][0].inst = list[1][0].inst = nil; + sel.r[0].q0 = -1; + if(t != nil) + nc = t->file->b.nc; + else + nc = runestrlen(r); + /* Execute machine once for each character */ + for(;;p++){ + doloop: + if(p>=eof || p>=nc){ + switch(wrapped++){ + case 0: /* let loop run one more click */ + case 2: + break; + case 1: /* expired; wrap to beginning */ + if(sel.r[0].q0>=0 || eof!=Infinity) + goto Return; + list[0][0].inst = list[1][0].inst = nil; + p = 0; + goto doloop; + default: + goto Return; + } + c = 0; + }else{ + if(((wrapped && p>=startp) || sel.r[0].q0>0) && nnl==0) + break; + if(t != nil) + c = textreadc(t, p); + else + c = r[p]; + } + /* fast check for first char */ + if(startchar && nnl==0 && c!=startchar) + continue; + tl = list[flag]; + nl = list[flag^=1]; + nl->inst = nil; + ntl = nnl; + nnl = 0; + if(sel.r[0].q0<0 && (!wrapped || p<startp || startp==eof)){ + /* Add first instruction to this list */ + if(++ntl >= NLIST){ + Overflow: + warning(nil, "regexp list overflow\n"); + sel.r[0].q0 = -1; + goto Return; + } + sempty.r[0].q0 = p; + addinst(tl, startinst, &sempty); + } + /* Execute machine until this list is empty */ + for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */ + Switchstmt: + switch(inst->type){ + default: /* regular character */ + if(inst->type==c){ + Addinst: + if(++nnl >= NLIST) + goto Overflow; + addinst(nl, inst->u1.next, &tlp->se); + } + break; + case LBRA: + if(inst->u.subid>=0) + tlp->se.r[inst->u.subid].q0 = p; + inst = inst->u1.next; + goto Switchstmt; + case RBRA: + if(inst->u.subid>=0) + tlp->se.r[inst->u.subid].q1 = p; + inst = inst->u1.next; + goto Switchstmt; + case ANY: + if(c!='\n') + goto Addinst; + break; + case BOL: + if(p==0 || (t!=nil && textreadc(t, p-1)=='\n') || (r!=nil && r[p-1]=='\n')){ + Step: + inst = inst->u1.next; + goto Switchstmt; + } + break; + case EOL: + if(c == '\n') + goto Step; + break; + case CCLASS: + if(c>=0 && classmatch(inst->u.class, c, 0)) + goto Addinst; + break; + case NCCLASS: + if(c>=0 && classmatch(inst->u.class, c, 1)) + goto Addinst; + break; + case OR: + /* evaluate right choice later */ + if(++ntl >= NLIST) + goto Overflow; + addinst(tlp, inst->u.right, &tlp->se); + /* efficiency: advance and re-evaluate */ + inst = inst->u1.left; + goto Switchstmt; + case END: /* Match! */ + tlp->se.r[0].q1 = p; + newmatch(&tlp->se); + break; + } + } + } + Return: + *rp = sel; + return sel.r[0].q0 >= 0; +} + +void +newmatch(Rangeset *sp) +{ + if(sel.r[0].q0<0 || sp->r[0].q0<sel.r[0].q0 || + (sp->r[0].q0==sel.r[0].q0 && sp->r[0].q1>sel.r[0].q1)) + sel = *sp; +} + +int +rxbexecute(Text *t, uint startp, Rangeset *rp) +{ + int flag; + Inst *inst; + Ilist *tlp; + int p; + int nnl, ntl; + int c; + int wrapped; + int startchar; + + flag = 0; + nnl = 0; + wrapped = 0; + p = startp; + startchar = 0; + if(bstartinst->type<OPERATOR) + startchar = bstartinst->type; + list[0][0].inst = list[1][0].inst = nil; + sel.r[0].q0= -1; + /* Execute machine once for each character, including terminal NUL */ + for(;;--p){ + doloop: + if(p <= 0){ + switch(wrapped++){ + case 0: /* let loop run one more click */ + case 2: + break; + case 1: /* expired; wrap to end */ + if(sel.r[0].q0>=0) + goto Return; + list[0][0].inst = list[1][0].inst = nil; + p = t->file->b.nc; + goto doloop; + case 3: + default: + goto Return; + } + c = 0; + }else{ + if(((wrapped && p<=startp) || sel.r[0].q0>0) && nnl==0) + break; + c = textreadc(t, p-1); + } + /* fast check for first char */ + if(startchar && nnl==0 && c!=startchar) + continue; + tl = list[flag]; + nl = list[flag^=1]; + nl->inst = nil; + ntl = nnl; + nnl = 0; + if(sel.r[0].q0<0 && (!wrapped || p>startp)){ + /* Add first instruction to this list */ + if(++ntl >= NLIST){ + Overflow: + warning(nil, "regexp list overflow\n"); + sel.r[0].q0 = -1; + goto Return; + } + /* the minus is so the optimizations in addinst work */ + sempty.r[0].q0 = -p; + addinst(tl, bstartinst, &sempty); + } + /* Execute machine until this list is empty */ + for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */ + Switchstmt: + switch(inst->type){ + default: /* regular character */ + if(inst->type == c){ + Addinst: + if(++nnl >= NLIST) + goto Overflow; + addinst(nl, inst->u1.next, &tlp->se); + } + break; + case LBRA: + if(inst->u.subid>=0) + tlp->se.r[inst->u.subid].q0 = p; + inst = inst->u1.next; + goto Switchstmt; + case RBRA: + if(inst->u.subid >= 0) + tlp->se.r[inst->u.subid].q1 = p; + inst = inst->u1.next; + goto Switchstmt; + case ANY: + if(c != '\n') + goto Addinst; + break; + case BOL: + if(c=='\n' || p==0){ + Step: + inst = inst->u1.next; + goto Switchstmt; + } + break; + case EOL: + if(p<t->file->b.nc && textreadc(t, p)=='\n') + goto Step; + break; + case CCLASS: + if(c>0 && classmatch(inst->u.class, c, 0)) + goto Addinst; + break; + case NCCLASS: + if(c>0 && classmatch(inst->u.class, c, 1)) + goto Addinst; + break; + case OR: + /* evaluate right choice later */ + if(++ntl >= NLIST) + goto Overflow; + addinst(tlp, inst->u.right, &tlp->se); + /* efficiency: advance and re-evaluate */ + inst = inst->u1.left; + goto Switchstmt; + case END: /* Match! */ + tlp->se.r[0].q0 = -tlp->se.r[0].q0; /* minus sign */ + tlp->se.r[0].q1 = p; + bnewmatch(&tlp->se); + break; + } + } + } + Return: + *rp = sel; + return sel.r[0].q0 >= 0; +} + +void +bnewmatch(Rangeset *sp) +{ + int i; + + if(sel.r[0].q0<0 || sp->r[0].q0>sel.r[0].q1 || (sp->r[0].q0==sel.r[0].q1 && sp->r[0].q1<sel.r[0].q0)) + for(i = 0; i<NRange; i++){ /* note the reversal; q0<=q1 */ + sel.r[i].q0 = sp->r[i].q1; + sel.r[i].q1 = sp->r[i].q0; + } +} diff --git a/src/cmd/acme/rows.c b/src/cmd/acme/rows.c new file mode 100644 index 00000000..11014c2c --- /dev/null +++ b/src/cmd/acme/rows.c @@ -0,0 +1,731 @@ +#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 <bio.h> +#include <plumb.h> +#include "dat.h" +#include "fns.h" + +static Rune Lcolhdr[] = { + 'N', 'e', 'w', 'c', 'o', 'l', ' ', + 'K', 'i', 'l', 'l', ' ', + 'P', 'u', 't', 'a', 'l', 'l', ' ', + 'D', 'u', 'm', 'p', ' ', + 'E', 'x', 'i', 't', ' ', + 0 +}; + +void +rowinit(Row *row, Rectangle r) +{ + Rectangle r1; + Text *t; + + draw(screen, r, display->white, nil, ZP); + row->r = r; + row->col = nil; + row->ncol = 0; + r1 = r; + r1.max.y = r1.min.y + font->height; + t = &row->tag; + textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols); + t->what = Rowtag; + t->row = row; + t->w = nil; + t->col = nil; + r1.min.y = r1.max.y; + r1.max.y += Border; + draw(screen, r1, display->black, nil, ZP); + textinsert(t, 0, Lcolhdr, 29, TRUE); + textsetselect(t, t->file->b.nc, t->file->b.nc); +} + +Column* +rowadd(Row *row, Column *c, int x) +{ + Rectangle r, r1; + Column *d; + int i; + + d = nil; + r = row->r; + r.min.y = row->tag.fr.r.max.y+Border; + if(x<r.min.x && row->ncol>0){ /*steal 40% of last column by default */ + d = row->col[row->ncol-1]; + x = d->r.min.x + 3*Dx(d->r)/5; + } + /* look for column we'll land on */ + for(i=0; i<row->ncol; i++){ + d = row->col[i]; + if(x < d->r.max.x) + break; + } + if(row->ncol > 0){ + if(i < row->ncol) + i++; /* new column will go after d */ + r = d->r; + if(Dx(r) < 100) + return nil; + draw(screen, r, display->white, nil, ZP); + r1 = r; + r1.max.x = min(x, r.max.x-50); + if(Dx(r1) < 50) + r1.max.x = r1.min.x+50; + colresize(d, r1); + r1.min.x = r1.max.x; + r1.max.x = r1.min.x+Border; + draw(screen, r1, display->black, nil, ZP); + r.min.x = r1.max.x; + } + if(c == nil){ + c = emalloc(sizeof(Column)); + colinit(c, r); + incref(&reffont.ref); + }else + colresize(c, r); + c->row = row; + c->tag.row = row; + row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*)); + memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*)); + row->col[i] = c; + row->ncol++; + clearmouse(); + return c; +} + +void +rowresize(Row *row, Rectangle r) +{ + int i, dx, odx; + Rectangle r1, r2; + Column *c; + + dx = Dx(r); + odx = Dx(row->r); + row->r = r; + r1 = r; + r1.max.y = r1.min.y + font->height; + textresize(&row->tag, r1); + r1.min.y = r1.max.y; + r1.max.y += Border; + draw(screen, r1, display->black, nil, ZP); + r.min.y = r1.max.y; + r1 = r; + r1.max.x = r1.min.x; + for(i=0; i<row->ncol; i++){ + c = row->col[i]; + r1.min.x = r1.max.x; + if(i == row->ncol-1) + r1.max.x = r.max.x; + else + r1.max.x = r1.min.x+Dx(c->r)*dx/odx; + if(i > 0){ + r2 = r1; + r2.max.x = r2.min.x+Border; + draw(screen, r2, display->black, nil, ZP); + r1.min.x = r2.max.x; + } + colresize(c, r1); + } +} + +void +rowdragcol(Row *row, Column *c, int _0) +{ + Rectangle r; + int i, b, x; + Point p, op; + Column *d; + + USED(_0); + + clearmouse(); + setcursor(mousectl, &boxcursor); + b = mouse->buttons; + op = mouse->xy; + while(mouse->buttons == b) + readmouse(mousectl); + setcursor(mousectl, nil); + if(mouse->buttons){ + while(mouse->buttons) + readmouse(mousectl); + return; + } + + for(i=0; i<row->ncol; i++) + if(row->col[i] == c) + goto Found; + error("can't find column"); + + Found: + if(i == 0) + return; + p = mouse->xy; + if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5)) + return; + if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){ + /* shuffle */ + x = c->r.min.x; + rowclose(row, c, FALSE); + if(rowadd(row, c, p.x) == nil) /* whoops! */ + if(rowadd(row, c, x) == nil) /* WHOOPS! */ + if(rowadd(row, c, -1)==nil){ /* shit! */ + rowclose(row, c, TRUE); + return; + } + colmousebut(c); + return; + } + d = row->col[i-1]; + if(p.x < d->r.min.x+80+Scrollwid) + p.x = d->r.min.x+80+Scrollwid; + if(p.x > c->r.max.x-80-Scrollwid) + p.x = c->r.max.x-80-Scrollwid; + r = d->r; + r.max.x = c->r.max.x; + draw(screen, r, display->white, nil, ZP); + r.max.x = p.x; + colresize(d, r); + r = c->r; + r.min.x = p.x; + r.max.x = r.min.x; + r.max.x += Border; + draw(screen, r, display->black, nil, ZP); + r.min.x = r.max.x; + r.max.x = c->r.max.x; + colresize(c, r); + colmousebut(c); +} + +void +rowclose(Row *row, Column *c, int dofree) +{ + Rectangle r; + int i; + + for(i=0; i<row->ncol; i++) + if(row->col[i] == c) + goto Found; + error("can't find column"); + Found: + r = c->r; + if(dofree) + colcloseall(c); + memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*)); + row->ncol--; + row->col = realloc(row->col, row->ncol*sizeof(Column*)); + if(row->ncol == 0){ + draw(screen, r, display->white, nil, ZP); + return; + } + if(i == row->ncol){ /* extend last column right */ + c = row->col[i-1]; + r.min.x = c->r.min.x; + r.max.x = row->r.max.x; + }else{ /* extend next window left */ + c = row->col[i]; + r.max.x = c->r.max.x; + } + draw(screen, r, display->white, nil, ZP); + colresize(c, r); +} + +Column* +rowwhichcol(Row *row, Point p) +{ + int i; + Column *c; + + for(i=0; i<row->ncol; i++){ + c = row->col[i]; + if(ptinrect(p, c->r)) + return c; + } + return nil; +} + +Text* +rowwhich(Row *row, Point p) +{ + Column *c; + + if(ptinrect(p, row->tag.all)) + return &row->tag; + c = rowwhichcol(row, p); + if(c) + return colwhich(c, p); + return nil; +} + +Text* +rowtype(Row *row, Rune r, Point p) +{ + Window *w; + Text *t; + + clearmouse(); + qlock(&row->lk); + if(bartflag) + t = barttext; + else + t = rowwhich(row, p); + if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){ + w = t->w; + if(w == nil) + texttype(t, r); + else{ + winlock(w, 'K'); + wintype(w, t, r); + winunlock(w); + } + } + qunlock(&row->lk); + return t; +} + +int +rowclean(Row *row) +{ + int clean; + int i; + + clean = TRUE; + for(i=0; i<row->ncol; i++) + clean &= colclean(row->col[i]); + return clean; +} + +void +rowdump(Row *row, char *file) +{ + int i, j, fd, m, n, dumped; + uint q0, q1; + Biobuf *b; + char *buf, *a, *fontname; + Rune *r; + Column *c; + Window *w, *w1; + Text *t; + + if(row->ncol == 0) + return; + buf = fbufalloc(); + if(file == nil){ + if(home == nil){ + warning(nil, "can't find file for dump: $home not defined\n"); + goto Rescue; + } + sprint(buf, "%s/acme.dump", home); + file = buf; + } + fd = create(file, OWRITE, 0600); + if(fd < 0){ + warning(nil, "can't open %s: %r\n", file); + goto Rescue; + } + b = emalloc(sizeof(Biobuf)); + Binit(b, fd, OWRITE); + r = fbufalloc(); + Bprint(b, "%s\n", wdir); + Bprint(b, "%s\n", fontnames[0]); + Bprint(b, "%s\n", fontnames[1]); + for(i=0; i<row->ncol; i++){ + c = row->col[i]; + Bprint(b, "%11d", 100*(c->r.min.x-row->r.min.x)/Dx(row->r)); + if(i == row->ncol-1) + Bputc(b, '\n'); + else + Bputc(b, ' '); + } + for(i=0; i<row->ncol; i++){ + c = row->col[i]; + for(j=0; j<c->nw; j++) + c->w[j]->body.file->dumpid = 0; + } + for(i=0; i<row->ncol; i++){ + c = row->col[i]; + for(j=0; j<c->nw; j++){ + w = c->w[j]; + wincommit(w, &w->tag); + t = &w->body; + /* windows owned by others get special treatment */ + if(w->nopen[QWevent] > 0) + if(w->dumpstr == nil) + continue; + /* zeroxes of external windows are tossed */ + if(t->file->ntext > 1) + for(n=0; n<t->file->ntext; n++){ + w1 = t->file->text[n]->w; + if(w == w1) + continue; + if(w1->nopen[QWevent]) + goto Continue2; + } + fontname = ""; + if(t->reffont->f != font) + fontname = t->reffont->f->name; + if(t->file->nname) + a = runetobyte(t->file->name, t->file->nname); + else + a = emalloc(1); + if(t->file->dumpid){ + dumped = FALSE; + Bprint(b, "x%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid, + w->body.q0, w->body.q1, + 100*(w->r.min.y-c->r.min.y)/Dy(c->r), + fontname); + }else if(w->dumpstr){ + dumped = FALSE; + Bprint(b, "e%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid, + 0, 0, + 100*(w->r.min.y-c->r.min.y)/Dy(c->r), + fontname); + }else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){ + dumped = FALSE; + t->file->dumpid = w->id; + Bprint(b, "f%11d %11d %11d %11d %11d %s\n", i, w->id, + w->body.q0, w->body.q1, + 100*(w->r.min.y-c->r.min.y)/Dy(c->r), + fontname); + }else{ + dumped = TRUE; + t->file->dumpid = w->id; + Bprint(b, "F%11d %11d %11d %11d %11d %11d %s\n", i, j, + w->body.q0, w->body.q1, + 100*(w->r.min.y-c->r.min.y)/Dy(c->r), + w->body.file->b.nc, fontname); + } + free(a); + winctlprint(w, buf, 0); + Bwrite(b, buf, strlen(buf)); + m = min(RBUFSIZE, w->tag.file->b.nc); + bufread(&w->tag.file->b, 0, r, m); + n = 0; + while(n<m && r[n]!='\n') + n++; + r[n++] = '\n'; + Bprint(b, "%.*S", n, r); + if(dumped){ + q0 = 0; + q1 = t->file->b.nc; + while(q0 < q1){ + n = q1 - q0; + if(n > BUFSIZE/UTFmax) + n = BUFSIZE/UTFmax; + bufread(&t->file->b, q0, r, n); + Bprint(b, "%.*S", n, r); + q0 += n; + } + } + if(w->dumpstr){ + if(w->dumpdir) + Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr); + else + Bprint(b, "\n%s\n", w->dumpstr); + } + Continue2:; + } + } + Bterm(b); + close(fd); + free(b); + fbuffree(r); + + Rescue: + fbuffree(buf); +} + +static +char* +rdline(Biobuf *b, int *linep) +{ + char *l; + + l = Brdline(b, '\n'); + if(l) + (*linep)++; + return l; +} + +/* + * Get font names from load file so we don't load fonts we won't use + */ +void +rowloadfonts(char *file) +{ + int i; + Biobuf *b; + char *l; + + b = Bopen(file, OREAD); + if(b == nil) + return; + /* current directory */ + l = Brdline(b, '\n'); + if(l == nil) + goto Return; + /* global fonts */ + for(i=0; i<2; i++){ + l = Brdline(b, '\n'); + if(l == nil) + goto Return; + l[Blinelen(b)-1] = 0; + if(*l && strcmp(l, fontnames[i])!=0) + fontnames[i] = estrdup(l); + } + Return: + Bterm(b); +} + +void +rowload(Row *row, char *file, int initing) +{ + int i, j, line, percent, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd; + Biobuf *b, *bout; + char *buf, *l, *t, *fontname; + Rune *r, rune, *fontr; + Column *c, *c1, *c2; + uint q0, q1; + Rectangle r1, r2; + Window *w; + + buf = fbufalloc(); + if(file == nil){ + if(home == nil){ + warning(nil, "can't find file for load: $home not defined\n"); + goto Rescue1; + } + sprint(buf, "%s/acme.dump", home); + file = buf; + } + b = Bopen(file, OREAD); + if(b == nil){ + warning(nil, "can't open load file %s: %r\n", file); + goto Rescue1; + } + /* current directory */ + line = 0; + l = rdline(b, &line); + if(l == nil) + goto Rescue2; + l[Blinelen(b)-1] = 0; + if(chdir(l) < 0){ + warning(nil, "can't chdir %s\n", l); + goto Rescue2; + } + /* global fonts */ + for(i=0; i<2; i++){ + l = rdline(b, &line); + if(l == nil) + goto Rescue2; + l[Blinelen(b)-1] = 0; + if(*l && strcmp(l, fontnames[i])!=0) + rfget(i, TRUE, i==0 && initing, estrdup(l)); + } + if(initing && row->ncol==0) + rowinit(row, screen->clipr); + l = rdline(b, &line); + if(l == nil) + goto Rescue2; + j = Blinelen(b)/12; + if(j<=0 || j>10) + goto Rescue2; + for(i=0; i<j; i++){ + percent = atoi(l+i*12); + if(percent<0 || percent>=100) + goto Rescue2; + x = row->r.min.x+percent*Dx(row->r)/100; + if(i < row->ncol){ + if(i == 0) + continue; + c1 = row->col[i-1]; + c2 = row->col[i]; + r1 = c1->r; + r2 = c2->r; + r1.max.x = x; + r2.min.x = x+Border; + if(Dx(r1) < 50 || Dx(r2) < 50) + continue; + draw(screen, Rpt(r1.min, r2.max), display->white, nil, ZP); + colresize(c1, r1); + colresize(c2, r2); + r2.min.x = x; + r2.max.x = x+Border; + draw(screen, r2, display->black, nil, ZP); + } + if(i >= row->ncol) + rowadd(row, nil, x); + } + for(;;){ + l = rdline(b, &line); + if(l == nil) + break; + dumpid = 0; + switch(l[0]){ + case 'e': + if(Blinelen(b) < 1+5*12+1) + goto Rescue2; + l = rdline(b, &line); /* ctl line; ignored */ + if(l == nil) + goto Rescue2; + l = rdline(b, &line); /* directory */ + if(l == nil) + goto Rescue2; + l[Blinelen(b)-1] = 0; + if(*l == '\0'){ + if(home == nil) + r = bytetorune("./", &nr); + else{ + t = emalloc(strlen(home)+1+1); + sprint(t, "%s/", home); + r = bytetorune(t, &nr); + free(t); + } + }else + r = bytetorune(l, &nr); + l = rdline(b, &line); /* command */ + if(l == nil) + goto Rescue2; + t = emalloc(Blinelen(b)+1); + memmove(t, l, Blinelen(b)); + run(nil, t, r, nr, TRUE, nil, nil, FALSE); + /* r is freed in run() */ + continue; + case 'f': + if(Blinelen(b) < 1+5*12+1) + goto Rescue2; + fontname = l+1+5*12; + ndumped = -1; + break; + case 'F': + if(Blinelen(b) < 1+6*12+1) + goto Rescue2; + fontname = l+1+6*12; + ndumped = atoi(l+1+5*12+1); + break; + case 'x': + if(Blinelen(b) < 1+5*12+1) + goto Rescue2; + fontname = l+1+5*12; + ndumped = -1; + dumpid = atoi(l+1+1*12); + break; + default: + goto Rescue2; + } + l[Blinelen(b)-1] = 0; + fontr = nil; + nfontr = 0; + if(*fontname) + fontr = bytetorune(fontname, &nfontr); + i = atoi(l+1+0*12); + j = atoi(l+1+1*12); + q0 = atoi(l+1+2*12); + q1 = atoi(l+1+3*12); + percent = atoi(l+1+4*12); + if(i<0 || i>10) + goto Rescue2; + if(i > row->ncol) + i = row->ncol; + c = row->col[i]; + y = c->r.min.y+(percent*Dy(c->r))/100; + if(y<c->r.min.y || y>=c->r.max.y) + y = -1; + if(dumpid == 0) + w = coladd(c, nil, nil, y); + else + w = coladd(c, nil, lookid(dumpid, TRUE), y); + if(w == nil) + continue; + w->dumpid = j; + l = rdline(b, &line); + if(l == nil) + goto Rescue2; + l[Blinelen(b)-1] = 0; + r = bytetorune(l+5*12, &nr); + ns = -1; + for(n=0; n<nr; n++){ + if(r[n] == '/') + ns = n; + if(r[n] == ' ') + break; + } + if(dumpid == 0) + winsetname(w, r, n); + for(; n<nr; n++) + if(r[n] == '|') + break; + wincleartag(w); + textinsert(&w->tag, w->tag.file->b.nc, r+n+1, nr-(n+1), TRUE); + free(r); + if(ndumped >= 0){ + /* simplest thing is to put it in a file and load that */ + sprint(buf, "/tmp/d%d.%.4sacme", getpid(), getuser()); + fd = create(buf, OWRITE|ORCLOSE, 0600); + if(fd < 0){ + warning(nil, "can't create temp file: %r\n"); + goto Rescue2; + } + bout = emalloc(sizeof(Biobuf)); + Binit(bout, fd, OWRITE); + for(n=0; n<ndumped; n++){ + rune = Bgetrune(b); + if(rune == '\n') + line++; + if(rune == (Rune)Beof){ + Bterm(bout); + free(bout); + close(fd); + goto Rescue2; + } + Bputrune(bout, rune); + } + Bterm(bout); + free(bout); + textload(&w->body, 0, buf, 1); + close(fd); + w->body.file->mod = TRUE; + for(n=0; n<w->body.file->ntext; n++) + w->body.file->text[n]->w->dirty = TRUE; + winsettag(w); + }else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-') + get(&w->body, nil, nil, FALSE, XXX, nil, 0); + if(fontr){ + fontx(&w->body, nil, nil, 0, 0, fontr, nfontr); + free(fontr); + } + if(q0>w->body.file->b.nc || q1>w->body.file->b.nc || q0>q1) + q0 = q1 = 0; + textshow(&w->body, q0, q1, 1); + w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines)); + } + Bterm(b); + +Rescue1: + fbuffree(buf); + return; + +Rescue2: + warning(nil, "bad load file %s:%d\n", file, line); + Bterm(b); + goto Rescue1; +} + +void +allwindows(void (*f)(Window*, void*), void *arg) +{ + int i, j; + Column *c; + + for(i=0; i<row.ncol; i++){ + c = row.col[i]; + for(j=0; j<c->nw; j++) + (*f)(c->w[j], arg); + } +} diff --git a/src/cmd/acme/scrl.c b/src/cmd/acme/scrl.c new file mode 100644 index 00000000..77aa0f85 --- /dev/null +++ b/src/cmd/acme/scrl.c @@ -0,0 +1,165 @@ +#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 "fns.h" + +static Image *scrtmp; + +static +Rectangle +scrpos(Rectangle r, uint p0, uint p1, uint tot) +{ + Rectangle q; + int h; + + q = r; + h = q.max.y-q.min.y; + if(tot == 0) + return q; + if(tot > 1024*1024){ + tot>>=10; + p0>>=10; + p1>>=10; + } + if(p0 > 0) + q.min.y += h*p0/tot; + if(p1 < tot) + q.max.y -= h*(tot-p1)/tot; + if(q.max.y < q.min.y+2){ + if(q.min.y+2 <= r.max.y) + q.max.y = q.min.y+2; + else + q.min.y = q.max.y-2; + } + return q; +} + +void +scrlresize(void) +{ + freeimage(scrtmp); + scrtmp = allocimage(display, Rect(0, 0, 32, screen->r.max.y), screen->chan, 0, DNofill); + if(scrtmp == nil) + error("scroll alloc"); +} + +void +textscrdraw(Text *t) +{ + Rectangle r, r1, r2; + Image *b; + + if(t->w==nil || t!=&t->w->body) + return; + if(scrtmp == nil) + scrlresize(); + r = t->scrollr; + b = scrtmp; + r1 = r; + r1.min.x = 0; + r1.max.x = Dx(r); + r2 = scrpos(r1, t->org, t->org+t->fr.nchars, t->file->b.nc); + if(!eqrect(r2, t->lastsr)){ + t->lastsr = r2; + draw(b, r1, t->fr.cols[BORD], nil, ZP); + draw(b, r2, t->fr.cols[BACK], nil, ZP); + r2.min.x = r2.max.x-1; + draw(b, r2, t->fr.cols[BORD], nil, ZP); + draw(t->fr.b, r, b, nil, Pt(0, r1.min.y)); +/*flushimage(display, 1);*//*BUG?*/ + } +} + +void +scrsleep(uint dt) +{ + Timer *timer; + static Alt alts[3]; + + timer = timerstart(dt); + alts[0].c = timer->c; + alts[0].v = nil; + alts[0].op = CHANRCV; + alts[1].c = mousectl->c; + alts[1].v = &mousectl->m; + alts[1].op = CHANRCV; + alts[2].op = CHANEND; + for(;;) + switch(alt(alts)){ + case 0: + timerstop(timer); + return; + case 1: + timercancel(timer); + return; + } +} + +void +textscroll(Text *t, int but) +{ + uint p0, oldp0; + Rectangle s; + int x, y, my, h, first; + + s = insetrect(t->scrollr, 1); + h = s.max.y-s.min.y; + x = (s.min.x+s.max.x)/2; + oldp0 = ~0; + first = TRUE; + do{ + flushimage(display, 1); + if(mouse->xy.x<s.min.x || s.max.x<=mouse->xy.x){ + readmouse(mousectl); + }else{ + my = mouse->xy.y; + if(my < s.min.y) + my = s.min.y; + if(my >= s.max.y) + my = s.max.y; + if(!eqpt(mouse->xy, Pt(x, my))){ + moveto(mousectl, Pt(x, my)); + readmouse(mousectl); /* absorb event generated by moveto() */ + } + if(but == 2){ + y = my; + if(y > s.max.y-2) + y = s.max.y-2; + if(t->file->b.nc > 1024*1024) + p0 = ((t->file->b.nc>>10)*(y-s.min.y)/h)<<10; + else + p0 = t->file->b.nc*(y-s.min.y)/h; + if(oldp0 != p0) + textsetorigin(t, p0, FALSE); + oldp0 = p0; + readmouse(mousectl); + continue; + } + if(but == 1) + p0 = textbacknl(t, t->org, (my-s.min.y)/t->fr.font->height); + else + p0 = t->org+frcharofpt(&t->fr, Pt(s.max.x, my)); + if(oldp0 != p0) + textsetorigin(t, p0, TRUE); + oldp0 = p0; + /* debounce */ + if(first){ + flushimage(display, 1); + sleep(200); + nbrecv(mousectl->c, &mousectl->m); + first = FALSE; + } + scrsleep(80); + } + }while(mouse->buttons & (1<<(but-1))); + while(mouse->buttons) + readmouse(mousectl); +} diff --git a/src/cmd/acme/text.c b/src/cmd/acme/text.c new file mode 100644 index 00000000..c38773ea --- /dev/null +++ b/src/cmd/acme/text.c @@ -0,0 +1,1221 @@ +#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 "fns.h" + +Image *tagcols[NCOL]; +Image *textcols[NCOL]; + +enum{ + TABDIR = 3 /* width of tabs in directory windows */ +}; + +void +textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL]) +{ + t->file = f; + t->all = r; + t->scrollr = r; + t->scrollr.max.x = r.min.x+Scrollwid; + t->lastsr = nullrect; + r.min.x += Scrollwid+Scrollgap; + t->eq0 = ~0; + t->ncache = 0; + t->reffont = rf; + t->tabstop = maxtab; + memmove(t->fr.cols, cols, sizeof t->fr.cols); + textredraw(t, r, rf->f, screen, -1); +} + +void +textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx) +{ + int maxt; + Rectangle rr; + + frinit(&t->fr, r, f, b, t->fr.cols); + rr = t->fr.r; + rr.min.x -= Scrollwid; /* back fill to scroll bar */ + draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP); + /* use no wider than 3-space tabs in a directory */ + maxt = maxtab; + if(t->what == Body){ + if(t->w->isdir) + maxt = min(TABDIR, maxtab); + else + maxt = t->tabstop; + } + t->fr.maxtab = maxt*stringwidth(f, "0"); + if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){ + if(t->fr.maxlines > 0){ + textreset(t); + textcolumnate(t, t->w->dlp, t->w->ndl); + textshow(t, 0, 0, 1); + } + }else{ + textfill(t); + textsetselect(t, t->q0, t->q1); + } +} + +int +textresize(Text *t, Rectangle r) +{ + int odx; + + if(Dy(r) > 0) + r.max.y -= Dy(r)%t->fr.font->height; + else + r.max.y = r.min.y; + odx = Dx(t->all); + t->all = r; + t->scrollr = r; + t->scrollr.max.x = r.min.x+Scrollwid; + t->lastsr = nullrect; + r.min.x += Scrollwid+Scrollgap; + frclear(&t->fr, 0); + textredraw(t, r, t->fr.font, t->fr.b, odx); + return r.max.y; +} + +void +textclose(Text *t) +{ + free(t->cache); + frclear(&t->fr, 1); + filedeltext(t->file, t); + t->file = nil; + rfclose(t->reffont); + if(argtext == t) + argtext = nil; + if(typetext == t) + typetext = nil; + if(seltext == t) + seltext = nil; + if(mousetext == t) + mousetext = nil; + if(barttext == t) + barttext = nil; +} + +int +dircmp(const void *a, const void *b) +{ + Dirlist *da, *db; + int i, n; + + da = *(Dirlist**)a; + db = *(Dirlist**)b; + n = min(da->nr, db->nr); + i = memcmp(da->r, db->r, n*sizeof(Rune)); + if(i) + return i; + return da->nr - db->nr; +} + +void +textcolumnate(Text *t, Dirlist **dlp, int ndl) +{ + int i, j, w, colw, mint, maxt, ncol, nrow; + Dirlist *dl; + uint q1; + static Rune Lnl[] = { '\n', 0 }; + static Rune Ltab[] = { '\t', 0 }; + + if(t->file->ntext > 1) + return; + mint = stringwidth(t->fr.font, "0"); + /* go for narrower tabs if set more than 3 wide */ + t->fr.maxtab = min(maxtab, TABDIR)*mint; + maxt = t->fr.maxtab; + colw = 0; + for(i=0; i<ndl; i++){ + dl = dlp[i]; + w = dl->wid; + if(maxt-w%maxt < mint || w%maxt==0) + w += mint; + if(w % maxt) + w += maxt-(w%maxt); + if(w > colw) + colw = w; + } + if(colw == 0) + ncol = 1; + else + ncol = max(1, Dx(t->fr.r)/colw); + nrow = (ndl+ncol-1)/ncol; + + q1 = 0; + for(i=0; i<nrow; i++){ + for(j=i; j<ndl; j+=nrow){ + dl = dlp[j]; + fileinsert(t->file, q1, dl->r, dl->nr); + q1 += dl->nr; + if(j+nrow >= ndl) + break; + w = dl->wid; + if(maxt-w%maxt < mint){ + fileinsert(t->file, q1, Ltab, 1); + q1++; + w += mint; + } + do{ + fileinsert(t->file, q1, Ltab, 1); + q1++; + w += maxt-(w%maxt); + }while(w < colw); + } + fileinsert(t->file, q1, Lnl, 1); + q1++; + } +} + +uint +textload(Text *t, uint q0, char *file, int setqid) +{ + Rune *rp; + Dirlist *dl, **dlp; + int fd, i, j, n, ndl, nulls; + uint q, q1; + Dir *d, *dbuf; + char *tmp; + Text *u; + + if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body || (t->w->isdir && t->file->nname==0)) + error("text.load"); + fd = open(file, OREAD); + if(fd < 0){ + warning(nil, "can't open %s: %r\n", file); + return 0; + } + d = dirfstat(fd); + if(d == nil){ + warning(nil, "can't fstat %s: %r\n", file); + goto Rescue; + } + nulls = FALSE; + if(d->qid.type & QTDIR){ + /* this is checked in get() but it's possible the file changed underfoot */ + if(t->file->ntext > 1){ + warning(nil, "%s is a directory; can't read with multiple windows on it\n", file); + goto Rescue; + } + t->w->isdir = TRUE; + t->w->filemenu = FALSE; + if(t->file->name[t->file->nname-1] != '/'){ + rp = runemalloc(t->file->nname+1); + runemove(rp, t->file->name, t->file->nname); + rp[t->file->nname] = '/'; + winsetname(t->w, rp, t->file->nname+1); + free(rp); + } + dlp = nil; + ndl = 0; + dbuf = nil; + while((n=dirread(fd, &dbuf)) > 0){ + for(i=0; i<n; i++){ + dl = emalloc(sizeof(Dirlist)); + j = strlen(dbuf[i].name); + tmp = emalloc(j+1+1); + memmove(tmp, dbuf[i].name, j); + if(dbuf[i].qid.type & QTDIR) + tmp[j++] = '/'; + tmp[j] = '\0'; + dl->r = bytetorune(tmp, &dl->nr); + dl->wid = stringwidth(t->fr.font, tmp); + free(tmp); + ndl++; + dlp = realloc(dlp, ndl*sizeof(Dirlist*)); + dlp[ndl-1] = dl; + } + free(dbuf); + } + qsort(dlp, ndl, sizeof(Dirlist*), dircmp); + t->w->dlp = dlp; + t->w->ndl = ndl; + textcolumnate(t, dlp, ndl); + q1 = t->file->b.nc; + }else{ + t->w->isdir = FALSE; + t->w->filemenu = TRUE; + q1 = q0 + fileload(t->file, q0, fd, &nulls); + } + if(setqid){ + t->file->dev = d->dev; + t->file->mtime = d->mtime; + t->file->qidpath = d->qid.path; + } + close(fd); + rp = fbufalloc(); + for(q=q0; q<q1; q+=n){ + n = q1-q; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(&t->file->b, q, rp, n); + if(q < t->org) + t->org += n; + else if(q <= t->org+t->fr.nchars) + frinsert(&t->fr, rp, rp+n, q-t->org); + if(t->fr.lastlinefull) + break; + } + fbuffree(rp); + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + if(u->org > u->file->b.nc) /* will be 0 because of reset(), but safety first */ + u->org = 0; + textresize(u, u->all); + textbacknl(u, u->org, 0); /* go to beginning of line */ + } + textsetselect(u, q0, q0); + } + if(nulls) + warning(nil, "%s: NUL bytes elided\n", file); + free(d); + return q1-q0; + + Rescue: + close(fd); + return 0; +} + +uint +textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp) +{ + Rune *bp, *tp, *up; + int i, initial; + + if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */ + Err: + textinsert(t, q0, r, n, tofile); + *nrp = n; + return q0; + } + bp = r; + for(i=0; i<n; i++) + if(*bp++ == '\b'){ + --bp; + initial = 0; + tp = runemalloc(n); + runemove(tp, r, i); + up = tp+i; + for(; i<n; i++){ + *up = *bp++; + if(*up == '\b') + if(up == tp) + initial++; + else + --up; + else + up++; + } + if(initial){ + if(initial > q0) + initial = q0; + q0 -= initial; + textdelete(t, q0, q0+initial, tofile); + } + n = up-tp; + textinsert(t, q0, tp, n, tofile); + free(tp); + *nrp = n; + return q0; + } + goto Err; +} + +void +textinsert(Text *t, uint q0, Rune *r, uint n, int tofile) +{ + int c, i; + Text *u; + + if(tofile && t->ncache != 0) + error("text.insert"); + if(n == 0) + return; + if(tofile){ + fileinsert(t->file, q0, r, n); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + if(t->file->ntext > 1) + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + u->w->dirty = TRUE; /* always a body */ + textinsert(u, q0, r, n, FALSE); + textsetselect(u, u->q0, u->q1); + textscrdraw(u); + } + } + + } + if(q0 < t->q1) + t->q1 += n; + if(q0 < t->q0) + t->q0 += n; + if(q0 < t->org) + t->org += n; + else if(q0 <= t->org+t->fr.nchars) + frinsert(&t->fr, r, r+n, q0-t->org); + if(t->w){ + c = 'i'; + if(t->what == Body) + c = 'I'; + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r); + else + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n); + } +} + + +void +textfill(Text *t) +{ + Rune *rp; + int i, n, m, nl; + + if(t->fr.lastlinefull || t->nofill) + return; + if(t->ncache > 0){ + if(t->w != nil) + wincommit(t->w, t); + else + textcommit(t, TRUE); + } + rp = fbufalloc(); + do{ + n = t->file->b.nc-(t->org+t->fr.nchars); + if(n == 0) + break; + if(n > 2000) /* educated guess at reasonable amount */ + n = 2000; + bufread(&t->file->b, t->org+t->fr.nchars, rp, n); + /* + * it's expensive to frinsert more than we need, so + * count newlines. + */ + nl = t->fr.maxlines-t->fr.nlines; + m = 0; + for(i=0; i<n; ){ + if(rp[i++] == '\n'){ + m++; + if(m >= nl) + break; + } + } + frinsert(&t->fr, rp, rp+i, t->fr.nchars); + }while(t->fr.lastlinefull == FALSE); + fbuffree(rp); +} + +void +textdelete(Text *t, uint q0, uint q1, int tofile) +{ + uint n, p0, p1; + int i, c; + Text *u; + + if(tofile && t->ncache != 0) + error("text.delete"); + n = q1-q0; + if(n == 0) + return; + if(tofile){ + filedelete(t->file, q0, q1); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + if(t->file->ntext > 1) + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + u->w->dirty = TRUE; /* always a body */ + textdelete(u, q0, q1, FALSE); + textsetselect(u, u->q0, u->q1); + textscrdraw(u); + } + } + } + if(q0 < t->q0) + t->q0 -= min(n, t->q0-q0); + if(q0 < t->q1) + t->q1 -= min(n, t->q1-q0); + if(q1 <= t->org) + t->org -= n; + else if(q0 < t->org+t->fr.nchars){ + p1 = q1 - t->org; + if(p1 > t->fr.nchars) + p1 = t->fr.nchars; + if(q0 < t->org){ + t->org = q0; + p0 = 0; + }else + p0 = q0 - t->org; + frdelete(&t->fr, p0, p1); + textfill(t); + } + if(t->w){ + c = 'd'; + if(t->what == Body) + c = 'D'; + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1); + } +} + +void +textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1) +{ + *p0 = min(q0, t->file->b.nc); + *p1 = min(q1, t->file->b.nc); +} + +Rune +textreadc(Text *t, uint q) +{ + Rune r; + + if(t->cq0<=q && q<t->cq0+t->ncache) + r = t->cache[q-t->cq0]; + else + bufread(&t->file->b, q, &r, 1); + return r; +} + +int +textbswidth(Text *t, Rune c) +{ + uint q, eq; + Rune r; + int skipping; + + /* there is known to be at least one character to erase */ + if(c == 0x08) /* ^H: erase character */ + return 1; + q = t->q0; + skipping = TRUE; + while(q > 0){ + r = textreadc(t, q-1); + if(r == '\n'){ /* eat at most one more character */ + if(q == t->q0) /* eat the newline */ + --q; + break; + } + if(c == 0x17){ + eq = isalnum(r); + if(eq && skipping) /* found one; stop skipping */ + skipping = FALSE; + else if(!eq && !skipping) + break; + } + --q; + } + return t->q0-q; +} + +void +texttype(Text *t, Rune r) +{ + uint q0, q1; + int nnb, nb, n, i; + Text *u; + + if(t->what!=Body && r=='\n') + return; + switch(r){ + case Kdown: + case Kleft: + case Kright: + n = t->fr.maxlines/2; + q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height)); + textsetorigin(t, q0, FALSE); + return; + case Kup: + n = t->fr.maxlines/2; + q0 = textbacknl(t, t->org, n); + textsetorigin(t, q0, FALSE); + return; + } + if(t->what == Body){ + seq++; + filemark(t->file); + } + if(t->q1 > t->q0){ + if(t->ncache != 0) + error("text.type"); + cut(t, t, nil, TRUE, TRUE, nil, 0); + t->eq0 = ~0; + } + textshow(t, t->q0, t->q0, 1); + switch(r){ + case 0x1B: + if(t->eq0 != ~0) + textsetselect(t, t->eq0, t->q0); + if(t->ncache > 0){ + if(t->w != nil) + wincommit(t->w, t); + else + textcommit(t, TRUE); + } + return; + case 0x08: /* ^H: erase character */ + case 0x15: /* ^U: erase line */ + case 0x17: /* ^W: erase word */ + if(t->q0 == 0) /* nothing to erase */ + return; + nnb = textbswidth(t, r); + q1 = t->q0; + q0 = q1-nnb; + /* if selection is at beginning of window, avoid deleting invisible text */ + if(q0 < t->org){ + q0 = t->org; + nnb = q1-q0; + } + if(nnb <= 0) + return; + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + u->nofill = TRUE; + nb = nnb; + n = u->ncache; + if(n > 0){ + if(q1 != u->cq0+n) + error("text.type backspace"); + if(n > nb) + n = nb; + u->ncache -= n; + textdelete(u, q1-n, q1, FALSE); + nb -= n; + } + if(u->eq0==q1 || u->eq0==~0) + u->eq0 = q0; + if(nb && u==t) + textdelete(u, q0, q0+nb, TRUE); + if(u != t) + textsetselect(u, u->q0, u->q1); + else + textsetselect(t, q0, q0); + u->nofill = FALSE; + } + for(i=0; i<t->file->ntext; i++) + textfill(t->file->text[i]); + return; + } + /* otherwise ordinary character; just insert, typically in caches of all texts */ + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u->eq0 == ~0) + u->eq0 = t->q0; + if(u->ncache == 0) + u->cq0 = t->q0; + else if(t->q0 != u->cq0+u->ncache) + error("text.type cq1"); + textinsert(u, t->q0, &r, 1, FALSE); + if(u != t) + textsetselect(u, u->q0, u->q1); + if(u->ncache == u->ncachealloc){ + u->ncachealloc += 10; + u->cache = runerealloc(u->cache, u->ncachealloc); + } + u->cache[u->ncache++] = r; + } + textsetselect(t, t->q0+1, t->q0+1); + if(r=='\n' && t->w!=nil) + wincommit(t->w, t); +} + +void +textcommit(Text *t, int tofile) +{ + if(t->ncache == 0) + return; + if(tofile) + fileinsert(t->file, t->cq0, t->cache, t->ncache); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + t->ncache = 0; +} + +static Text *clicktext; +static uint clickmsec; +static Text *selecttext; +static uint selectq; + +/* + * called from frame library + */ +void +framescroll(Frame *f, int dl) +{ + if(f != &selecttext->fr) + error("frameselect not right frame"); + textframescroll(selecttext, dl); +} + +void +textframescroll(Text *t, int dl) +{ + uint q0; + + if(dl == 0){ + scrsleep(100); + return; + } + if(dl < 0){ + q0 = textbacknl(t, t->org, -dl); + if(selectq > t->org+t->fr.p0) + textsetselect(t, t->org+t->fr.p0, selectq); + else + textsetselect(t, selectq, t->org+t->fr.p0); + }else{ + if(t->org+t->fr.nchars == t->file->b.nc) + return; + q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height)); + if(selectq > t->org+t->fr.p1) + textsetselect(t, t->org+t->fr.p1, selectq); + else + textsetselect(t, selectq, t->org+t->fr.p1); + } + textsetorigin(t, q0, TRUE); +} + + +void +textselect(Text *t) +{ + uint q0, q1; + int b, x, y; + int state; + + selecttext = t; + /* + * To have double-clicking and chording, we double-click + * immediately if it might make sense. + */ + b = mouse->buttons; + q0 = t->q0; + q1 = t->q1; + selectq = t->org+frcharofpt(&t->fr, mouse->xy); + if(clicktext==t && mouse->msec-clickmsec<500) + if(q0==q1 && selectq==q0){ + textdoubleclick(t, &q0, &q1); + textsetselect(t, q0, q1); + flushimage(display, 1); + x = mouse->xy.x; + y = mouse->xy.y; + /* stay here until something interesting happens */ + do + readmouse(mousectl); + while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3); + mouse->xy.x = x; /* in case we're calling frselect */ + mouse->xy.y = y; + q0 = t->q0; /* may have changed */ + q1 = t->q1; + selectq = q0; + } + if(mouse->buttons == b){ + t->fr.scroll = framescroll; + frselect(&t->fr, mousectl); + /* horrible botch: while asleep, may have lost selection altogether */ + if(selectq > t->file->b.nc) + selectq = t->org + t->fr.p0; + t->fr.scroll = nil; + if(selectq < t->org) + q0 = selectq; + else + q0 = t->org + t->fr.p0; + if(selectq > t->org+t->fr.nchars) + q1 = selectq; + else + q1 = t->org+t->fr.p1; + } + if(q0 == q1){ + if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){ + textdoubleclick(t, &q0, &q1); + clicktext = nil; + }else{ + clicktext = t; + clickmsec = mouse->msec; + } + }else + clicktext = nil; + textsetselect(t, q0, q1); + flushimage(display, 1); + state = 0; /* undo when possible; +1 for cut, -1 for paste */ + while(mouse->buttons){ + mouse->msec = 0; + b = mouse->buttons; + if(b & 6){ + if(state==0 && t->what==Body){ + seq++; + filemark(t->w->body.file); + } + if(b & 2){ + if(state==-1 && t->what==Body){ + winundo(t->w, TRUE); + textsetselect(t, q0, t->q0); + state = 0; + }else if(state != 1){ + cut(t, t, nil, TRUE, TRUE, nil, 0); + state = 1; + } + }else{ + if(state==1 && t->what==Body){ + winundo(t->w, TRUE); + textsetselect(t, q0, t->q1); + state = 0; + }else if(state != -1){ + paste(t, t, nil, TRUE, FALSE, nil, 0); + state = -1; + } + } + textscrdraw(t); + clearmouse(); + } + flushimage(display, 1); + while(mouse->buttons == b) + readmouse(mousectl); + clicktext = nil; + } +} + +void +textshow(Text *t, uint q0, uint q1, int doselect) +{ + int qe; + int nl; + uint q; + + if(t->what != Body) + return; + if(t->w!=nil && t->fr.maxlines==0) + colgrow(t->col, t->w, 1); + if(doselect) + textsetselect(t, q0, q1); + qe = t->org+t->fr.nchars; + if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->b.nc+t->ncache))) + textscrdraw(t); + else{ + if(t->w->nopen[QWevent] > 0) + nl = 3*t->fr.maxlines/4; + else + nl = t->fr.maxlines/4; + q = textbacknl(t, q0, nl); + /* avoid going backwards if trying to go forwards - long lines! */ + if(!(q0>t->org && q<t->org)) + textsetorigin(t, q, TRUE); + while(q0 > t->org+t->fr.nchars) + textsetorigin(t, t->org+1, FALSE); + } +} + +static +int +region(int a, int b) +{ + if(a < b) + return -1; + if(a == b) + return 0; + return 1; +} + +void +selrestore(Frame *f, Point pt0, uint p0, uint p1) +{ + if(p1<=f->p0 || p0>=f->p1){ + /* no overlap */ + frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]); + return; + } + if(p0>=f->p0 && p1<=f->p1){ + /* entirely inside */ + frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); + return; + } + + /* they now are known to overlap */ + + /* before selection */ + if(p0 < f->p0){ + frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]); + p0 = f->p0; + pt0 = frptofchar(f, p0); + } + /* after selection */ + if(p1 > f->p1){ + frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]); + p1 = f->p1; + } + /* inside selection */ + frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); +} + +void +textsetselect(Text *t, uint q0, uint q1) +{ + int p0, p1; + + /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */ + t->q0 = q0; + t->q1 = q1; + /* compute desired p0,p1 from q0,q1 */ + p0 = q0-t->org; + p1 = q1-t->org; + if(p0 < 0) + p0 = 0; + if(p1 < 0) + p1 = 0; + if(p0 > t->fr.nchars) + p0 = t->fr.nchars; + if(p1 > t->fr.nchars) + p1 = t->fr.nchars; + if(p0==t->fr.p0 && p1==t->fr.p1) + return; + /* screen disagrees with desired selection */ + if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){ + /* no overlap or too easy to bother trying */ + frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0); + frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1); + goto Return; + } + /* overlap; avoid unnecessary painting */ + if(p0 < t->fr.p0){ + /* extend selection backwards */ + frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1); + }else if(p0 > t->fr.p0){ + /* trim first part of selection */ + frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0); + } + if(p1 > t->fr.p1){ + /* extend selection forwards */ + frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1); + }else if(p1 < t->fr.p1){ + /* trim last part of selection */ + frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0); + } + + Return: + t->fr.p0 = p0; + t->fr.p1 = p1; +} + +/* + * Release the button in less than DELAY ms and it's considered a null selection + * if the mouse hardly moved, regardless of whether it crossed a char boundary. + */ +enum { + DELAY = 2, + MINMOVE = 4, +}; + +uint +xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */ +{ + uint p0, p1, q, tmp; + ulong msec; + Point mp, pt0, pt1, qt; + int reg, b; + + mp = mc->m.xy; + b = mc->m.buttons; + msec = mc->m.msec; + + /* remove tick */ + if(f->p0 == f->p1) + frtick(f, frptofchar(f, f->p0), 0); + p0 = p1 = frcharofpt(f, mp); + pt0 = frptofchar(f, p0); + pt1 = frptofchar(f, p1); + reg = 0; + frtick(f, pt0, 1); + do{ + q = frcharofpt(f, mc->m.xy); + if(p1 != q){ + if(p0 == p1) + frtick(f, pt0, 0); + if(reg != region(q, p0)){ /* crossed starting point; reset */ + if(reg > 0) + selrestore(f, pt0, p0, p1); + else if(reg < 0) + selrestore(f, pt1, p1, p0); + p1 = p0; + pt1 = pt0; + reg = region(q, p0); + if(reg == 0) + frdrawsel0(f, pt0, p0, p1, col, display->white); + } + qt = frptofchar(f, q); + if(reg > 0){ + if(q > p1) + frdrawsel0(f, pt1, p1, q, col, display->white); + + else if(q < p1) + selrestore(f, qt, q, p1); + }else if(reg < 0){ + if(q > p1) + selrestore(f, pt1, p1, q); + else + frdrawsel0(f, qt, q, p1, col, display->white); + } + p1 = q; + pt1 = qt; + } + if(p0 == p1) + frtick(f, pt0, 1); + flushimage(f->display, 1); + readmouse(mc); + }while(mc->m.buttons == b); + if(mc->m.msec-msec < DELAY && p0!=p1 + && abs(mp.x-mc->m.xy.x)<MINMOVE + && abs(mp.y-mc->m.xy.y)<MINMOVE) { + if(reg > 0) + selrestore(f, pt0, p0, p1); + else if(reg < 0) + selrestore(f, pt1, p1, p0); + p1 = p0; + } + if(p1 < p0){ + tmp = p0; + p0 = p1; + p1 = tmp; + } + pt0 = frptofchar(f, p0); + if(p0 == p1) + frtick(f, pt0, 0); + selrestore(f, pt0, p0, p1); + /* restore tick */ + if(f->p0 == f->p1) + frtick(f, frptofchar(f, f->p0), 1); + flushimage(f->display, 1); + *p1p = p1; + return p0; +} + +int +textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask) +{ + uint p0, p1; + int buts; + + p0 = xselect(&t->fr, mousectl, high, &p1); + buts = mousectl->m.buttons; + if((buts & mask) == 0){ + *q0 = p0+t->org; + *q1 = p1+t->org; + } + + while(mousectl->m.buttons) + readmouse(mousectl); + return buts; +} + +int +textselect2(Text *t, uint *q0, uint *q1, Text **tp) +{ + int buts; + + *tp = nil; + buts = textselect23(t, q0, q1, but2col, 4); + if(buts & 4) + return 0; + if(buts & 1){ /* pick up argument */ + *tp = argtext; + return 1; + } + return 1; +} + +int +textselect3(Text *t, uint *q0, uint *q1) +{ + int h; + + h = (textselect23(t, q0, q1, but3col, 1|2) == 0); + return h; +} + +static Rune left1[] = { '{', '[', '(', '<', 0xab, 0 }; +static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 }; +static Rune left2[] = { '\n', 0 }; +static Rune left3[] = { '\'', '"', '`', 0 }; + +static +Rune *left[] = { + left1, + left2, + left3, + nil +}; +static +Rune *right[] = { + right1, + left2, + left3, + nil +}; + +void +textdoubleclick(Text *t, uint *q0, uint *q1) +{ + int c, i; + Rune *r, *l, *p; + uint q; + + for(i=0; left[i]!=nil; i++){ + q = *q0; + l = left[i]; + r = right[i]; + /* try matching character to left, looking right */ + if(q == 0) + c = '\n'; + else + c = textreadc(t, q-1); + p = runestrchr(l, c); + if(p != nil){ + if(textclickmatch(t, c, r[p-l], 1, &q)) + *q1 = q-(c!='\n'); + return; + } + /* try matching character to right, looking left */ + if(q == t->file->b.nc) + c = '\n'; + else + c = textreadc(t, q); + p = runestrchr(r, c); + if(p != nil){ + if(textclickmatch(t, c, l[p-r], -1, &q)){ + *q1 = *q0+(*q0<t->file->b.nc && c=='\n'); + *q0 = q; + if(c!='\n' || q!=0 || textreadc(t, 0)=='\n') + (*q0)++; + } + return; + } + } + /* try filling out word to right */ + while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1))) + (*q1)++; + /* try filling out word to left */ + while(*q0>0 && isalnum(textreadc(t, *q0-1))) + (*q0)--; +} + +int +textclickmatch(Text *t, int cl, int cr, int dir, uint *q) +{ + Rune c; + int nest; + + nest = 1; + for(;;){ + if(dir > 0){ + if(*q == t->file->b.nc) + break; + c = textreadc(t, *q); + (*q)++; + }else{ + if(*q == 0) + break; + (*q)--; + c = textreadc(t, *q); + } + if(c == cr){ + if(--nest==0) + return 1; + }else if(c == cl) + nest++; + } + return cl=='\n' && nest==1; +} + +uint +textbacknl(Text *t, uint p, uint n) +{ + int i, j; + + /* look for start of this line if n==0 */ + if(n==0 && p>0 && textreadc(t, p-1)!='\n') + n = 1; + i = n; + while(i-->0 && p>0){ + --p; /* it's at a newline now; back over it */ + if(p == 0) + break; + /* at 128 chars, call it a line anyway */ + for(j=128; --j>0 && p>0; p--) + if(textreadc(t, p-1)=='\n') + break; + } + return p; +} + +void +textsetorigin(Text *t, uint org, int exact) +{ + int i, a, fixup; + Rune *r; + uint n; + + if(org>0 && !exact){ + /* org is an estimate of the char posn; find a newline */ + /* don't try harder than 256 chars */ + for(i=0; i<256 && org<t->file->b.nc; i++){ + if(textreadc(t, org) == '\n'){ + org++; + break; + } + org++; + } + } + a = org-t->org; + fixup = 0; + if(a>=0 && a<t->fr.nchars){ + frdelete(&t->fr, 0, a); + fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ + } + else if(a<0 && -a<t->fr.nchars){ + n = t->org - org; + r = runemalloc(n); + bufread(&t->file->b, org, r, n); + frinsert(&t->fr, r, r+n, 0); + free(r); + }else + frdelete(&t->fr, 0, t->fr.nchars); + t->org = org; + textfill(t); + textscrdraw(t); + textsetselect(t, t->q0, t->q1); + if(fixup && t->fr.p1 > t->fr.p0) + frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1); +} + +void +textreset(Text *t) +{ + t->file->seq = 0; + t->eq0 = ~0; + /* do t->delete(0, t->nc, TRUE) without building backup stuff */ + textsetselect(t, t->org, t->org); + frdelete(&t->fr, 0, t->fr.nchars); + t->org = 0; + t->q0 = 0; + t->q1 = 0; + filereset(t->file); + bufreset(&t->file->b); +} diff --git a/src/cmd/acme/time.c b/src/cmd/acme/time.c new file mode 100644 index 00000000..b281d68d --- /dev/null +++ b/src/cmd/acme/time.c @@ -0,0 +1,121 @@ +#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 "fns.h" + +static Channel* ctimer; /* chan(Timer*)[100] */ +static Timer *timer; + +static +uint +msec(void) +{ + return nsec()/1000000; +} + +void +timerstop(Timer *t) +{ + t->next = timer; + timer = t; +} + +void +timercancel(Timer *t) +{ + t->cancel = TRUE; +} + +static +void +timerproc(void *v) +{ + int i, nt, na, dt, del; + Timer **t, *x; + uint old, new; + + USED(v); + threadsetname("timerproc"); + rfork(RFFDG); + t = nil; + na = 0; + nt = 0; + old = msec(); + for(;;){ + sleep(1); /* will sleep minimum incr */ + new = msec(); + dt = new-old; + old = new; + if(dt < 0) /* timer wrapped; go around, losing a tick */ + continue; + for(i=0; i<nt; i++){ + x = t[i]; + x->dt -= dt; + del = FALSE; + if(x->cancel){ + timerstop(x); + del = TRUE; + }else if(x->dt <= 0){ + /* + * avoid possible deadlock if client is + * now sending on ctimer + */ + if(nbsendul(x->c, 0) > 0) + del = TRUE; + } + if(del){ + memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]); + --nt; + --i; + } + } + if(nt == 0){ + x = recvp(ctimer); + gotit: + if(nt == na){ + na += 10; + t = realloc(t, na*sizeof(Timer*)); + if(t == nil) + error("timer realloc failed"); + } + t[nt++] = x; + old = msec(); + } + if(nbrecv(ctimer, &x) > 0) + goto gotit; + } +} + +void +timerinit(void) +{ + ctimer = chancreate(sizeof(Timer*), 100); + proccreate(timerproc, nil, STACK); +} + +Timer* +timerstart(int dt) +{ + Timer *t; + + t = timer; + if(t) + timer = timer->next; + else{ + t = emalloc(sizeof(Timer)); + t->c = chancreate(sizeof(int), 0); + } + t->next = nil; + t->dt = dt; + t->cancel = FALSE; + sendp(ctimer, t); + return t; +} diff --git a/src/cmd/acme/util.c b/src/cmd/acme/util.c new file mode 100644 index 00000000..8e3cfa29 --- /dev/null +++ b/src/cmd/acme/util.c @@ -0,0 +1,395 @@ +#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 "fns.h" + +static Point prevmouse; +static Window *mousew; + +void +cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls) +{ + uchar *q; + Rune *s; + int j, w; + + /* + * Always guaranteed that n bytes may be interpreted + * without worrying about partial runes. This may mean + * reading up to UTFmax-1 more bytes than n; the caller + * knows this. If n is a firm limit, the caller should + * set p[n] = 0. + */ + q = (uchar*)p; + s = r; + for(j=0; j<n; j+=w){ + if(*q < Runeself){ + w = 1; + *s = *q++; + }else{ + w = chartorune(s, (char*)q); + q += w; + } + if(*s) + s++; + else if(nulls) + *nulls = TRUE; + } + *nb = (char*)q-p; + *nr = s-r; +} + +void +error(char *s) +{ + fprint(2, "acme: %s: %r\n", s); + abort(); +} + +Window* +errorwin1(Rune *dir, int ndir, Rune **incl, int nincl) +{ + Window *w; + Rune *r; + int i, n; + static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 }; + + r = runemalloc(ndir+7); + if(n = ndir) /* assign = */ + runemove(r, dir, ndir); + runemove(r+n, Lpluserrors, 7); + n += 7; + w = lookfile(r, n); + if(w == nil){ + if(row.ncol == 0) + if(rowadd(&row, nil, -1) == nil) + error("can't create column to make error window"); + w = coladd(row.col[row.ncol-1], nil, nil, -1); + w->filemenu = FALSE; + winsetname(w, r, n); + } + free(r); + for(i=nincl; --i>=0; ){ + n = runestrlen(incl[i]); + r = runemalloc(n); + runemove(r, incl[i], n); + winaddincl(w, r, n); + } + return w; +} + +/* make new window, if necessary; return with it locked */ +Window* +errorwin(Mntdir *md, int owner, Window *e) +{ + Window *w; + + for(;;){ + if(md == nil) + w = errorwin1(nil, 0, nil, 0); + else + w = errorwin1(md->dir, md->ndir, md->incl, md->nincl); + if(w != e) + winlock(w, owner); + if(w->col != nil) + break; + /* window was deleted too fast */ + if(w != e) + winunlock(w); + } + return w; +} + +static void +printwarning(Window *ew, Mntdir *md, Rune *r) +{ + int nr, q0, owner; + Window *w; + Text *t; + + if(r == nil) + error("runevsmprint failed"); + nr = runestrlen(r); + + if(row.ncol == 0){ /* really early error */ + rowinit(&row, screen->clipr); + rowadd(&row, nil, -1); + rowadd(&row, nil, -1); + if(row.ncol == 0) + error("initializing columns in warning()"); + } + + w = errorwin(md, 'E', ew); + t = &w->body; + owner = w->owner; + if(owner == 0) + w->owner = 'E'; + wincommit(w, t); + q0 = textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr); + textshow(t, q0, q0+nr, 1); + winsettag(t->w); + textscrdraw(t); + w->owner = owner; + w->dirty = FALSE; + if(ew != w) + winunlock(w); + free(r); +} + +void +warning(Mntdir *md, char *s, ...) +{ + Rune *r; + va_list arg; + + va_start(arg, s); + r = runevsmprint(s, arg); + va_end(arg); + printwarning(nil, md, r); +} + +/* + * Warningew is like warning but avoids locking the error window + * if it's already locked by checking that ew!=error window. + */ +void +warningew(Window *ew, Mntdir *md, char *s, ...) +{ + Rune *r; + va_list arg; + + va_start(arg, s); + r = runevsmprint(s, arg); + va_end(arg); + printwarning(ew, md, r); +} + +int +runeeq(Rune *s1, uint n1, Rune *s2, uint n2) +{ + if(n1 != n2) + return FALSE; + return memcmp(s1, s2, n1*sizeof(Rune)) == 0; +} + +uint +min(uint a, uint b) +{ + if(a < b) + return a; + return b; +} + +uint +max(uint a, uint b) +{ + if(a > b) + return a; + return b; +} + +char* +runetobyte(Rune *r, int n) +{ + char *s; + + if(r == nil) + return nil; + s = emalloc(n*UTFmax+1); + setmalloctag(s, getcallerpc(&r)); + snprint(s, n*UTFmax+1, "%.*S", n, r); + return s; +} + +Rune* +bytetorune(char *s, int *ip) +{ + Rune *r; + int nb, nr; + + nb = strlen(s); + r = runemalloc(nb+1); + cvttorunes(s, nb, r, &nb, &nr, nil); + r[nr] = '\0'; + *ip = nr; + return r; +} + +int +isalnum(Rune c) +{ + /* + * Hard to get absolutely right. Use what we know about ASCII + * and assume anything above the Latin control characters is + * potentially an alphanumeric. + */ + if(c <= ' ') + return FALSE; + if(0x7F<=c && c<=0xA0) + return FALSE; + if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c)) + return FALSE; + return TRUE; +} + +int +rgetc(void *v, uint n) +{ + return ((Rune*)v)[n]; +} + +int +tgetc(void *a, uint n) +{ + Text *t; + + t = a; + if(n >= t->file->b.nc) + return 0; + return textreadc(t, n); +} + +Rune* +skipbl(Rune *r, int n, int *np) +{ + while(n>0 && *r==' ' || *r=='\t' || *r=='\n'){ + --n; + r++; + } + *np = n; + return r; +} + +Rune* +findbl(Rune *r, int n, int *np) +{ + while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){ + --n; + r++; + } + *np = n; + return r; +} + +void +savemouse(Window *w) +{ + prevmouse = mouse->xy; + mousew = w; +} + +void +restoremouse(Window *w) +{ + if(mousew!=nil && mousew==w) + moveto(mousectl, prevmouse); + mousew = nil; +} + +void +clearmouse() +{ + mousew = nil; +} + +char* +estrdup(char *s) +{ + char *t; + + t = strdup(s); + if(t == nil) + error("strdup failed"); + setmalloctag(t, getcallerpc(&s)); + return t; +} + +void* +emalloc(uint n) +{ + void *p; + + p = malloc(n); + if(p == nil){ + fprint(2, "allocating %d from %lux: %r\n", n, getcallerpc(&n)); + *(int*)0=0; + error("malloc failed"); + } + setmalloctag(p, getcallerpc(&n)); + memset(p, 0, n); + return p; +} + +void* +erealloc(void *p, uint n) +{ + p = realloc(p, n); + if(p == nil){ + fprint(2, "reallocating %d: %r\n", n); + error("realloc failed"); + } + setmalloctag(p, getcallerpc(&n)); + return p; +} + +/* + * Heuristic city. + */ +Window* +makenewwindow(Text *t) +{ + Column *c; + Window *w, *bigw, *emptyw; + Text *emptyb; + int i, y, el; + + if(activecol) + c = activecol; + else if(seltext && seltext->col) + c = seltext->col; + else if(t && t->col) + c = t->col; + else{ + if(row.ncol==0 && rowadd(&row, nil, -1)==nil) + error("can't make column"); + c = row.col[row.ncol-1]; + } + activecol = c; + if(t==nil || t->w==nil || c->nw==0) + return coladd(c, nil, nil, -1); + + /* find biggest window and biggest blank spot */ + emptyw = c->w[0]; + bigw = emptyw; + for(i=1; i<c->nw; i++){ + w = c->w[i]; + /* use >= to choose one near bottom of screen */ + if(w->body.fr.maxlines >= bigw->body.fr.maxlines) + bigw = w; + if(w->body.fr.maxlines-w->body.fr.nlines >= emptyw->body.fr.maxlines-emptyw->body.fr.nlines) + emptyw = w; + } + emptyb = &emptyw->body; + el = emptyb->fr.maxlines-emptyb->fr.nlines; + /* if empty space is big, use it */ + if(el>15 || (el>3 && el>(bigw->body.fr.maxlines-1)/2)) + y = emptyb->fr.r.min.y+emptyb->fr.nlines*font->height; + else{ + /* if this window is in column and isn't much smaller, split it */ + if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3) + bigw = t->w; + y = (bigw->r.min.y + bigw->r.max.y)/2; + } + w = coladd(c, nil, nil, y); + if(w->body.fr.maxlines < 2) + colgrow(w->col, w, 1); + return w; +} diff --git a/src/cmd/acme/wind.c b/src/cmd/acme/wind.c new file mode 100644 index 00000000..a9a1c4bf --- /dev/null +++ b/src/cmd/acme/wind.c @@ -0,0 +1,576 @@ +#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 "fns.h" + +int winid; + +void +wininit(Window *w, Window *clone, Rectangle r) +{ + Rectangle r1, br; + File *f; + Reffont *rf; + Rune *rp; + int nc; + + w->tag.w = w; + w->body.w = w; + w->id = ++winid; + incref(&w->ref); + w->ctlfid = ~0; + w->utflastqid = -1; + r1 = r; + r1.max.y = r1.min.y + font->height; + incref(&reffont.ref); + f = fileaddtext(nil, &w->tag); + textinit(&w->tag, f, r1, &reffont, tagcols); + w->tag.what = Tag; + /* tag is a copy of the contents, not a tracked image */ + if(clone){ + textdelete(&w->tag, 0, w->tag.file->b.nc, TRUE); + nc = clone->tag.file->b.nc; + rp = runemalloc(nc); + bufread(&clone->tag.file->b, 0, rp, nc); + textinsert(&w->tag, 0, rp, nc, TRUE); + free(rp); + filereset(w->tag.file); + textsetselect(&w->tag, nc, nc); + } + r1 = r; + r1.min.y += font->height + 1; + if(r1.max.y < r1.min.y) + r1.max.y = r1.min.y; + f = nil; + if(clone){ + f = clone->body.file; + w->body.org = clone->body.org; + w->isscratch = clone->isscratch; + rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name); + }else + rf = rfget(FALSE, FALSE, FALSE, nil); + f = fileaddtext(f, &w->body); + w->body.what = Body; + textinit(&w->body, f, r1, rf, textcols); + r1.min.y -= 1; + r1.max.y = r1.min.y+1; + draw(screen, r1, tagcols[BORD], nil, ZP); + textscrdraw(&w->body); + w->r = r; + w->r.max.y = w->body.fr.r.max.y; + br.min = w->tag.scrollr.min; + br.max.x = br.min.x + Dx(button->r); + br.max.y = br.min.y + Dy(button->r); + draw(screen, br, button, nil, button->r.min); + w->filemenu = TRUE; + w->maxlines = w->body.fr.maxlines; + if(clone){ + w->dirty = clone->dirty; + textsetselect(&w->body, clone->body.q0, clone->body.q1); + winsettag(w); + } +} + +int +winresize(Window *w, Rectangle r, int safe) +{ + Rectangle r1; + int y; + Image *b; + Rectangle br; + + r1 = r; + r1.max.y = r1.min.y + font->height; + y = r1.max.y; + if(!safe || !eqrect(w->tag.fr.r, r1)){ + y = textresize(&w->tag, r1); + b = button; + if(w->body.file->mod && !w->isdir && !w->isscratch) + b = modbutton; + br.min = w->tag.scrollr.min; + br.max.x = br.min.x + Dx(b->r); + br.max.y = br.min.y + Dy(b->r); + draw(screen, br, b, nil, b->r.min); + } + if(!safe || !eqrect(w->body.fr.r, r1)){ + if(y+1+font->height > r.max.y){ /* no body */ + r1.min.y = y; + r1.max.y = y; + textresize(&w->body, r1); + w->r = r; + w->r.max.y = y; + return y; + } + r1 = r; + r1.min.y = y; + r1.max.y = y + 1; + draw(screen, r1, tagcols[BORD], nil, ZP); + r1.min.y = y + 1; + r1.max.y = r.max.y; + y = textresize(&w->body, r1); + w->r = r; + w->r.max.y = y; + textscrdraw(&w->body); + } + w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines)); + return w->r.max.y; +} + +void +winlock1(Window *w, int owner) +{ + incref(&w->ref); + qlock(&w->lk); + w->owner = owner; +} + +void +winlock(Window *w, int owner) +{ + int i; + File *f; + + f = w->body.file; + for(i=0; i<f->ntext; i++) + winlock1(f->text[i]->w, owner); +} + +void +winunlock(Window *w) +{ + int i; + File *f; + + f = w->body.file; + for(i=0; i<f->ntext; i++){ + w = f->text[i]->w; + w->owner = 0; + qunlock(&w->lk); + winclose(w); + /* winclose() can change up f->text; beware */ + if(f->ntext>0 && w != f->text[i]->w) + --i; /* winclose() deleted window */ + } +} + +void +winmousebut(Window *w) +{ + moveto(mousectl, divpt(addpt(w->tag.scrollr.min, w->tag.scrollr.max), 2)); +} + +void +windirfree(Window *w) +{ + int i; + Dirlist *dl; + + if(w->isdir){ + for(i=0; i<w->ndl; i++){ + dl = w->dlp[i]; + free(dl->r); + free(dl); + } + free(w->dlp); + } + w->dlp = nil; + w->ndl = 0; +} + +void +winclose(Window *w) +{ + int i; + + if(decref(&w->ref) == 0){ + windirfree(w); + textclose(&w->tag); + textclose(&w->body); + if(activewin == w) + activewin = nil; + for(i=0; i<w->nincl; i++) + free(w->incl[i]); + free(w->incl); + free(w->events); + free(w); + } +} + +void +windelete(Window *w) +{ + Xfid *x; + + x = w->eventx; + if(x){ + w->nevents = 0; + free(w->events); + w->events = nil; + w->eventx = nil; + sendp(x->c, nil); /* wake him up */ + } +} + +void +winundo(Window *w, int isundo) +{ + Text *body; + int i; + File *f; + Window *v; + + w->utflastqid = -1; + body = &w->body; + fileundo(body->file, isundo, &body->q0, &body->q1); + textshow(body, body->q0, body->q1, 1); + f = body->file; + for(i=0; i<f->ntext; i++){ + v = f->text[i]->w; + v->dirty = (f->seq != v->putseq); + if(v != w){ + v->body.q0 = v->body.fr.p0+v->body.org; + v->body.q1 = v->body.fr.p1+v->body.org; + } + } + winsettag(w); +} + +void +winsetname(Window *w, Rune *name, int n) +{ + Text *t; + Window *v; + int i; + static Rune Lslashguide[] = { '/', 'g', 'u', 'i', 'd', 'e', 0 }; + static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 }; + t = &w->body; + if(runeeq(t->file->name, t->file->nname, name, n) == TRUE) + return; + w->isscratch = FALSE; + if(n>=6 && runeeq(Lslashguide, 6, name+(n-6), 6)) + w->isscratch = TRUE; + else if(n>=7 && runeeq(Lpluserrors, 7, name+(n-7), 7)) + w->isscratch = TRUE; + filesetname(t->file, name, n); + for(i=0; i<t->file->ntext; i++){ + v = t->file->text[i]->w; + winsettag(v); + v->isscratch = w->isscratch; + } +} + +void +wintype(Window *w, Text *t, Rune r) +{ + int i; + + texttype(t, r); + if(t->what == Body) + for(i=0; i<t->file->ntext; i++) + textscrdraw(t->file->text[i]); + winsettag(w); +} + +void +wincleartag(Window *w) +{ + int i, n; + Rune *r; + + /* w must be committed */ + n = w->tag.file->b.nc; + r = runemalloc(n); + bufread(&w->tag.file->b, 0, r, n); + for(i=0; i<n; i++) + if(r[i]==' ' || r[i]=='\t') + break; + for(; i<n; i++) + if(r[i] == '|') + break; + if(i == n) + return; + i++; + textdelete(&w->tag, i, n, TRUE); + free(r); + w->tag.file->mod = FALSE; + if(w->tag.q0 > i) + w->tag.q0 = i; + if(w->tag.q1 > i) + w->tag.q1 = i; + textsetselect(&w->tag, w->tag.q0, w->tag.q1); +} + +void +winsettag1(Window *w) +{ + int i, j, k, n, bar, dirty; + Rune *new, *old, *r; + Image *b; + uint q0, q1; + Rectangle br; + static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ', + 'S', 'n', 'a', 'r', 'f', 0 }; + static Rune Lundo[] = { ' ', 'U', 'n', 'd', 'o', 0 }; + static Rune Lredo[] = { ' ', 'R', 'e', 'd', 'o', 0 }; + static Rune Lget[] = { ' ', 'G', 'e', 't', 0 }; + static Rune Lput[] = { ' ', 'P', 'u', 't', 0 }; + static Rune Llook[] = { ' ', 'L', 'o', 'o', 'k', ' ', 0 }; + static Rune Lpipe[] = { ' ', '|', 0 }; + /* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */ + if(w->tag.ncache!=0 || w->tag.file->mod) + wincommit(w, &w->tag); /* check file name; also guarantees we can modify tag contents */ + old = runemalloc(w->tag.file->b.nc+1); + bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc); + old[w->tag.file->b.nc] = '\0'; + for(i=0; i<w->tag.file->b.nc; i++) + if(old[i]==' ' || old[i]=='\t') + break; + if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){ + textdelete(&w->tag, 0, i, TRUE); + textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE); + free(old); + old = runemalloc(w->tag.file->b.nc+1); + bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc); + old[w->tag.file->b.nc] = '\0'; + } + new = runemalloc(w->body.file->nname+100); + i = 0; + runemove(new+i, w->body.file->name, w->body.file->nname); + i += w->body.file->nname; + runemove(new+i, Ldelsnarf, 10); + i += 10; + if(w->filemenu){ + if(w->body.file->delta.nc>0 || w->body.ncache){ + runemove(new+i, Lundo, 5); + i += 5; + } + if(w->body.file->epsilon.nc > 0){ + runemove(new+i, Lredo, 5); + i += 5; + } + dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq); + if(!w->isdir && dirty){ + runemove(new+i, Lput, 4); + i += 4; + } + } + if(w->isdir){ + runemove(new+i, Lget, 4); + i += 4; + } + runemove(new+i, Lpipe, 2); + i += 2; + r = runestrchr(old, '|'); + if(r) + k = r-old+1; + else{ + k = w->tag.file->b.nc; + if(w->body.file->seq == 0){ + runemove(new+i, Llook, 6); + i += 6; + } + } + new[i] = 0; + if(runestrlen(new) != i) + fprint(2, "s '%S' len not %d\n", new, i); + assert(i==runestrlen(new)); + if(runeeq(new, i, old, k) == FALSE){ + n = k; + if(n > i) + n = i; + for(j=0; j<n; j++) + if(old[j] != new[j]) + break; + q0 = w->tag.q0; + q1 = w->tag.q1; + textdelete(&w->tag, j, k, TRUE); + textinsert(&w->tag, j, new+j, i-j, TRUE); + /* try to preserve user selection */ + r = runestrchr(old, '|'); + if(r){ + bar = r-old; + if(q0 > bar){ + bar = (runestrchr(new, '|')-new)-bar; + w->tag.q0 = q0+bar; + w->tag.q1 = q1+bar; + } + } + } + free(old); + free(new); + w->tag.file->mod = FALSE; + n = w->tag.file->b.nc+w->tag.ncache; + if(w->tag.q0 > n) + w->tag.q0 = n; + if(w->tag.q1 > n) + w->tag.q1 = n; + textsetselect(&w->tag, w->tag.q0, w->tag.q1); + b = button; + if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache)) + b = modbutton; + br.min = w->tag.scrollr.min; + br.max.x = br.min.x + Dx(b->r); + br.max.y = br.min.y + Dy(b->r); + draw(screen, br, b, nil, b->r.min); +} + +void +winsettag(Window *w) +{ + int i; + File *f; + Window *v; + + f = w->body.file; + for(i=0; i<f->ntext; i++){ + v = f->text[i]->w; + if(v->col->safe || v->body.fr.maxlines>0) + winsettag1(v); + } +} + +void +wincommit(Window *w, Text *t) +{ + Rune *r; + int i; + File *f; + + textcommit(t, TRUE); + f = t->file; + if(f->ntext > 1) + for(i=0; i<f->ntext; i++) + textcommit(f->text[i], FALSE); /* no-op for t */ + if(t->what == Body) + return; + r = runemalloc(w->tag.file->b.nc); + bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc); + for(i=0; i<w->tag.file->b.nc; i++) + if(r[i]==' ' || r[i]=='\t') + break; + if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){ + seq++; + filemark(w->body.file); + w->body.file->mod = TRUE; + w->dirty = TRUE; + winsetname(w, r, i); + winsettag(w); + } + free(r); +} + +void +winaddincl(Window *w, Rune *r, int n) +{ + char *a; + Dir *d; + Runestr rs; + + a = runetobyte(r, n); + d = dirstat(a); + if(d == nil){ + if(a[0] == '/') + goto Rescue; + rs = dirname(&w->body, r, n); + r = rs.r; + n = rs.nr; + free(a); + a = runetobyte(r, n); + d = dirstat(a); + if(d == nil) + goto Rescue; + r = runerealloc(r, n+1); + r[n] = 0; + } + free(a); + if((d->qid.type&QTDIR) == 0){ + free(d); + warning(nil, "%s: not a directory\n", a); + free(r); + return; + } + free(d); + w->nincl++; + w->incl = realloc(w->incl, w->nincl*sizeof(Rune*)); + memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*)); + w->incl[0] = runemalloc(n+1); + runemove(w->incl[0], r, n); + free(r); + return; + +Rescue: + warning(nil, "%s: %r\n", a); + free(r); + free(a); + return; +} + +int +winclean(Window *w, int conservative) /* as it stands, conservative is always TRUE */ +{ + if(w->isscratch || w->isdir) /* don't whine if it's a guide file, error window, etc. */ + return TRUE; + if(!conservative && w->nopen[QWevent]>0) + return TRUE; + if(w->dirty){ + if(w->body.file->nname) + warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name); + else{ + if(w->body.file->b.nc < 100) /* don't whine if it's too small */ + return TRUE; + warning(nil, "unnamed file modified\n"); + } + w->dirty = FALSE; + return FALSE; + } + return TRUE; +} + +void +winctlprint(Window *w, char *buf, int fonts) +{ + int n; + + n = sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->b.nc, + w->body.file->b.nc, w->isdir, w->dirty); + if(fonts) + sprint(buf+n, "%11d %s" , Dx(w->body.fr.r), w->body.reffont->f->name); +} + +void +winevent(Window *w, char *fmt, ...) +{ + int n; + char *b; + Xfid *x; + va_list arg; + + if(w->nopen[QWevent] == 0) + return; + if(w->owner == 0) + error("no window owner"); + va_start(arg, fmt); + b = vsmprint(fmt, arg); + va_end(arg); + if(b == nil) + error("vsmprint failed"); + n = strlen(b); + w->events = realloc(w->events, w->nevents+1+n); + w->events[w->nevents++] = w->owner; + memmove(w->events+w->nevents, b, n); + free(b); + w->nevents += n; + x = w->eventx; + if(x){ + w->eventx = nil; + sendp(x->c, nil); + } +} diff --git a/src/cmd/acme/xfid.c b/src/cmd/acme/xfid.c new file mode 100644 index 00000000..f397623e --- /dev/null +++ b/src/cmd/acme/xfid.c @@ -0,0 +1,1046 @@ +#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 "fns.h" + +enum +{ + Ctlsize = 5*12 +}; + +char Edel[] = "deleted window"; +char Ebadctl[] = "ill-formed control message"; +char Ebadaddr[] = "bad address syntax"; +char Eaddr[] = "address out of range"; +char Einuse[] = "already in use"; +char Ebadevent[] = "bad event syntax"; +extern char Eperm[]; + +static +void +clampaddr(Window *w) +{ + if(w->addr.q0 < 0) + w->addr.q0 = 0; + if(w->addr.q1 < 0) + w->addr.q1 = 0; + if(w->addr.q0 > w->body.file->b.nc) + w->addr.q0 = w->body.file->b.nc; + if(w->addr.q1 > w->body.file->b.nc) + w->addr.q1 = w->body.file->b.nc; +} + +void +xfidctl(void *arg) +{ + Xfid *x; + void (*f)(Xfid*); + + threadsetname("xfidctlthread"); + x = arg; + for(;;){ + f = recvp(x->c); + (*f)(x); + flushimage(display, 1); + sendp(cxfidfree, x); + } +} + +void +xfidflush(Xfid *x) +{ + Fcall fc; + int i, j; + Window *w; + Column *c; + Xfid *wx; + + /* search windows for matching tag */ + qlock(&row.lk); + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + winlock(w, 'E'); + wx = w->eventx; + if(wx!=nil && wx->fcall.tag==x->fcall.oldtag){ + w->eventx = nil; + wx->flushed = TRUE; + sendp(wx->c, nil); + winunlock(w); + goto out; + } + winunlock(w); + } + } +out: + qunlock(&row.lk); + respond(x, &fc, nil); +} + +void +xfidopen(Xfid *x) +{ + Fcall fc; + Window *w; + Text *t; + char *s; + Rune *r; + int m, n, q, q0, q1; + + w = x->f->w; + t = &w->body; + if(w){ + winlock(w, 'E'); + q = FILE(x->f->qid); + switch(q){ + case QWaddr: + if(w->nopen[q]++ == 0){ + w->addr = (Range){0,0}; + w->limit = (Range){-1,-1}; + } + break; + case QWdata: + w->nopen[q]++; + break; + case QWevent: + if(w->nopen[q]++ == 0){ + if(!w->isdir && w->col!=nil){ + w->filemenu = FALSE; + winsettag(w); + } + } + break; + case QWrdsel: + /* + * Use a temporary file. + * A pipe would be the obvious, but we can't afford the + * broken pipe notification. Using the code to read QWbody + * is n², which should probably also be fixed. Even then, + * though, we'd need to squirrel away the data in case it's + * modified during the operation, e.g. by |sort + */ + if(w->rdselfd > 0){ + winunlock(w); + respond(x, &fc, Einuse); + return; + } + w->rdselfd = tempfile(); + if(w->rdselfd < 0){ + winunlock(w); + respond(x, &fc, "can't create temp file"); + return; + } + w->nopen[q]++; + q0 = t->q0; + q1 = t->q1; + r = fbufalloc(); + s = fbufalloc(); + while(q0 < q1){ + n = q1 - q0; + if(n > BUFSIZE/UTFmax) + n = BUFSIZE/UTFmax; + bufread(&t->file->b, q0, r, n); + m = snprint(s, BUFSIZE+1, "%.*S", n, r); + if(write(w->rdselfd, s, m) != m){ + warning(nil, "can't write temp file for pipe command %r\n"); + break; + } + q0 += n; + } + fbuffree(s); + fbuffree(r); + break; + case QWwrsel: + w->nopen[q]++; + seq++; + filemark(t->file); + cut(t, t, nil, FALSE, TRUE, nil, 0); + w->wrselrange = (Range){t->q1, t->q1}; + w->nomark = TRUE; + break; + case QWeditout: + if(editing == FALSE){ + winunlock(w); + respond(x, &fc, Eperm); + return; + } + w->wrselrange = (Range){t->q1, t->q1}; + break; + } + winunlock(w); + } + fc.qid = x->f->qid; + fc.iounit = messagesize-IOHDRSZ; + x->f->open = TRUE; + respond(x, &fc, nil); +} + +void +xfidclose(Xfid *x) +{ + Fcall fc; + Window *w; + int q; + Text *t; + + w = x->f->w; + x->f->busy = FALSE; + if(x->f->open == FALSE){ + if(w != nil) + winclose(w); + respond(x, &fc, nil); + return; + } + + x->f->open = FALSE; + if(w){ + winlock(w, 'E'); + q = FILE(x->f->qid); + switch(q){ + case QWctl: + if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){ + w->ctlfid = ~0; + qunlock(&w->ctllock); + } + break; + case QWdata: + w->nomark = FALSE; + /* fall through */ + case QWaddr: + case QWevent: /* BUG: do we need to shut down Xfid? */ + if(--w->nopen[q] == 0){ + if(q == QWdata) + w->nomark = FALSE; + if(q==QWevent && !w->isdir && w->col!=nil){ + w->filemenu = TRUE; + winsettag(w); + } + if(q == QWevent){ + free(w->dumpstr); + free(w->dumpdir); + w->dumpstr = nil; + w->dumpdir = nil; + } + } + break; + case QWrdsel: + close(w->rdselfd); + w->rdselfd = 0; + break; + case QWwrsel: + w->nomark = FALSE; + t = &w->body; + /* before: only did this if !w->noscroll, but that didn't seem right in practice */ + textshow(t, min(w->wrselrange.q0, t->file->b.nc), + min(w->wrselrange.q1, t->file->b.nc), 1); + textscrdraw(t); + break; + } + winunlock(w); + winclose(w); + } + respond(x, &fc, nil); +} + +void +xfidread(Xfid *x) +{ + Fcall fc; + int n, q; + uint off; + char *b; + char buf[128]; + Window *w; + + q = FILE(x->f->qid); + w = x->f->w; + if(w == nil){ + fc.count = 0; + switch(q){ + case Qcons: + case Qlabel: + break; + case Qindex: + xfidindexread(x); + return; + default: + warning(nil, "unknown qid %d\n", q); + break; + } + respond(x, &fc, nil); + return; + } + winlock(w, 'F'); + if(w->col == nil){ + winunlock(w); + respond(x, &fc, Edel); + return; + } + off = x->fcall.offset; + switch(q){ + case QWaddr: + textcommit(&w->body, TRUE); + clampaddr(w); + sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1); + goto Readbuf; + + case QWbody: + xfidutfread(x, &w->body, w->body.file->b.nc, QWbody); + break; + + case QWctl: + winctlprint(w, buf, 1); + goto Readbuf; + + Readbuf: + n = strlen(buf); + if(off > n) + off = n; + if(off+x->fcall.count > n) + x->fcall.count = n-off; + fc.count = x->fcall.count; + fc.data = buf+off; + respond(x, &fc, nil); + break; + + case QWevent: + xfideventread(x, w); + break; + + case QWdata: + /* BUG: what should happen if q1 > q0? */ + if(w->addr.q0 > w->body.file->b.nc){ + respond(x, &fc, Eaddr); + break; + } + w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->b.nc); + w->addr.q1 = w->addr.q0; + break; + + case QWtag: + xfidutfread(x, &w->tag, w->tag.file->b.nc, QWtag); + break; + + case QWrdsel: + seek(w->rdselfd, off, 0); + n = x->fcall.count; + if(n > BUFSIZE) + n = BUFSIZE; + b = fbufalloc(); + n = read(w->rdselfd, b, n); + if(n < 0){ + respond(x, &fc, "I/O error in temp file"); + break; + } + fc.count = n; + fc.data = b; + respond(x, &fc, nil); + fbuffree(b); + break; + + default: + sprint(buf, "unknown qid %d in read", q); + respond(x, &fc, nil); + } + winunlock(w); +} + +void +xfidwrite(Xfid *x) +{ + Fcall fc; + int c, cnt, qid, q, nb, nr, eval; + char buf[64], *err; + Window *w; + Rune *r; + Range a; + Text *t; + uint q0, tq0, tq1; + + qid = FILE(x->f->qid); + w = x->f->w; + if(w){ + c = 'F'; + if(qid==QWtag || qid==QWbody) + c = 'E'; + winlock(w, c); + if(w->col == nil){ + winunlock(w); + respond(x, &fc, Edel); + return; + } + } + x->fcall.data[x->fcall.count] = 0; + switch(qid){ + case Qcons: + w = errorwin(x->f->mntdir, 'X', nil); + t=&w->body; + goto BodyTag; + + case Qlabel: + fc.count = x->fcall.count; + respond(x, &fc, nil); + break; + + case QWaddr: + x->fcall.data[x->fcall.count] = 0; + r = bytetorune(x->fcall.data, &nr); + t = &w->body; + wincommit(w, t); + eval = TRUE; + a = address(x->f->mntdir, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb); + free(r); + if(nb < nr){ + respond(x, &fc, Ebadaddr); + break; + } + if(!eval){ + respond(x, &fc, Eaddr); + break; + } + w->addr = a; + fc.count = x->fcall.count; + respond(x, &fc, nil); + break; + + case Qeditout: + case QWeditout: + r = bytetorune(x->fcall.data, &nr); + if(w) + err = edittext(w, w->wrselrange.q1, r, nr); + else + err = edittext(nil, 0, r, nr); + free(r); + if(err != nil){ + respond(x, &fc, err); + break; + } + fc.count = x->fcall.count; + respond(x, &fc, nil); + break; + + case QWbody: + case QWwrsel: + t = &w->body; + goto BodyTag; + + case QWctl: + xfidctlwrite(x, w); + break; + + case QWdata: + a = w->addr; + t = &w->body; + wincommit(w, t); + if(a.q0>t->file->b.nc || a.q1>t->file->b.nc){ + respond(x, &fc, Eaddr); + break; + } + r = runemalloc(x->fcall.count); + cvttorunes(x->fcall.data, x->fcall.count, r, &nb, &nr, nil); + if(w->nomark == FALSE){ + seq++; + filemark(t->file); + } + q0 = a.q0; + if(a.q1 > q0){ + textdelete(t, q0, a.q1, TRUE); + w->addr.q1 = q0; + } + tq0 = t->q0; + tq1 = t->q1; + textinsert(t, q0, r, nr, TRUE); + if(tq0 >= q0) + tq0 += nr; + if(tq1 >= q0) + tq1 += nr; + textsetselect(t, tq0, tq1); + if(!t->w->noscroll) + textshow(t, q0, q0+nr, 0); + textscrdraw(t); + winsettag(w); + free(r); + w->addr.q0 += nr; + w->addr.q1 = w->addr.q0; + fc.count = x->fcall.count; + respond(x, &fc, nil); + break; + + case QWevent: + xfideventwrite(x, w); + break; + + case QWtag: + t = &w->tag; + goto BodyTag; + + BodyTag: + q = x->f->nrpart; + cnt = x->fcall.count; + if(q > 0){ + memmove(x->fcall.data+q, x->fcall.data, cnt); /* there's room; see fsysproc */ + memmove(x->fcall.data, x->f->rpart, q); + cnt += q; + x->f->nrpart = 0; + } + r = runemalloc(cnt); + cvttorunes(x->fcall.data, cnt-UTFmax, r, &nb, &nr, nil); + /* approach end of buffer */ + while(fullrune(x->fcall.data+nb, cnt-nb)){ + c = nb; + nb += chartorune(&r[nr], x->fcall.data+c); + if(r[nr]) + nr++; + } + if(nb < cnt){ + memmove(x->f->rpart, x->fcall.data+nb, cnt-nb); + x->f->nrpart = cnt-nb; + } + if(nr > 0){ + wincommit(w, t); + if(qid == QWwrsel){ + q0 = w->wrselrange.q1; + if(q0 > t->file->b.nc) + q0 = t->file->b.nc; + }else + q0 = t->file->b.nc; + if(qid == QWtag) + textinsert(t, q0, r, nr, TRUE); + else{ + if(w->nomark == FALSE){ + seq++; + filemark(t->file); + } + q0 = textbsinsert(t, q0, r, nr, TRUE, &nr); + textsetselect(t, t->q0, t->q1); /* insert could leave it somewhere else */ + if(qid!=QWwrsel && !t->w->noscroll) + textshow(t, q0+nr, q0+nr, 1); + textscrdraw(t); + } + winsettag(w); + if(qid == QWwrsel) + w->wrselrange.q1 += nr; + free(r); + } + fc.count = x->fcall.count; + respond(x, &fc, nil); + break; + + default: + sprint(buf, "unknown qid %d in write", qid); + respond(x, &fc, buf); + break; + } + if(w) + winunlock(w); +} + +void +xfidctlwrite(Xfid *x, Window *w) +{ + Fcall fc; + int i, m, n, nb, nr, nulls; + Rune *r; + char *err, *p, *pp, *q, *e; + int isfbuf, scrdraw, settag; + Text *t; + + err = nil; + e = x->fcall.data+x->fcall.count; + scrdraw = FALSE; + settag = FALSE; + isfbuf = TRUE; + if(x->fcall.count < RBUFSIZE) + r = fbufalloc(); + else{ + isfbuf = FALSE; + r = emalloc(x->fcall.count*UTFmax+1); + } + x->fcall.data[x->fcall.count] = 0; + textcommit(&w->tag, TRUE); + for(n=0; n<x->fcall.count; n+=m){ + p = x->fcall.data+n; + if(strncmp(p, "lock", 4) == 0){ /* make window exclusive use */ + qlock(&w->ctllock); + w->ctlfid = x->f->fid; + m = 4; + }else + if(strncmp(p, "unlock", 6) == 0){ /* release exclusive use */ + w->ctlfid = ~0; + qunlock(&w->ctllock); + m = 6; + }else + if(strncmp(p, "clean", 5) == 0){ /* mark window 'clean', seq=0 */ + t = &w->body; + t->eq0 = ~0; + filereset(t->file); + t->file->mod = FALSE; + w->dirty = FALSE; + settag = TRUE; + m = 5; + }else + if(strncmp(p, "dirty", 5) == 0){ /* mark window 'dirty' */ + t = &w->body; + /* doesn't change sequence number, so "Put" won't appear. it shouldn't. */ + t->file->mod = TRUE; + w->dirty = TRUE; + settag = TRUE; + m = 5; + }else + if(strncmp(p, "show", 4) == 0){ /* show dot */ + t = &w->body; + textshow(t, t->q0, t->q1, 1); + m = 4; + }else + if(strncmp(p, "name ", 5) == 0){ /* set file name */ + pp = p+5; + m = 5; + q = memchr(pp, '\n', e-pp); + if(q==nil || q==pp){ + err = Ebadctl; + break; + } + *q = 0; + nulls = FALSE; + cvttorunes(pp, q-pp, r, &nb, &nr, &nulls); + if(nulls){ + err = "nulls in file name"; + break; + } + for(i=0; i<nr; i++) + if(r[i] <= ' '){ + err = "bad character in file name"; + goto out; + } +out: + seq++; + filemark(w->body.file); + winsetname(w, r, nr); + m += (q+1) - pp; + }else + if(strncmp(p, "dump ", 5) == 0){ /* set dump string */ + pp = p+5; + m = 5; + q = memchr(pp, '\n', e-pp); + if(q==nil || q==pp){ + err = Ebadctl; + break; + } + *q = 0; + nulls = FALSE; + cvttorunes(pp, q-pp, r, &nb, &nr, &nulls); + if(nulls){ + err = "nulls in dump string"; + break; + } + w->dumpstr = runetobyte(r, nr); + m += (q+1) - pp; + }else + if(strncmp(p, "dumpdir ", 8) == 0){ /* set dump directory */ + pp = p+8; + m = 8; + q = memchr(pp, '\n', e-pp); + if(q==nil || q==pp){ + err = Ebadctl; + break; + } + *q = 0; + nulls = FALSE; + cvttorunes(pp, q-pp, r, &nb, &nr, &nulls); + if(nulls){ + err = "nulls in dump directory string"; + break; + } + w->dumpdir = runetobyte(r, nr); + m += (q+1) - pp; + }else + if(strncmp(p, "delete", 6) == 0){ /* delete for sure */ + colclose(w->col, w, TRUE); + m = 6; + }else + if(strncmp(p, "del", 3) == 0){ /* delete, but check dirty */ + if(!winclean(w, TRUE)){ + err = "file dirty"; + break; + } + colclose(w->col, w, TRUE); + m = 3; + }else + if(strncmp(p, "get", 3) == 0){ /* get file */ + get(&w->body, nil, nil, FALSE, XXX, nil, 0); + m = 3; + }else + if(strncmp(p, "put", 3) == 0){ /* put file */ + put(&w->body, nil, nil, XXX, XXX, nil, 0); + m = 3; + }else + if(strncmp(p, "dot=addr", 8) == 0){ /* set dot */ + textcommit(&w->body, TRUE); + clampaddr(w); + w->body.q0 = w->addr.q0; + w->body.q1 = w->addr.q1; + textsetselect(&w->body, w->body.q0, w->body.q1); + settag = TRUE; + m = 8; + }else + if(strncmp(p, "addr=dot", 8) == 0){ /* set addr */ + w->addr.q0 = w->body.q0; + w->addr.q1 = w->body.q1; + m = 8; + }else + if(strncmp(p, "limit=addr", 10) == 0){ /* set limit */ + textcommit(&w->body, TRUE); + clampaddr(w); + w->limit.q0 = w->addr.q0; + w->limit.q1 = w->addr.q1; + m = 10; + }else + if(strncmp(p, "nomark", 6) == 0){ /* turn off automatic marking */ + w->nomark = TRUE; + m = 6; + }else + if(strncmp(p, "mark", 4) == 0){ /* mark file */ + seq++; + filemark(w->body.file); + settag = TRUE; + m = 4; + }else + if(strncmp(p, "noscroll", 8) == 0){ /* turn off automatic scrolling */ + w->noscroll = TRUE; + m = 8; + }else + if(strncmp(p, "cleartag", 8) == 0){ /* wipe tag right of bar */ + wincleartag(w); + settag = TRUE; + m = 8; + }else + if(strncmp(p, "scroll", 6) == 0){ /* turn on automatic scrolling (writes to body only) */ + w->noscroll = FALSE; + m = 6; + }else{ + err = Ebadctl; + break; + } + while(p[m] == '\n') + m++; + } + + if(isfbuf) + fbuffree(r); + else + free(r); + if(err) + n = 0; + fc.count = n; + respond(x, &fc, err); + if(settag) + winsettag(w); + if(scrdraw) + textscrdraw(&w->body); +} + +void +xfideventwrite(Xfid *x, Window *w) +{ + Fcall fc; + int m, n; + Rune *r; + char *err, *p, *q; + int isfbuf; + Text *t; + int c; + uint q0, q1; + + err = nil; + isfbuf = TRUE; + if(x->fcall.count < RBUFSIZE) + r = fbufalloc(); + else{ + isfbuf = FALSE; + r = emalloc(x->fcall.count*UTFmax+1); + } + for(n=0; n<x->fcall.count; n+=m){ + p = x->fcall.data+n; + w->owner = *p++; /* disgusting */ + c = *p++; + while(*p == ' ') + p++; + q0 = strtoul(p, &q, 10); + if(q == p) + goto Rescue; + p = q; + while(*p == ' ') + p++; + q1 = strtoul(p, &q, 10); + if(q == p) + goto Rescue; + p = q; + while(*p == ' ') + p++; + if(*p++ != '\n') + goto Rescue; + m = p-(x->fcall.data+n); + if('a'<=c && c<='z') + t = &w->tag; + else if('A'<=c && c<='Z') + t = &w->body; + else + goto Rescue; + if(q0>t->file->b.nc || q1>t->file->b.nc || q0>q1) + goto Rescue; + + qlock(&row.lk); /* just like mousethread */ + switch(c){ + case 'x': + case 'X': + execute(t, q0, q1, TRUE, nil); + break; + case 'l': + case 'L': + look3(t, q0, q1, TRUE); + break; + default: + qunlock(&row.lk); + goto Rescue; + } + qunlock(&row.lk); + + } + + Out: + if(isfbuf) + fbuffree(r); + else + free(r); + if(err) + n = 0; + fc.count = n; + respond(x, &fc, err); + return; + + Rescue: + err = Ebadevent; + goto Out; +} + +void +xfidutfread(Xfid *x, Text *t, uint q1, int qid) +{ + Fcall fc; + Window *w; + Rune *r; + char *b, *b1; + uint q, off, boff; + int m, n, nr, nb; + + w = t->w; + wincommit(w, t); + off = x->fcall.offset; + r = fbufalloc(); + b = fbufalloc(); + b1 = fbufalloc(); + n = 0; + if(qid==w->utflastqid && off>=w->utflastboff && w->utflastq<=q1){ + boff = w->utflastboff; + q = w->utflastq; + }else{ + /* BUG: stupid code: scan from beginning */ + boff = 0; + q = 0; + } + w->utflastqid = qid; + while(q<q1 && n<x->fcall.count){ + /* + * Updating here avoids partial rune problem: we're always on a + * char boundary. The cost is we will usually do one more read + * than we really need, but that's better than being n^2. + */ + w->utflastboff = boff; + w->utflastq = q; + nr = q1-q; + if(nr > BUFSIZE/UTFmax) + nr = BUFSIZE/UTFmax; + bufread(&t->file->b, q, r, nr); + nb = snprint(b, BUFSIZE+1, "%.*S", nr, r); + if(boff >= off){ + m = nb; + if(boff+m > off+x->fcall.count) + m = off+x->fcall.count - boff; + memmove(b1+n, b, m); + n += m; + }else if(boff+nb > off){ + if(n != 0) + error("bad count in utfrune"); + m = nb - (off-boff); + if(m > x->fcall.count) + m = x->fcall.count; + memmove(b1, b+(off-boff), m); + n += m; + } + boff += nb; + q += nr; + } + fbuffree(r); + fbuffree(b); + fc.count = n; + fc.data = b1; + respond(x, &fc, nil); + fbuffree(b1); +} + +int +xfidruneread(Xfid *x, Text *t, uint q0, uint q1) +{ + Fcall fc; + Window *w; + Rune *r, junk; + char *b, *b1; + uint q, boff; + int i, rw, m, n, nr, nb; + + w = t->w; + wincommit(w, t); + r = fbufalloc(); + b = fbufalloc(); + b1 = fbufalloc(); + n = 0; + q = q0; + boff = 0; + while(q<q1 && n<x->fcall.count){ + nr = q1-q; + if(nr > BUFSIZE/UTFmax) + nr = BUFSIZE/UTFmax; + bufread(&t->file->b, q, r, nr); + nb = snprint(b, BUFSIZE+1, "%.*S", nr, r); + m = nb; + if(boff+m > x->fcall.count){ + i = x->fcall.count - boff; + /* copy whole runes only */ + m = 0; + nr = 0; + while(m < i){ + rw = chartorune(&junk, b+m); + if(m+rw > i) + break; + m += rw; + nr++; + } + if(m == 0) + break; + } + memmove(b1+n, b, m); + n += m; + boff += nb; + q += nr; + } + fbuffree(r); + fbuffree(b); + fc.count = n; + fc.data = b1; + respond(x, &fc, nil); + fbuffree(b1); + return q-q0; +} + +void +xfideventread(Xfid *x, Window *w) +{ + Fcall fc; + char *b; + int i, n; + + i = 0; + x->flushed = FALSE; + while(w->nevents == 0){ + if(i){ + if(!x->flushed) + respond(x, &fc, "window shut down"); + return; + } + w->eventx = x; + winunlock(w); + recvp(x->c); + winlock(w, 'F'); + i++; + } + + n = w->nevents; + if(n > x->fcall.count) + n = x->fcall.count; + fc.count = n; + fc.data = w->events; + respond(x, &fc, nil); + b = w->events; + w->events = estrdup(w->events+n); + free(b); + w->nevents -= n; +} + +void +xfidindexread(Xfid *x) +{ + Fcall fc; + int i, j, m, n, nmax, isbuf, cnt, off; + Window *w; + char *b; + Rune *r; + Column *c; + + qlock(&row.lk); + nmax = 0; + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + nmax += Ctlsize + w->tag.file->b.nc*UTFmax + 1; + } + } + nmax++; + isbuf = (nmax<=RBUFSIZE); + if(isbuf) + b = (char*)x->buf; + else + b = emalloc(nmax); + r = fbufalloc(); + n = 0; + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + /* only show the currently active window of a set */ + if(w->body.file->curtext != &w->body) + continue; + winctlprint(w, b+n, 0); + n += Ctlsize; + m = min(RBUFSIZE, w->tag.file->b.nc); + bufread(&w->tag.file->b, 0, r, m); + m = n + snprint(b+n, nmax-n-1, "%.*S", m, r); + while(n<m && b[n]!='\n') + n++; + b[n++] = '\n'; + } + } + qunlock(&row.lk); + off = x->fcall.offset; + cnt = x->fcall.count; + if(off > n) + off = n; + if(off+cnt > n) + cnt = n-off; + fc.count = cnt; + memmove(r, b+off, cnt); + fc.data = (char*)r; + if(!isbuf) + free(b); + respond(x, &fc, nil); + fbuffree(r); +} |