summaryrefslogtreecommitdiff
path: root/patch/acme-esc-move
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2021-01-28 21:33:30 +0000
committerJohn Ankarström <john@ankarstrom.se>2021-01-28 21:33:30 +0000
commit51b5b02f52cc6f314029f3f2fe97afdc26ba0f25 (patch)
tree711d1efadeb78ad9b2d9f7b629350358838bc626 /patch/acme-esc-move
parentf3fd330cddade1c66d0f101d5cc6f657c4cd1bb6 (diff)
downloadplan9-51b5b02f52cc6f314029f3f2fe97afdc26ba0f25.tar.gz
Add various patches
Diffstat (limited to 'patch/acme-esc-move')
-rw-r--r--patch/acme-esc-move/email1
-rw-r--r--patch/acme-esc-move/files1
-rw-r--r--patch/acme-esc-move/notes0
-rw-r--r--patch/acme-esc-move/readme5
-rw-r--r--patch/acme-esc-move/text.c1462
-rw-r--r--patch/acme-esc-move/text.c.backup1460
-rw-r--r--patch/acme-esc-move/text.c.new1462
-rw-r--r--patch/acme-esc-move/text.c.orig1460
8 files changed, 5851 insertions, 0 deletions
diff --git a/patch/acme-esc-move/email b/patch/acme-esc-move/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/acme-esc-move/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/acme-esc-move/files b/patch/acme-esc-move/files
new file mode 100644
index 0000000..644ef30
--- /dev/null
+++ b/patch/acme-esc-move/files
@@ -0,0 +1 @@
+/sys/src/cmd/acme/text.c text.c
diff --git a/patch/acme-esc-move/notes b/patch/acme-esc-move/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/acme-esc-move/notes
diff --git a/patch/acme-esc-move/readme b/patch/acme-esc-move/readme
new file mode 100644
index 0000000..e8e7334
--- /dev/null
+++ b/patch/acme-esc-move/readme
@@ -0,0 +1,5 @@
+move mouse cursor to beginning of Esc-selection
+
+This way, you can Esc-select some text
+and immediately 2/3-press it without needing to
+find the selection with the mouse.
diff --git a/patch/acme-esc-move/text.c b/patch/acme-esc-move/text.c
new file mode 100644
index 0000000..8aef971
--- /dev/null
+++ b/patch/acme-esc-move/text.c
@@ -0,0 +1,1462 @@
+#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);
+ moveto(mousectl, addpt(frptofchar(t, t->p0), Pt(4, font->height-4)));
+ }
+ 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-esc-move/text.c.backup b/patch/acme-esc-move/text.c.backup
new file mode 100644
index 0000000..484ec3d
--- /dev/null
+++ b/patch/acme-esc-move/text.c.backup
@@ -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-esc-move/text.c.new b/patch/acme-esc-move/text.c.new
new file mode 100644
index 0000000..8aef971
--- /dev/null
+++ b/patch/acme-esc-move/text.c.new
@@ -0,0 +1,1462 @@
+#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);
+ moveto(mousectl, addpt(frptofchar(t, t->p0), Pt(4, font->height-4)));
+ }
+ 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-esc-move/text.c.orig b/patch/acme-esc-move/text.c.orig
new file mode 100644
index 0000000..484ec3d
--- /dev/null
+++ b/patch/acme-esc-move/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);
+}