diff options
Diffstat (limited to 'patch/acme-moveto-undo')
-rw-r--r-- | patch/acme-moveto-undo/acme.c | 972 | ||||
-rw-r--r-- | patch/acme-moveto-undo/acme.c.orig | 966 | ||||
-rw-r--r-- | patch/acme-moveto-undo/email | 1 | ||||
-rw-r--r-- | patch/acme-moveto-undo/files | 5 | ||||
-rw-r--r-- | patch/acme-moveto-undo/fns.h | 100 | ||||
-rw-r--r-- | patch/acme-moveto-undo/fns.h.orig | 96 | ||||
-rw-r--r-- | patch/acme-moveto-undo/mkfile | 47 | ||||
-rw-r--r-- | patch/acme-moveto-undo/mkfile.orig | 46 | ||||
-rw-r--r-- | patch/acme-moveto-undo/moveto.c | 37 | ||||
-rw-r--r-- | patch/acme-moveto-undo/notes | 0 | ||||
-rw-r--r-- | patch/acme-moveto-undo/readme | 5 | ||||
-rw-r--r-- | patch/acme-moveto-undo/text.c | 1460 | ||||
-rw-r--r-- | patch/acme-moveto-undo/text.c.orig | 1460 |
13 files changed, 5195 insertions, 0 deletions
diff --git a/patch/acme-moveto-undo/acme.c b/patch/acme-moveto-undo/acme.c new file mode 100644 index 0000000..c394e4e --- /dev/null +++ b/patch/acme-moveto-undo/acme.c @@ -0,0 +1,972 @@ +#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]; + +Command *command; + +void acmeerrorinit(void); +void readfile(Column*, char*); +int shutdown(void*, char*); + +void +derror(Display*, char *errorstr) +{ + 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 'a': + globalindent[AUTOINDENT] = TRUE; + break; + 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 'i': + globalindent[SPACESINDENT] = TRUE; + break; + case 'l': + loadfile = ARGF(); + if(loadfile == nil) + goto Usage; + break; + default: + Usage: + fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n"); + exits("usage"); + }ARGEND + + if(fontnames[0] == nil) + fontnames[0] = getenv("font"); + if(fontnames[0] == nil) + fontnames[0] = "/lib/font/bit/vga/unicode.font"; + if(access(fontnames[0], 0) < 0){ + fprint(2, "acme: can't access %s: %r\n", fontnames[0]); + exits("font open"); + } + if(fontnames[1] == nil) + fontnames[1] = fontnames[0]; + fontnames[0] = estrdup(fontnames[0]); + fontnames[1] = estrdup(fontnames[1]); + + quotefmtinstall(); + 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"); + } + d = display; + font = d->defaultfont; + + reffont.f = font; + reffonts[0] = &reffont; + incref(&reffont); /* one to hold up 'font' variable */ + incref(&reffont); /* 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); + cwarn = chancreate(sizeof(void*), 1); + if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){ + fprint(2, "acme: can't create initial channels: %r\n"); + threadexitsall("channels"); + } + + mousectl = initmouse(nil, screen); + if(mousectl == nil){ + fprint(2, "acme: can't initialize mouse: %r\n"); + threadexitsall("mouse"); + } + mouse = mousectl; + keyboardctl = initkeyboard(nil); + if(keyboardctl == nil){ + fprint(2, "acme: can't initialize keyboard: %r\n"); + threadexitsall("keyboard"); + } + mainpid = getpid(); + plumbeditfd = plumbopen("edit", OREAD|OCEXEC); + if(plumbeditfd >= 0){ + 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)){ + 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->nc, w->tag.file->nc); + xfidlog(w, "new"); +} + +char *oknotes[] ={ + "delete", + "hangup", + "kill", + "exit", + nil +}; + +int dumping; + +int +shutdown(void*, char *msg) +{ + int i; + + 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"); + remove(acmeerrorfile); +} + +static int errorfd; + +void +acmeerrorproc(void *) +{ + char *buf, *s; + int n; + + threadsetname("acmeerrorproc"); + buf = emalloc(8192+1); + while((n=read(errorfd, buf, 8192)) >= 0){ + buf[n] = '\0'; + s = estrdup(buf); + sendp(cerr, s); + } + free(buf); +} + +void +acmeerrorinit(void) +{ + int fd, pfd[2]; + char buf[64]; + + if(pipe(pfd) < 0) + error("can't create pipe"); + 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); + if(errorfd < 0) + error("can't re-open acmeerror file"); + close(pfd[1]); + close(pfd[0]); + proccreate(acmeerrorproc, nil, STACK); +} + +void +plumbproc(void *) +{ + Plumbmsg *m; + + threadsetname("plumbproc"); + for(;;){ + m = plumbrecv(plumbeditfd); + if(m == nil) + threadexits(nil); + sendp(cplumb, m); + } +} + +void +keyboardthread(void *) +{ + Rune r; + Timer *timer; + Text *t; + enum { KTimer, KKey, NKALT }; + static Alt alts[NKALT+1]; + + 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: + switch(r){ + case KF|1: + undomoveto(mousectl); + goto skip; + } + 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; + } + skip: + if(nbrecv(keyboardctl->c, &r) > 0) + goto casekeyboard; + flushimage(display, 1); + break; + } + } +} + +void +mousethread(void *) +{ + Text *t, *argt; + int but; + uint q0, q1; + Window *w; + Plumbmsg *pm; + Mouse m; + char *act; + enum { MResize, MMouse, MPlumb, MWarnings, NMALT }; + static Alt alts[NMALT+1]; + + threadsetname("mousethread"); + alts[MResize].c = mousectl->resizec; + alts[MResize].v = nil; + alts[MResize].op = CHANRCV; + alts[MMouse].c = mousectl->c; + alts[MMouse].v = &mousectl->Mouse; + alts[MMouse].op = CHANRCV; + alts[MPlumb].c = cplumb; + alts[MPlumb].v = ± + alts[MPlumb].op = CHANRCV; + alts[MWarnings].c = cwarn; + alts[MWarnings].v = nil; + alts[MWarnings].op = CHANRCV; + if(cplumb == nil) + alts[MPlumb].op = CHANNOP; + alts[NMALT].op = CHANEND; + + for(;;){ + qlock(&row); + flushwarnings(); + qunlock(&row); + flushimage(display, 1); + switch(alt(alts)){ + case MResize: + if(getwindow(display, Refnone) < 0) + error("attach to window"); + scrlresize(); + rowresize(&row, screen->clipr); + 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); + } + plumbfree(pm); + break; + case MWarnings: + 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->Mouse; + qlock(&row); + t = rowwhich(&row, m.xy); + + if((t!=mousetext && t!=nil && t->w!=nil) && + (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) { + xfidlog(t->w, "focus"); + } + + 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; + } + /* scroll buttons, wheels, etc. */ + if(t->what==Body && w != nil && (m.buttons & (8|16))){ + if(m.buttons & 8) + but = Kscrolloneup; + else + but = Kscrollonedown; + winlock(w, 'M'); + t->eq0 = ~0; + texttype(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: + qunlock(&row); + 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 *) +{ + 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]; + + 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); + warning(nil, "%s", err); + free(err); + flushimage(display, 1); + qunlock(&row); + 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); + 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\n", w->msg); + flushimage(display, 1); + } + qunlock(&row); + 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); + t = &row.tag; + textcommit(t, TRUE); + textinsert(t, 0, c->name, c->nname, TRUE); + textsetselect(t, 0, 0); + flushimage(display, 1); + qunlock(&row); + break; + } + } +} + +void +xfidallocthread(void*) +{ + Xfid *xfree, *x; + enum { Alloc, Free, N }; + static Alt alts[N+1]; + + 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*) +{ + Window *w; + + threadsetname("newwindowthread"); + + for(;;){ + /* only fsysproc is talking to us, so synchronization is trivial */ + recvp(cnewwindow); + w = makenewwindow(nil); + winsettag(w); + xfidlog(w, "new"); + 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 = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*)); + fontcache[nfontcache++] = r; + } + Found: + if(save){ + incref(r); + if(reffonts[fix]) + rfclose(reffonts[fix]); + reffonts[fix] = r; + if(name != fontnames[fix]){ + free(fontnames[fix]); + fontnames[fix] = estrdup(name); + } + } + if(setfont){ + reffont.f = r->f; + incref(r); + rfclose(reffonts[0]); + font = r->f; + reffonts[0] = r; + incref(r); + iconinit(); + } + incref(r); + return r; +} + +void +rfclose(Reffont *r) +{ + int i; + + if(decref(r) == 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 +putsnarf(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 +getsnarf() +{ + 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/patch/acme-moveto-undo/acme.c.orig b/patch/acme-moveto-undo/acme.c.orig new file mode 100644 index 0000000..e874d35 --- /dev/null +++ b/patch/acme-moveto-undo/acme.c.orig @@ -0,0 +1,966 @@ +#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]; + +Command *command; + +void acmeerrorinit(void); +void readfile(Column*, char*); +int shutdown(void*, char*); + +void +derror(Display*, char *errorstr) +{ + 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 'a': + globalindent[AUTOINDENT] = TRUE; + break; + 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 'i': + globalindent[SPACESINDENT] = TRUE; + break; + case 'l': + loadfile = ARGF(); + if(loadfile == nil) + goto Usage; + break; + default: + Usage: + fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n"); + exits("usage"); + }ARGEND + + if(fontnames[0] == nil) + fontnames[0] = getenv("font"); + if(fontnames[0] == nil) + fontnames[0] = "/lib/font/bit/vga/unicode.font"; + if(access(fontnames[0], 0) < 0){ + fprint(2, "acme: can't access %s: %r\n", fontnames[0]); + exits("font open"); + } + if(fontnames[1] == nil) + fontnames[1] = fontnames[0]; + fontnames[0] = estrdup(fontnames[0]); + fontnames[1] = estrdup(fontnames[1]); + + quotefmtinstall(); + 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"); + } + d = display; + font = d->defaultfont; + + reffont.f = font; + reffonts[0] = &reffont; + incref(&reffont); /* one to hold up 'font' variable */ + incref(&reffont); /* 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); + cwarn = chancreate(sizeof(void*), 1); + if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){ + fprint(2, "acme: can't create initial channels: %r\n"); + threadexitsall("channels"); + } + + mousectl = initmouse(nil, screen); + if(mousectl == nil){ + fprint(2, "acme: can't initialize mouse: %r\n"); + threadexitsall("mouse"); + } + mouse = mousectl; + keyboardctl = initkeyboard(nil); + if(keyboardctl == nil){ + fprint(2, "acme: can't initialize keyboard: %r\n"); + threadexitsall("keyboard"); + } + mainpid = getpid(); + plumbeditfd = plumbopen("edit", OREAD|OCEXEC); + if(plumbeditfd >= 0){ + 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)){ + 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->nc, w->tag.file->nc); + xfidlog(w, "new"); +} + +char *oknotes[] ={ + "delete", + "hangup", + "kill", + "exit", + nil +}; + +int dumping; + +int +shutdown(void*, char *msg) +{ + int i; + + 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"); + remove(acmeerrorfile); +} + +static int errorfd; + +void +acmeerrorproc(void *) +{ + char *buf, *s; + int n; + + threadsetname("acmeerrorproc"); + buf = emalloc(8192+1); + while((n=read(errorfd, buf, 8192)) >= 0){ + buf[n] = '\0'; + s = estrdup(buf); + sendp(cerr, s); + } + free(buf); +} + +void +acmeerrorinit(void) +{ + int fd, pfd[2]; + char buf[64]; + + if(pipe(pfd) < 0) + error("can't create pipe"); + 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); + if(errorfd < 0) + error("can't re-open acmeerror file"); + close(pfd[1]); + close(pfd[0]); + proccreate(acmeerrorproc, nil, STACK); +} + +void +plumbproc(void *) +{ + Plumbmsg *m; + + threadsetname("plumbproc"); + for(;;){ + m = plumbrecv(plumbeditfd); + if(m == nil) + threadexits(nil); + sendp(cplumb, m); + } +} + +void +keyboardthread(void *) +{ + Rune r; + Timer *timer; + Text *t; + enum { KTimer, KKey, NKALT }; + static Alt alts[NKALT+1]; + + 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 *) +{ + Text *t, *argt; + int but; + uint q0, q1; + Window *w; + Plumbmsg *pm; + Mouse m; + char *act; + enum { MResize, MMouse, MPlumb, MWarnings, NMALT }; + static Alt alts[NMALT+1]; + + threadsetname("mousethread"); + alts[MResize].c = mousectl->resizec; + alts[MResize].v = nil; + alts[MResize].op = CHANRCV; + alts[MMouse].c = mousectl->c; + alts[MMouse].v = &mousectl->Mouse; + alts[MMouse].op = CHANRCV; + alts[MPlumb].c = cplumb; + alts[MPlumb].v = ± + alts[MPlumb].op = CHANRCV; + alts[MWarnings].c = cwarn; + alts[MWarnings].v = nil; + alts[MWarnings].op = CHANRCV; + if(cplumb == nil) + alts[MPlumb].op = CHANNOP; + alts[NMALT].op = CHANEND; + + for(;;){ + qlock(&row); + flushwarnings(); + qunlock(&row); + flushimage(display, 1); + switch(alt(alts)){ + case MResize: + if(getwindow(display, Refnone) < 0) + error("attach to window"); + scrlresize(); + rowresize(&row, screen->clipr); + 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); + } + plumbfree(pm); + break; + case MWarnings: + 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->Mouse; + qlock(&row); + t = rowwhich(&row, m.xy); + + if((t!=mousetext && t!=nil && t->w!=nil) && + (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) { + xfidlog(t->w, "focus"); + } + + 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; + } + /* scroll buttons, wheels, etc. */ + if(t->what==Body && w != nil && (m.buttons & (8|16))){ + if(m.buttons & 8) + but = Kscrolloneup; + else + but = Kscrollonedown; + winlock(w, 'M'); + t->eq0 = ~0; + texttype(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: + qunlock(&row); + 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 *) +{ + 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]; + + 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); + warning(nil, "%s", err); + free(err); + flushimage(display, 1); + qunlock(&row); + 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); + 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\n", w->msg); + flushimage(display, 1); + } + qunlock(&row); + 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); + t = &row.tag; + textcommit(t, TRUE); + textinsert(t, 0, c->name, c->nname, TRUE); + textsetselect(t, 0, 0); + flushimage(display, 1); + qunlock(&row); + break; + } + } +} + +void +xfidallocthread(void*) +{ + Xfid *xfree, *x; + enum { Alloc, Free, N }; + static Alt alts[N+1]; + + 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*) +{ + Window *w; + + threadsetname("newwindowthread"); + + for(;;){ + /* only fsysproc is talking to us, so synchronization is trivial */ + recvp(cnewwindow); + w = makenewwindow(nil); + winsettag(w); + xfidlog(w, "new"); + 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 = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*)); + fontcache[nfontcache++] = r; + } + Found: + if(save){ + incref(r); + if(reffonts[fix]) + rfclose(reffonts[fix]); + reffonts[fix] = r; + if(name != fontnames[fix]){ + free(fontnames[fix]); + fontnames[fix] = estrdup(name); + } + } + if(setfont){ + reffont.f = r->f; + incref(r); + rfclose(reffonts[0]); + font = r->f; + reffonts[0] = r; + incref(r); + iconinit(); + } + incref(r); + return r; +} + +void +rfclose(Reffont *r) +{ + int i; + + if(decref(r) == 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 +putsnarf(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 +getsnarf() +{ + 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/patch/acme-moveto-undo/email b/patch/acme-moveto-undo/email new file mode 100644 index 0000000..191feb6 --- /dev/null +++ b/patch/acme-moveto-undo/email @@ -0,0 +1 @@ +john@ankarstrom.se diff --git a/patch/acme-moveto-undo/files b/patch/acme-moveto-undo/files new file mode 100644 index 0000000..610da9a --- /dev/null +++ b/patch/acme-moveto-undo/files @@ -0,0 +1,5 @@ +/sys/src/cmd/acme/acme.c acme.c +/sys/src/cmd/acme/fns.h fns.h +/sys/src/cmd/acme/moveto.c moveto.c +/sys/src/cmd/acme/text.c text.c +/sys/src/cmd/acme/mkfile mkfile diff --git a/patch/acme-moveto-undo/fns.h b/patch/acme-moveto-undo/fns.h new file mode 100644 index 0000000..4ba641b --- /dev/null +++ b/patch/acme-moveto-undo/fns.h @@ -0,0 +1,100 @@ +#pragma varargck argpos warning 2 + +void warning(Mntdir*, char*, ...); + +#define fbufalloc() emalloc(BUFSIZE) +#define fbuffree(x) free(x) + +#define moveto(mc, pt) recmoveto(mc, pt) +void recmoveto(Mousectl *mc, Point pt); +void undomoveto(Mousectl *mc); + +void plumblook(Plumbmsg*m); +void plumbshow(Plumbmsg*m); +void putsnarf(void); +void getsnarf(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); +char* getname(Text*, Text*, Rune*, int, int); +void scrsleep(uint); +void savemouse(Window*); +int restoremouse(Window*); +void clearmouse(void); +void allwindows(void(*)(Window*, void*), void*); +uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*); +void movetodel(Window*); + +Window* errorwin(Mntdir*, int); +Window* errorwinforwin(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 isspace(Rune); +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 fsysincid(Mntdir*); +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); +void flushwarnings(void); +long nlcount(Text*, long, long, long*); +long nlcounttopos(Text*, long, long, long); + +#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/patch/acme-moveto-undo/fns.h.orig b/patch/acme-moveto-undo/fns.h.orig new file mode 100644 index 0000000..02cc06d --- /dev/null +++ b/patch/acme-moveto-undo/fns.h.orig @@ -0,0 +1,96 @@ +#pragma varargck argpos warning 2 + +void warning(Mntdir*, char*, ...); + +#define fbufalloc() emalloc(BUFSIZE) +#define fbuffree(x) free(x) + +void plumblook(Plumbmsg*m); +void plumbshow(Plumbmsg*m); +void putsnarf(void); +void getsnarf(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); +char* getname(Text*, Text*, Rune*, int, int); +void scrsleep(uint); +void savemouse(Window*); +int restoremouse(Window*); +void clearmouse(void); +void allwindows(void(*)(Window*, void*), void*); +uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*); +void movetodel(Window*); + +Window* errorwin(Mntdir*, int); +Window* errorwinforwin(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 isspace(Rune); +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 fsysincid(Mntdir*); +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); +void flushwarnings(void); +long nlcount(Text*, long, long, long*); +long nlcounttopos(Text*, long, long, long); + +#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/patch/acme-moveto-undo/mkfile b/patch/acme-moveto-undo/mkfile new file mode 100644 index 0000000..f5048ca --- /dev/null +++ b/patch/acme-moveto-undo/mkfile @@ -0,0 +1,47 @@ +</$objtype/mkfile +BIN=/$objtype/bin + +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\ + logf.$O\ + look.$O\ + moveto.$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}\ + +</sys/src/cmd/mkone + +$O.out: /$objtype/lib/libframe.a /$objtype/lib/libdraw.a /$objtype/lib/libthread.a + +edit.$O ecmd.$O elog.$O: edit.h + +syms:V: + $CC -a acme.c > syms + for(i in ????.c) $CC -aa $i >> syms diff --git a/patch/acme-moveto-undo/mkfile.orig b/patch/acme-moveto-undo/mkfile.orig new file mode 100644 index 0000000..22ad647 --- /dev/null +++ b/patch/acme-moveto-undo/mkfile.orig @@ -0,0 +1,46 @@ +</$objtype/mkfile +BIN=/$objtype/bin + +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\ + logf.$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}\ + +</sys/src/cmd/mkone + +$O.out: /$objtype/lib/libframe.a /$objtype/lib/libdraw.a /$objtype/lib/libthread.a + +edit.$O ecmd.$O elog.$O: edit.h + +syms:V: + $CC -a acme.c > syms + for(i in ????.c) $CC -aa $i >> syms diff --git a/patch/acme-moveto-undo/moveto.c b/patch/acme-moveto-undo/moveto.c new file mode 100644 index 0000000..dae0a6b --- /dev/null +++ b/patch/acme-moveto-undo/moveto.c @@ -0,0 +1,37 @@ +#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" + +static Point newmouse; +static Point oldmouse; + +/* call moveto, but record old and new mouse positions */ +void +recmoveto(Mousectl *mc, Point pt){ + static vlong prevtime = 0; + vlong curtime = nsec(); + if(curtime-prevtime>500000000){ /* 500ms */ + newmouse = pt; + oldmouse = mouse->xy; + } + prevtime = curtime; + moveto(mc, pt); +} + +void +undomoveto(Mousectl *mc){ + Point tmp; + moveto(mc, oldmouse); + tmp = newmouse; + newmouse = oldmouse; + oldmouse = tmp; +} + diff --git a/patch/acme-moveto-undo/notes b/patch/acme-moveto-undo/notes new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/patch/acme-moveto-undo/notes diff --git a/patch/acme-moveto-undo/readme b/patch/acme-moveto-undo/readme new file mode 100644 index 0000000..770cb83 --- /dev/null +++ b/patch/acme-moveto-undo/readme @@ -0,0 +1,5 @@ +Press F1 to undo automatic mouse movement + +Many Acme operations call moveto, moving the mouse cursor +without the user's involvement. This patch allows the user to +undo calls to moveto by pressing F1. Another F1 undoes the undo. diff --git a/patch/acme-moveto-undo/text.c b/patch/acme-moveto-undo/text.c new file mode 100644 index 0000000..484ec3d --- /dev/null +++ b/patch/acme-moveto-undo/text.c @@ -0,0 +1,1460 @@ +#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 <complete.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->Frame.cols, cols, sizeof t->Frame.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, r, f, b, t->Frame.cols); + rr = t->r; + rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */ + draw(t->b, rr, t->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->maxtab = maxt*stringwidth(f, "0"); + if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){ + if(t->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->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, 0); + textredraw(t, r, t->font, t->b, odx); + return r.max.y; +} + +void +textclose(Text *t) +{ + free(t->cache); + frclear(t, 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(void *a, 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; + + if(t->file->ntext > 1) + return; + mint = stringwidth(t->font, "0"); + /* go for narrower tabs if set more than 3 wide */ + t->maxtab = min(maxtab, TABDIR)*mint; + maxt = t->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->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, L"\t", 1); + q1++; + w += mint; + } + do{ + fileinsert(t->file, q1, L"\t", 1); + q1++; + w += maxt-(w%maxt); + }while(w < colw); + } + fileinsert(t->file, q1, L"\n", 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->nc || t->w==nil || t!=&t->w->body) + error("text.load"); + if(t->w->isdir && t->file->nname==0){ + warning(nil, "empty directory name\n"); + return 0; + } + 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->nname > 0 && 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->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->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, q, rp, n); + if(q < t->org) + t->org += n; + else if(q <= t->org+t->nchars) + frinsert(t, rp, rp+n, q-t->org); + if(t->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->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->nchars) + frinsert(t, 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 +typecommit(Text *t) +{ + if(t->w != nil) + wincommit(t->w, t); + else + textcommit(t, TRUE); +} + +void +textfill(Text *t) +{ + Rune *rp; + int i, n, m, nl; + + if(t->lastlinefull || t->nofill) + return; + if(t->ncache > 0) + typecommit(t); + rp = fbufalloc(); + do{ + n = t->file->nc-(t->org+t->nchars); + if(n == 0) + break; + if(n > 2000) /* educated guess at reasonable amount */ + n = 2000; + bufread(t->file, t->org+t->nchars, rp, n); + /* + * it's expensive to frinsert more than we need, so + * count newlines. + */ + nl = t->maxlines-t->nlines; + m = 0; + for(i=0; i<n; ){ + if(rp[i++] == '\n'){ + m++; + if(m >= nl) + break; + } + } + frinsert(t, rp, rp+i, t->nchars); + }while(t->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->nchars){ + p1 = q1 - t->org; + if(p1 > t->nchars) + p1 = t->nchars; + if(q0 < t->org){ + t->org = q0; + p0 = 0; + }else + p0 = q0 - t->org; + frdelete(t, 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->nc); + *p1 = min(q1, t->file->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, q, &r, 1); + return r; +} + +static int +spacesindentbswidth(Text *t) +{ + uint q, col; + Rune r; + + col = textbswidth(t, 0x15); + q = t->q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r != ' ') + break; + q--; + if(--col % t->tabstop == 0) + break; + } + if(t->q0 == q) + return 1; + return t->q0-q; +} + +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 */ + if(t->what == Body && t->w->indent[SPACESINDENT]) + return spacesindentbswidth(t); + 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; +} + +int +textfilewidth(Text *t, uint q0, int oneelement) +{ + uint q; + Rune r; + + q = q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r <= ' ') + break; + if(oneelement && r=='/') + break; + --q; + } + return q0-q; +} + +Rune* +textcomplete(Text *t) +{ + int i, nstr, npath; + uint q; + Rune tmp[200]; + Rune *str, *path; + Rune *rp; + Completion *c; + char *s, *dirs; + Runestr dir; + + /* control-f: filename completion; works back to white space or / */ + if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */ + return nil; + nstr = textfilewidth(t, t->q0, TRUE); + str = runemalloc(nstr); + npath = textfilewidth(t, t->q0-nstr, FALSE); + path = runemalloc(npath); + + c = nil; + rp = nil; + dirs = nil; + + q = t->q0-nstr; + for(i=0; i<nstr; i++) + str[i] = textreadc(t, q++); + q = t->q0-nstr-npath; + for(i=0; i<npath; i++) + path[i] = textreadc(t, q++); + /* is path rooted? if not, we need to make it relative to window path */ + if(npath>0 && path[0]=='/') + dir = (Runestr){path, npath}; + else{ + dir = dirname(t, nil, 0); + if(dir.nr + 1 + npath > nelem(tmp)){ + free(dir.r); + goto Return; + } + if(dir.nr == 0){ + dir.nr = 1; + dir.r = runestrdup(L"."); + } + runemove(tmp, dir.r, dir.nr); + tmp[dir.nr] = '/'; + runemove(tmp+dir.nr+1, path, npath); + free(dir.r); + dir.r = tmp; + dir.nr += 1+npath; + dir = cleanrname(dir); + } + + s = smprint("%.*S", nstr, str); + dirs = smprint("%.*S", dir.nr, dir.r); + c = complete(dirs, s); + free(s); + if(c == nil){ + warning(nil, "error attempting completion: %r\n"); + goto Return; + } + + if(!c->advance){ + warning(nil, "%.*S%s%.*S*%s\n", + dir.nr, dir.r, + dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "", + nstr, str, + c->nmatch? "" : ": no matches in:"); + for(i=0; i<c->nfile; i++) + warning(nil, " %s\n", c->filename[i]); + } + + if(c->advance) + rp = runesmprint("%s", c->string); + else + rp = nil; + Return: + freecompletion(c); + free(dirs); + free(str); + free(path); + return rp; +} + +void +texttype(Text *t, Rune r) +{ + uint q0, q1; + int nnb, nb, n, i; + int nr; + Rune *rp; + Text *u; + + nr = 1; + rp = &r; + switch(r){ + case Kleft: + typecommit(t); + if(t->q0 > 0) + textshow(t, t->q0-1, t->q0-1, TRUE); + return; + case Kright: + typecommit(t); + if(t->q1 < t->file->nc) + textshow(t, t->q1+1, t->q1+1, TRUE); + return; + case Kdown: + n = t->maxlines/3; + goto case_Down; + case Kscrollonedown: + n = mousescrollsize(t->maxlines); + if(n <= 0) + n = 1; + goto case_Down; + case Kpgdown: + n = 2*t->maxlines/3; + case_Down: + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height)); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Kup: + n = t->maxlines/3; + goto case_Up; + case Kscrolloneup: + n = mousescrollsize(t->maxlines); + goto case_Up; + case Kpgup: + n = 2*t->maxlines/3; + case_Up: + q0 = textbacknl(t, t->org, n); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Khome: + typecommit(t); + textshow(t, 0, 0, FALSE); + return; + case Kend: + typecommit(t); + textshow(t, t->file->nc, t->file->nc, FALSE); + return; + case 0x01: /* ^A: beginning of line */ + typecommit(t); + /* go to where ^U would erase, if not already at BOL */ + nnb = 0; + if(t->q0>0 && textreadc(t, t->q0-1)!='\n') + nnb = textbswidth(t, 0x15); + textshow(t, t->q0-nnb, t->q0-nnb, TRUE); + return; + case 0x05: /* ^E: end of line */ + typecommit(t); + q0 = t->q0; + while(q0<t->file->nc && textreadc(t, q0)!='\n') + q0++; + textshow(t, q0, q0, TRUE); + 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 0x06: + case Kins: + rp = textcomplete(t); + if(rp == nil) + return; + nr = runestrlen(rp); + break; /* fall through to normal insertion case */ + case 0x1B: + if(t->eq0 != ~0) + textsetselect(t, t->eq0, t->q0); + if(t->ncache > 0) + typecommit(t); + 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; + case '\t': + if(t->what == Body && t->w->indent[SPACESINDENT]){ + nnb = textbswidth(t, 0x15); + if(nnb == 1 && textreadc(t, t->q0-1) == '\n') + nnb = 0; + nnb = t->tabstop - nnb % t->tabstop; + rp = runemalloc(nnb); + for(nr = 0; nr < nnb; nr++) + rp[nr] = ' '; + } + break; + case '\n': + if(t->what == Body && t->w->indent[AUTOINDENT]){ + /* find beginning of previous line using backspace code */ + nnb = textbswidth(t, 0x15); /* ^U case */ + rp = runemalloc(nnb + 1); + nr = 0; + rp[nr++] = r; + for(i=0; i<nnb; i++){ + r = textreadc(t, t->q0-nnb+i); + if(r != ' ' && r != '\t') + break; + rp[nr++] = r; + } + } + break; /* fall through to normal code */ + } + /* 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, rp, nr, FALSE); + if(u != t) + textsetselect(u, u->q0, u->q1); + if(u->ncache+nr > u->ncachealloc){ + u->ncachealloc += 10 + nr; + u->cache = runerealloc(u->cache, u->ncachealloc); + } + runemove(u->cache+u->ncache, rp, nr); + u->ncache += nr; + } + if(rp != &r) + free(rp); + textsetselect(t, t->q0+nr, t->q0+nr); + 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 int clickcount; +static Point clickpt; +static Text *selecttext; +static uint selectq; + +/* + * called from frame library + */ +void +framescroll(Frame *f, int dl) +{ + if(f != &selecttext->Frame) + 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->p0) + textsetselect(t, t->org+t->p0, selectq); + else + textsetselect(t, selectq, t->org+t->p0); + }else{ + if(t->org+t->nchars == t->file->nc) + return; + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height)); + if(selectq > t->org+t->p1) + textsetselect(t, t->org+t->p1, selectq); + else + textsetselect(t, selectq, t->org+t->p1); + } + textsetorigin(t, q0, TRUE); + flushimage(display, 1); +} + + +void +textselect(Text *t) +{ + uint q0, q1; + int b, x, y, dx, dy; + 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; + dx = abs(clickpt.x - mouse->xy.x); + dy = abs(clickpt.y - mouse->xy.y); + clickpt = mouse->xy; + selectq = t->org+frcharofpt(t, mouse->xy); + clickcount++; + if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3) + clickcount = 0; + if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){ + textstretchsel(t, selectq, &q0, &q1, clickcount); + textsetselect(t, q0, q1); + flushimage(display, 1); + x = mouse->xy.x; + y = mouse->xy.y; + /* stay here until something interesting happens */ + while(1){ + readmouse(mousectl); + dx = abs(mouse->xy.x - x); + dy = abs(mouse->xy.y - y); + if(mouse->buttons != b || dx >= 3 || dy >= 3) + break; + clickcount++; + clickmsec = mouse->msec; + } + mouse->xy.x = x; /* in case we're calling frselect */ + mouse->xy.y = y; + q0 = t->q0; /* may have changed */ + q1 = t->q1; + selectq = t->org+frcharofpt(t, mouse->xy);; + } + if(mouse->buttons == b && clickcount == 0){ + t->Frame.scroll = framescroll; + frselect(t, mousectl); + /* horrible botch: while asleep, may have lost selection altogether */ + if(selectq > t->file->nc) + selectq = t->org + t->p0; + t->Frame.scroll = nil; + if(selectq < t->org) + q0 = selectq; + else + q0 = t->org + t->p0; + if(selectq > t->org+t->nchars) + q1 = selectq; + else + q1 = t->org+t->p1; + } + if(q0 == q1){ + if(q0==t->q0 && mouse->msec-clickmsec<500) + textstretchsel(t, selectq, &q0, &q1, clickcount); + 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&1) && (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); + if(mouse->msec-clickmsec >= 500) + clicktext = nil; + } +} + +void +textshow(Text *t, uint q0, uint q1, int doselect) +{ + int qe; + int nl; + uint q; + + if(t->what != Body){ + if(doselect) + textsetselect(t, q0, q1); + return; + } + if(t->w!=nil && t->maxlines==0) + colgrow(t->col, t->w, 1); + if(doselect) + textsetselect(t, q0, q1); + qe = t->org+t->nchars; + if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache))) + textscrdraw(t); + else{ + if(t->w->nopen[QWevent] > 0) + nl = 3*t->maxlines/4; + else + nl = t->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->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->p0 and t->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->nchars) + p0 = t->nchars; + if(p1 > t->nchars) + p1 = t->nchars; + if(p0==t->p0 && p1==t->p1) + return; + /* screen disagrees with desired selection */ + if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){ + /* no overlap or too easy to bother trying */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0); + frdrawsel(t, frptofchar(t, p0), p0, p1, 1); + goto Return; + } + /* overlap; avoid unnecessary painting */ + if(p0 < t->p0){ + /* extend selection backwards */ + frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1); + }else if(p0 > t->p0){ + /* trim first part of selection */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0); + } + if(p1 > t->p1){ + /* extend selection forwards */ + frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1); + }else if(p1 < t->p1){ + /* trim last part of selection */ + frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0); + } + + Return: + t->p0 = p0; + t->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->xy; + b = mc->buttons; + msec = mc->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->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->buttons == b); + if(mc->msec-msec < DELAY && p0!=p1 + && abs(mp.x-mc->xy.x)<MINMOVE + && abs(mp.y-mc->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, mousectl, high, &p1); + buts = mousectl->buttons; + if((buts & mask) == 0){ + *q0 = p0+t->org; + *q1 = p1+t->org; + } + + while(mousectl->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[] = { L'{', L'[', L'(', L'<', L'«', 0 }; +static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; +static Rune left2[] = { L'\n', 0 }; +static Rune left3[] = { L'\'', L'"', L'`', 0 }; + +static +Rune *left[] = { + left1, + left2, + left3, + nil +}; +static +Rune *right[] = { + right1, + left2, + left3, + nil +}; + +int +inmode(Rune r, int mode) +{ + return (mode == 1) ? isalnum(r) : r && !isspace(r); +} + +void +textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode) +{ + int c, i; + Rune *r, *l, *p; + uint q; + + *q0 = mp; + *q1 = mp; + 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->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->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->nc && inmode(textreadc(t, *q1), mode)) + (*q1)++; + /* try filling out word to left */ + while(*q0>0 && inmode(textreadc(t, *q0-1), mode)) + (*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->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 && textreadc(t, org-1) != '\n'){ + /* 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->nc; i++){ + if(textreadc(t, org) == '\n'){ + org++; + break; + } + org++; + } + } + a = org-t->org; + fixup = 0; + if(a>=0 && a<t->nchars){ + frdelete(t, 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->nchars){ + n = t->org - org; + r = runemalloc(n); + bufread(t->file, org, r, n); + frinsert(t, r, r+n, 0); + free(r); + }else + frdelete(t, 0, t->nchars); + t->org = org; + textfill(t); + textscrdraw(t); + textsetselect(t, t->q0, t->q1); + if(fixup && t->p1 > t->p0) + frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->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, 0, t->nchars); + t->org = 0; + t->q0 = 0; + t->q1 = 0; + filereset(t->file); + bufreset(t->file); +} diff --git a/patch/acme-moveto-undo/text.c.orig b/patch/acme-moveto-undo/text.c.orig new file mode 100644 index 0000000..484ec3d --- /dev/null +++ b/patch/acme-moveto-undo/text.c.orig @@ -0,0 +1,1460 @@ +#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 <complete.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->Frame.cols, cols, sizeof t->Frame.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, r, f, b, t->Frame.cols); + rr = t->r; + rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */ + draw(t->b, rr, t->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->maxtab = maxt*stringwidth(f, "0"); + if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){ + if(t->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->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, 0); + textredraw(t, r, t->font, t->b, odx); + return r.max.y; +} + +void +textclose(Text *t) +{ + free(t->cache); + frclear(t, 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(void *a, 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; + + if(t->file->ntext > 1) + return; + mint = stringwidth(t->font, "0"); + /* go for narrower tabs if set more than 3 wide */ + t->maxtab = min(maxtab, TABDIR)*mint; + maxt = t->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->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, L"\t", 1); + q1++; + w += mint; + } + do{ + fileinsert(t->file, q1, L"\t", 1); + q1++; + w += maxt-(w%maxt); + }while(w < colw); + } + fileinsert(t->file, q1, L"\n", 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->nc || t->w==nil || t!=&t->w->body) + error("text.load"); + if(t->w->isdir && t->file->nname==0){ + warning(nil, "empty directory name\n"); + return 0; + } + 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->nname > 0 && 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->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->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, q, rp, n); + if(q < t->org) + t->org += n; + else if(q <= t->org+t->nchars) + frinsert(t, rp, rp+n, q-t->org); + if(t->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->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->nchars) + frinsert(t, 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 +typecommit(Text *t) +{ + if(t->w != nil) + wincommit(t->w, t); + else + textcommit(t, TRUE); +} + +void +textfill(Text *t) +{ + Rune *rp; + int i, n, m, nl; + + if(t->lastlinefull || t->nofill) + return; + if(t->ncache > 0) + typecommit(t); + rp = fbufalloc(); + do{ + n = t->file->nc-(t->org+t->nchars); + if(n == 0) + break; + if(n > 2000) /* educated guess at reasonable amount */ + n = 2000; + bufread(t->file, t->org+t->nchars, rp, n); + /* + * it's expensive to frinsert more than we need, so + * count newlines. + */ + nl = t->maxlines-t->nlines; + m = 0; + for(i=0; i<n; ){ + if(rp[i++] == '\n'){ + m++; + if(m >= nl) + break; + } + } + frinsert(t, rp, rp+i, t->nchars); + }while(t->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->nchars){ + p1 = q1 - t->org; + if(p1 > t->nchars) + p1 = t->nchars; + if(q0 < t->org){ + t->org = q0; + p0 = 0; + }else + p0 = q0 - t->org; + frdelete(t, 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->nc); + *p1 = min(q1, t->file->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, q, &r, 1); + return r; +} + +static int +spacesindentbswidth(Text *t) +{ + uint q, col; + Rune r; + + col = textbswidth(t, 0x15); + q = t->q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r != ' ') + break; + q--; + if(--col % t->tabstop == 0) + break; + } + if(t->q0 == q) + return 1; + return t->q0-q; +} + +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 */ + if(t->what == Body && t->w->indent[SPACESINDENT]) + return spacesindentbswidth(t); + 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; +} + +int +textfilewidth(Text *t, uint q0, int oneelement) +{ + uint q; + Rune r; + + q = q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r <= ' ') + break; + if(oneelement && r=='/') + break; + --q; + } + return q0-q; +} + +Rune* +textcomplete(Text *t) +{ + int i, nstr, npath; + uint q; + Rune tmp[200]; + Rune *str, *path; + Rune *rp; + Completion *c; + char *s, *dirs; + Runestr dir; + + /* control-f: filename completion; works back to white space or / */ + if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */ + return nil; + nstr = textfilewidth(t, t->q0, TRUE); + str = runemalloc(nstr); + npath = textfilewidth(t, t->q0-nstr, FALSE); + path = runemalloc(npath); + + c = nil; + rp = nil; + dirs = nil; + + q = t->q0-nstr; + for(i=0; i<nstr; i++) + str[i] = textreadc(t, q++); + q = t->q0-nstr-npath; + for(i=0; i<npath; i++) + path[i] = textreadc(t, q++); + /* is path rooted? if not, we need to make it relative to window path */ + if(npath>0 && path[0]=='/') + dir = (Runestr){path, npath}; + else{ + dir = dirname(t, nil, 0); + if(dir.nr + 1 + npath > nelem(tmp)){ + free(dir.r); + goto Return; + } + if(dir.nr == 0){ + dir.nr = 1; + dir.r = runestrdup(L"."); + } + runemove(tmp, dir.r, dir.nr); + tmp[dir.nr] = '/'; + runemove(tmp+dir.nr+1, path, npath); + free(dir.r); + dir.r = tmp; + dir.nr += 1+npath; + dir = cleanrname(dir); + } + + s = smprint("%.*S", nstr, str); + dirs = smprint("%.*S", dir.nr, dir.r); + c = complete(dirs, s); + free(s); + if(c == nil){ + warning(nil, "error attempting completion: %r\n"); + goto Return; + } + + if(!c->advance){ + warning(nil, "%.*S%s%.*S*%s\n", + dir.nr, dir.r, + dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "", + nstr, str, + c->nmatch? "" : ": no matches in:"); + for(i=0; i<c->nfile; i++) + warning(nil, " %s\n", c->filename[i]); + } + + if(c->advance) + rp = runesmprint("%s", c->string); + else + rp = nil; + Return: + freecompletion(c); + free(dirs); + free(str); + free(path); + return rp; +} + +void +texttype(Text *t, Rune r) +{ + uint q0, q1; + int nnb, nb, n, i; + int nr; + Rune *rp; + Text *u; + + nr = 1; + rp = &r; + switch(r){ + case Kleft: + typecommit(t); + if(t->q0 > 0) + textshow(t, t->q0-1, t->q0-1, TRUE); + return; + case Kright: + typecommit(t); + if(t->q1 < t->file->nc) + textshow(t, t->q1+1, t->q1+1, TRUE); + return; + case Kdown: + n = t->maxlines/3; + goto case_Down; + case Kscrollonedown: + n = mousescrollsize(t->maxlines); + if(n <= 0) + n = 1; + goto case_Down; + case Kpgdown: + n = 2*t->maxlines/3; + case_Down: + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height)); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Kup: + n = t->maxlines/3; + goto case_Up; + case Kscrolloneup: + n = mousescrollsize(t->maxlines); + goto case_Up; + case Kpgup: + n = 2*t->maxlines/3; + case_Up: + q0 = textbacknl(t, t->org, n); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Khome: + typecommit(t); + textshow(t, 0, 0, FALSE); + return; + case Kend: + typecommit(t); + textshow(t, t->file->nc, t->file->nc, FALSE); + return; + case 0x01: /* ^A: beginning of line */ + typecommit(t); + /* go to where ^U would erase, if not already at BOL */ + nnb = 0; + if(t->q0>0 && textreadc(t, t->q0-1)!='\n') + nnb = textbswidth(t, 0x15); + textshow(t, t->q0-nnb, t->q0-nnb, TRUE); + return; + case 0x05: /* ^E: end of line */ + typecommit(t); + q0 = t->q0; + while(q0<t->file->nc && textreadc(t, q0)!='\n') + q0++; + textshow(t, q0, q0, TRUE); + 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 0x06: + case Kins: + rp = textcomplete(t); + if(rp == nil) + return; + nr = runestrlen(rp); + break; /* fall through to normal insertion case */ + case 0x1B: + if(t->eq0 != ~0) + textsetselect(t, t->eq0, t->q0); + if(t->ncache > 0) + typecommit(t); + 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; + case '\t': + if(t->what == Body && t->w->indent[SPACESINDENT]){ + nnb = textbswidth(t, 0x15); + if(nnb == 1 && textreadc(t, t->q0-1) == '\n') + nnb = 0; + nnb = t->tabstop - nnb % t->tabstop; + rp = runemalloc(nnb); + for(nr = 0; nr < nnb; nr++) + rp[nr] = ' '; + } + break; + case '\n': + if(t->what == Body && t->w->indent[AUTOINDENT]){ + /* find beginning of previous line using backspace code */ + nnb = textbswidth(t, 0x15); /* ^U case */ + rp = runemalloc(nnb + 1); + nr = 0; + rp[nr++] = r; + for(i=0; i<nnb; i++){ + r = textreadc(t, t->q0-nnb+i); + if(r != ' ' && r != '\t') + break; + rp[nr++] = r; + } + } + break; /* fall through to normal code */ + } + /* 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, rp, nr, FALSE); + if(u != t) + textsetselect(u, u->q0, u->q1); + if(u->ncache+nr > u->ncachealloc){ + u->ncachealloc += 10 + nr; + u->cache = runerealloc(u->cache, u->ncachealloc); + } + runemove(u->cache+u->ncache, rp, nr); + u->ncache += nr; + } + if(rp != &r) + free(rp); + textsetselect(t, t->q0+nr, t->q0+nr); + 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 int clickcount; +static Point clickpt; +static Text *selecttext; +static uint selectq; + +/* + * called from frame library + */ +void +framescroll(Frame *f, int dl) +{ + if(f != &selecttext->Frame) + 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->p0) + textsetselect(t, t->org+t->p0, selectq); + else + textsetselect(t, selectq, t->org+t->p0); + }else{ + if(t->org+t->nchars == t->file->nc) + return; + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height)); + if(selectq > t->org+t->p1) + textsetselect(t, t->org+t->p1, selectq); + else + textsetselect(t, selectq, t->org+t->p1); + } + textsetorigin(t, q0, TRUE); + flushimage(display, 1); +} + + +void +textselect(Text *t) +{ + uint q0, q1; + int b, x, y, dx, dy; + 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; + dx = abs(clickpt.x - mouse->xy.x); + dy = abs(clickpt.y - mouse->xy.y); + clickpt = mouse->xy; + selectq = t->org+frcharofpt(t, mouse->xy); + clickcount++; + if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3) + clickcount = 0; + if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){ + textstretchsel(t, selectq, &q0, &q1, clickcount); + textsetselect(t, q0, q1); + flushimage(display, 1); + x = mouse->xy.x; + y = mouse->xy.y; + /* stay here until something interesting happens */ + while(1){ + readmouse(mousectl); + dx = abs(mouse->xy.x - x); + dy = abs(mouse->xy.y - y); + if(mouse->buttons != b || dx >= 3 || dy >= 3) + break; + clickcount++; + clickmsec = mouse->msec; + } + mouse->xy.x = x; /* in case we're calling frselect */ + mouse->xy.y = y; + q0 = t->q0; /* may have changed */ + q1 = t->q1; + selectq = t->org+frcharofpt(t, mouse->xy);; + } + if(mouse->buttons == b && clickcount == 0){ + t->Frame.scroll = framescroll; + frselect(t, mousectl); + /* horrible botch: while asleep, may have lost selection altogether */ + if(selectq > t->file->nc) + selectq = t->org + t->p0; + t->Frame.scroll = nil; + if(selectq < t->org) + q0 = selectq; + else + q0 = t->org + t->p0; + if(selectq > t->org+t->nchars) + q1 = selectq; + else + q1 = t->org+t->p1; + } + if(q0 == q1){ + if(q0==t->q0 && mouse->msec-clickmsec<500) + textstretchsel(t, selectq, &q0, &q1, clickcount); + 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&1) && (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); + if(mouse->msec-clickmsec >= 500) + clicktext = nil; + } +} + +void +textshow(Text *t, uint q0, uint q1, int doselect) +{ + int qe; + int nl; + uint q; + + if(t->what != Body){ + if(doselect) + textsetselect(t, q0, q1); + return; + } + if(t->w!=nil && t->maxlines==0) + colgrow(t->col, t->w, 1); + if(doselect) + textsetselect(t, q0, q1); + qe = t->org+t->nchars; + if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache))) + textscrdraw(t); + else{ + if(t->w->nopen[QWevent] > 0) + nl = 3*t->maxlines/4; + else + nl = t->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->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->p0 and t->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->nchars) + p0 = t->nchars; + if(p1 > t->nchars) + p1 = t->nchars; + if(p0==t->p0 && p1==t->p1) + return; + /* screen disagrees with desired selection */ + if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){ + /* no overlap or too easy to bother trying */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0); + frdrawsel(t, frptofchar(t, p0), p0, p1, 1); + goto Return; + } + /* overlap; avoid unnecessary painting */ + if(p0 < t->p0){ + /* extend selection backwards */ + frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1); + }else if(p0 > t->p0){ + /* trim first part of selection */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0); + } + if(p1 > t->p1){ + /* extend selection forwards */ + frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1); + }else if(p1 < t->p1){ + /* trim last part of selection */ + frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0); + } + + Return: + t->p0 = p0; + t->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->xy; + b = mc->buttons; + msec = mc->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->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->buttons == b); + if(mc->msec-msec < DELAY && p0!=p1 + && abs(mp.x-mc->xy.x)<MINMOVE + && abs(mp.y-mc->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, mousectl, high, &p1); + buts = mousectl->buttons; + if((buts & mask) == 0){ + *q0 = p0+t->org; + *q1 = p1+t->org; + } + + while(mousectl->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[] = { L'{', L'[', L'(', L'<', L'«', 0 }; +static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; +static Rune left2[] = { L'\n', 0 }; +static Rune left3[] = { L'\'', L'"', L'`', 0 }; + +static +Rune *left[] = { + left1, + left2, + left3, + nil +}; +static +Rune *right[] = { + right1, + left2, + left3, + nil +}; + +int +inmode(Rune r, int mode) +{ + return (mode == 1) ? isalnum(r) : r && !isspace(r); +} + +void +textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode) +{ + int c, i; + Rune *r, *l, *p; + uint q; + + *q0 = mp; + *q1 = mp; + 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->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->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->nc && inmode(textreadc(t, *q1), mode)) + (*q1)++; + /* try filling out word to left */ + while(*q0>0 && inmode(textreadc(t, *q0-1), mode)) + (*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->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 && textreadc(t, org-1) != '\n'){ + /* 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->nc; i++){ + if(textreadc(t, org) == '\n'){ + org++; + break; + } + org++; + } + } + a = org-t->org; + fixup = 0; + if(a>=0 && a<t->nchars){ + frdelete(t, 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->nchars){ + n = t->org - org; + r = runemalloc(n); + bufread(t->file, org, r, n); + frinsert(t, r, r+n, 0); + free(r); + }else + frdelete(t, 0, t->nchars); + t->org = org; + textfill(t); + textscrdraw(t); + textsetselect(t, t->q0, t->q1); + if(fixup && t->p1 > t->p0) + frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->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, 0, t->nchars); + t->org = 0; + t->q0 = 0; + t->q1 = 0; + filereset(t->file); + bufreset(t->file); +} |