summaryrefslogtreecommitdiff
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
parentf3fd330cddade1c66d0f101d5cc6f657c4cd1bb6 (diff)
downloadplan9-51b5b02f52cc6f314029f3f2fe97afdc26ba0f25.tar.gz
Add various patches
-rwxr-xr-xpatch/9fs-local-sources/9fs87
-rwxr-xr-xpatch/9fs-local-sources/9fs.orig85
-rw-r--r--patch/9fs-local-sources/email1
-rw-r--r--patch/9fs-local-sources/files1
-rw-r--r--patch/9fs-local-sources/notes0
-rw-r--r--patch/9fs-local-sources/readme3
-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
-rw-r--r--patch/acme-fixfont/acme.c969
-rw-r--r--patch/acme-fixfont/acme.c.backup968
-rw-r--r--patch/acme-fixfont/acme.c.new971
-rw-r--r--patch/acme-fixfont/acme.c.orig966
-rw-r--r--patch/acme-fixfont/email1
-rw-r--r--patch/acme-fixfont/files1
-rw-r--r--patch/acme-fixfont/notes0
-rw-r--r--patch/acme-fixfont/readme5
-rw-r--r--patch/acme-moveto-undo/acme.c972
-rw-r--r--patch/acme-moveto-undo/acme.c.orig966
-rw-r--r--patch/acme-moveto-undo/email1
-rw-r--r--patch/acme-moveto-undo/files5
-rw-r--r--patch/acme-moveto-undo/fns.h100
-rw-r--r--patch/acme-moveto-undo/fns.h.orig96
-rw-r--r--patch/acme-moveto-undo/mkfile47
-rw-r--r--patch/acme-moveto-undo/mkfile.orig46
-rw-r--r--patch/acme-moveto-undo/moveto.c37
-rw-r--r--patch/acme-moveto-undo/notes0
-rw-r--r--patch/acme-moveto-undo/readme5
-rw-r--r--patch/acme-moveto-undo/text.c1460
-rw-r--r--patch/acme-moveto-undo/text.c.orig1460
-rw-r--r--patch/diff-c-plus/diffio.c401
-rw-r--r--patch/diff-c-plus/diffio.c.orig401
-rw-r--r--patch/diff-c-plus/email1
-rw-r--r--patch/diff-c-plus/files1
-rw-r--r--patch/diff-c-plus/notes0
-rw-r--r--patch/diff-c-plus/readme6
-rw-r--r--patch/patch-create-i/create98
-rwxr-xr-xpatch/patch-create-i/create.orig85
-rw-r--r--patch/patch-create-i/email1
-rw-r--r--patch/patch-create-i/files1
-rw-r--r--patch/patch-create-i/notes0
-rw-r--r--patch/patch-create-i/readme1
-rw-r--r--patch/patch-create-skip-unchanged/create91
-rwxr-xr-xpatch/patch-create-skip-unchanged/create.orig85
-rw-r--r--patch/patch-create-skip-unchanged/email1
-rw-r--r--patch/patch-create-skip-unchanged/files1
-rw-r--r--patch/patch-create-skip-unchanged/notes0
-rw-r--r--patch/patch-create-skip-unchanged/readme3
-rw-r--r--patch/patch-orig/email1
-rw-r--r--patch/patch-orig/files1
-rw-r--r--patch/patch-orig/notes0
-rwxr-xr-xpatch/patch-orig/orig16
-rw-r--r--patch/patch-orig/readme9
-rw-r--r--patch/patch-update/email1
-rw-r--r--patch/patch-update/files1
-rw-r--r--patch/patch-update/notes0
-rw-r--r--patch/patch-update/readme8
-rwxr-xr-xpatch/patch-update/update35
-rw-r--r--patch/troff-hyphenate-latin-1-fixed/email1
-rw-r--r--patch/troff-hyphenate-latin-1-fixed/files1
-rw-r--r--patch/troff-hyphenate-latin-1-fixed/n8.c584
-rw-r--r--patch/troff-hyphenate-latin-1-fixed/n8.c.orig545
-rw-r--r--patch/troff-hyphenate-latin-1-fixed/notes0
-rw-r--r--patch/troff-hyphenate-latin-1-fixed/readme9
69 files changed, 17493 insertions, 0 deletions
diff --git a/patch/9fs-local-sources/9fs b/patch/9fs-local-sources/9fs
new file mode 100755
index 0000000..a4a4fc0
--- /dev/null
+++ b/patch/9fs-local-sources/9fs
@@ -0,0 +1,87 @@
+#!/bin/rc
+# 9fs filesystem [mountpoint] - srv & mount filesystem, usually from plan 9
+
+rfork e
+switch($1){
+case ''
+ echo usage: 9fs service '[mountpoint]' >[1=2]
+ exit usage
+case 9fat esp pidos dos
+ if(~ $#2 1)
+ part=`{ls $2 >[2]/dev/null}
+ if not if(~ $1 pidos)
+ part=`{ls /dev/sdM*/dos >[2]/dev/null}
+ if not
+ part=`{ls /dev/fs/$1 /dev/sd*/$1 >[2]/dev/null}
+ if(~ $#part 0) {
+ echo 'no '$1' partition found' >[1=2]
+ exit no.$1
+ }
+ part=$part(1)
+
+ if(! test -f /srv/dos)
+ dossrv >/dev/null </dev/null >[2]/dev/null
+
+ unmount /n/$1 >/dev/null >[2]/dev/null
+ mount -c /srv/dos /n/$1 $part
+ if(~ $1 9fat){
+ unmount /n/9 >/dev/null >[2]/dev/null
+ mount -c /srv/dos /n/9 $part
+ }
+case dump other
+ mount -C /srv/boot /n/$1 $1
+case sources
+ bind -c $home/sources /n/sources
+case rsources
+ srv -nqC tcp!sources.cs.bell-labs.com sources /n/sources
+case sourcesdump
+ 9fs sources
+ mount -nC /srv/sources /n/sourcesdump main/archive
+case sourcessnap
+ 9fs sources
+ mount -nC /srv/sources /n/sourcessnap main/snapshot
+case atom
+ srv -nq tcp!atom.9atom.org atom && mount -nC /srv/atom /n/atom atom
+case atomdump
+ 9fs atom && mount -nC /srv/atom /n/atomdump atomdump
+case 9pio
+ srv -nq tcp!9p.io 9pio && mount -nC /srv/9pio /n/9pio
+case 9front
+ 9fs 9front.org
+ for(i in 9front extra fqa hardware iso lists pkg sites)
+ bind /n/9front.org/$i /n/$i
+case 9bugs
+ 9fs contrib.9front.org
+ bind /n/contrib.9front.org/bugs /n/bugs
+case 9contrib
+ 9fs contrib.9front.org
+ for(i in contrib sources)
+ bind /n/contrib.9front.org/$i /n/$i
+case 9grep
+ 9fs tcp!9front.org!7734
+ bind -b /n/9front.org!7734 /n/lists
+# arbitrary venti archives
+case vac:*
+ vacfs <{echo $1}
+case *.vac
+ if (test -e $1)
+ score=$1
+ if not if (! ~ $1 /* && test -e $home/lib/vac/$1)
+ score=$home/lib/vac/$1
+ if not if (! ~ $1 /* && test -e /lib/vac/$1)
+ score=/lib/vac/$1
+ if not {
+ echo $0: $1: no such score file >[1=2]
+ exit 'no score file'
+ }
+ vacfs -m /n/`{basename $1 .vac} `{cat $score}
+case wiki
+ srv -m 'net!9p.io!wiki' wiki /mnt/wiki
+case *
+ switch($#*){
+ case 1
+ srv -m $1
+ case *
+ srv -m $1 $1 $2
+ }
+}
diff --git a/patch/9fs-local-sources/9fs.orig b/patch/9fs-local-sources/9fs.orig
new file mode 100755
index 0000000..c60a9c9
--- /dev/null
+++ b/patch/9fs-local-sources/9fs.orig
@@ -0,0 +1,85 @@
+#!/bin/rc
+# 9fs filesystem [mountpoint] - srv & mount filesystem, usually from plan 9
+
+rfork e
+switch($1){
+case ''
+ echo usage: 9fs service '[mountpoint]' >[1=2]
+ exit usage
+case 9fat esp pidos dos
+ if(~ $#2 1)
+ part=`{ls $2 >[2]/dev/null}
+ if not if(~ $1 pidos)
+ part=`{ls /dev/sdM*/dos >[2]/dev/null}
+ if not
+ part=`{ls /dev/fs/$1 /dev/sd*/$1 >[2]/dev/null}
+ if(~ $#part 0) {
+ echo 'no '$1' partition found' >[1=2]
+ exit no.$1
+ }
+ part=$part(1)
+
+ if(! test -f /srv/dos)
+ dossrv >/dev/null </dev/null >[2]/dev/null
+
+ unmount /n/$1 >/dev/null >[2]/dev/null
+ mount -c /srv/dos /n/$1 $part
+ if(~ $1 9fat){
+ unmount /n/9 >/dev/null >[2]/dev/null
+ mount -c /srv/dos /n/9 $part
+ }
+case dump other
+ mount -C /srv/boot /n/$1 $1
+case sources
+ srv -nqC tcp!sources.cs.bell-labs.com sources /n/sources
+case sourcesdump
+ 9fs sources
+ mount -nC /srv/sources /n/sourcesdump main/archive
+case sourcessnap
+ 9fs sources
+ mount -nC /srv/sources /n/sourcessnap main/snapshot
+case atom
+ srv -nq tcp!atom.9atom.org atom && mount -nC /srv/atom /n/atom atom
+case atomdump
+ 9fs atom && mount -nC /srv/atom /n/atomdump atomdump
+case 9pio
+ srv -nq tcp!9p.io 9pio && mount -nC /srv/9pio /n/9pio
+case 9front
+ 9fs 9front.org
+ for(i in 9front extra fqa hardware iso lists pkg sites)
+ bind /n/9front.org/$i /n/$i
+case 9bugs
+ 9fs contrib.9front.org
+ bind /n/contrib.9front.org/bugs /n/bugs
+case 9contrib
+ 9fs contrib.9front.org
+ for(i in contrib sources)
+ bind /n/contrib.9front.org/$i /n/$i
+case 9grep
+ 9fs tcp!9front.org!7734
+ bind -b /n/9front.org!7734 /n/lists
+# arbitrary venti archives
+case vac:*
+ vacfs <{echo $1}
+case *.vac
+ if (test -e $1)
+ score=$1
+ if not if (! ~ $1 /* && test -e $home/lib/vac/$1)
+ score=$home/lib/vac/$1
+ if not if (! ~ $1 /* && test -e /lib/vac/$1)
+ score=/lib/vac/$1
+ if not {
+ echo $0: $1: no such score file >[1=2]
+ exit 'no score file'
+ }
+ vacfs -m /n/`{basename $1 .vac} `{cat $score}
+case wiki
+ srv -m 'net!9p.io!wiki' wiki /mnt/wiki
+case *
+ switch($#*){
+ case 1
+ srv -m $1
+ case *
+ srv -m $1 $1 $2
+ }
+}
diff --git a/patch/9fs-local-sources/email b/patch/9fs-local-sources/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/9fs-local-sources/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/9fs-local-sources/files b/patch/9fs-local-sources/files
new file mode 100644
index 0000000..5273d26
--- /dev/null
+++ b/patch/9fs-local-sources/files
@@ -0,0 +1 @@
+/rc/bin/9fs 9fs
diff --git a/patch/9fs-local-sources/notes b/patch/9fs-local-sources/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/9fs-local-sources/notes
diff --git a/patch/9fs-local-sources/readme b/patch/9fs-local-sources/readme
new file mode 100644
index 0000000..10e7ca3
--- /dev/null
+++ b/patch/9fs-local-sources/readme
@@ -0,0 +1,3 @@
+9fs: Make 'sources' bind /n/sources to $home/sources
+
+The remote sources are relegated to 'rsources'.
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);
+}
diff --git a/patch/acme-fixfont/acme.c b/patch/acme-fixfont/acme.c
new file mode 100644
index 0000000..3e0279c
--- /dev/null
+++ b/patch/acme-fixfont/acme.c
@@ -0,0 +1,969 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+ /* for generating syms in mkfile only: */
+ #include <bio.h>
+ #include "edit.h"
+
+void mousethread(void*);
+void keyboardthread(void*);
+void waitthread(void*);
+void xfidallocthread(void*);
+void newwindowthread(void*);
+void plumbproc(void*);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int plumbsendfd;
+int plumbeditfd;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2];
+
+Command *command;
+
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+int shutdown(void*, char*);
+
+void
+derror(Display*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+ char *p, *loadfile;
+ char buf[256];
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ loadfile = nil;
+ ARGBEGIN{
+ case 'a':
+ globalindent[AUTOINDENT] = TRUE;
+ break;
+ case 'b':
+ bartflag = TRUE;
+ break;
+ case 'c':
+ p = ARGF();
+ if(p == nil)
+ goto Usage;
+ ncol = atoi(p);
+ if(ncol <= 0)
+ goto Usage;
+ break;
+ case 'f':
+ fontnames[0] = ARGF();
+ if(fontnames[0] == nil)
+ goto Usage;
+ break;
+ case 'F':
+ fontnames[1] = ARGF();
+ if(fontnames[1] == nil)
+ goto Usage;
+ break;
+ case 'i':
+ globalindent[SPACESINDENT] = TRUE;
+ break;
+ case 'l':
+ loadfile = ARGF();
+ if(loadfile == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
+ exits("usage");
+ }ARGEND
+
+ if(fontnames[0] == nil)
+ fontnames[0] = getenv("font");
+ if(fontnames[0] == nil)
+ fontnames[0] = "/lib/font/bit/vga/unicode.font";
+ if(access(fontnames[0], 0) < 0){
+ fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
+ exits("font open");
+ }
+ if(fontnames[1] == nil)
+ fontnames[1] = getenv("fixfont");
+ if(fontnames[1] == nil)
+ fontnames[1] = fontnames[0];
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("home");
+ p = getenv("tabstop");
+ if(p != nil){
+ maxtab = strtoul(p, nil, 0);
+ free(p);
+ }
+ if(maxtab == 0)
+ maxtab = 4;
+ if(loadfile)
+ rowloadfonts(loadfile);
+ putenv("font", fontnames[0]);
+ putenv("fixfont", fontnames[1]);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(cputype){
+ sprint(buf, "/acme/bin/%s", cputype);
+ bind(buf, "/bin", MBEFORE);
+ }
+ bind("/acme/bin", "/bin", MBEFORE);
+ getwd(wdir, sizeof wdir);
+
+ if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ exits("geninitdraw");
+ }
+ d = display;
+ font = d->defaultfont;
+
+ reffont.f = font;
+ reffonts[0] = &reffont;
+ incref(&reffont); /* one to hold up 'font' variable */
+ incref(&reffont); /* one to hold up reffonts[0] */
+ fontcache = emalloc(sizeof(Reffont*));
+ nfontcache = 1;
+ fontcache[0] = &reffont;
+
+ iconinit();
+ timerinit();
+ rxinit();
+
+ cwait = threadwaitchan();
+ ccommand = chancreate(sizeof(Command**), 0);
+ ckill = chancreate(sizeof(Rune*), 0);
+ cxfidalloc = chancreate(sizeof(Xfid*), 0);
+ cxfidfree = chancreate(sizeof(Xfid*), 0);
+ cnewwindow = chancreate(sizeof(Channel*), 0);
+ cerr = chancreate(sizeof(char*), 0);
+ cedit = chancreate(sizeof(int), 0);
+ cexit = chancreate(sizeof(int), 0);
+ cwarn = chancreate(sizeof(void*), 1);
+ if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ rowinit(&row, screen->clipr);
+ if(ncol < 0){
+ if(argc == 0)
+ ncol = 2;
+ else{
+ ncol = (argc+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(argc == 0)
+ readfile(c, wdir);
+ else
+ for(i=0; i<argc; i++){
+ p = utfrrune(argv[i], '/');
+ if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+ readfile(c, argv[i]);
+ else
+ readfile(row.col[i/WPERCOL], argv[i]);
+ }
+ }
+ flushimage(display, 1);
+
+ acmeerrorinit();
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(waitthread, nil, STACK);
+ threadcreate(xfidallocthread, nil, STACK);
+ threadcreate(newwindowthread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nb, nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ textload(&w->body, 0, s, 1);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
+ xfidlog(w, "new");
+}
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+ dumping = TRUE;
+ rowdump(&row, nil);
+ }
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("acme: %s\n", msg);
+ abort();
+ return 0;
+}
+
+void
+killprocs(void)
+{
+ Command *c;
+
+ fsysclose();
+// if(display)
+// flushimage(display, 1);
+
+ for(c=command; c; c=c->next)
+ postnote(PNGROUP, c->pid, "hangup");
+ remove(acmeerrorfile);
+}
+
+static int errorfd;
+
+void
+acmeerrorproc(void *)
+{
+ char *buf, *s;
+ int n;
+
+ threadsetname("acmeerrorproc");
+ buf = emalloc(8192+1);
+ while((n=read(errorfd, buf, 8192)) >= 0){
+ buf[n] = '\0';
+ s = estrdup(buf);
+ sendp(cerr, s);
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int fd, pfd[2];
+ char buf[64];
+
+ if(pipe(pfd) < 0)
+ error("can't create pipe");
+ sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0){
+ remove(acmeerrorfile);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0)
+ error("can't create acmeerror file");
+ }
+ sprint(buf, "%d", pfd[0]);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ /* reopen pfd[1] close on exec */
+ sprint(buf, "/fd/%d", pfd[1]);
+ errorfd = open(buf, OREAD|OCEXEC);
+ if(errorfd < 0)
+ error("can't re-open acmeerror file");
+ close(pfd[1]);
+ close(pfd[0]);
+ proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbeditfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+void
+keyboardthread(void *)
+{
+ Rune r;
+ Timer *timer;
+ Text *t;
+ enum { KTimer, KKey, NKALT };
+ static Alt alts[NKALT+1];
+
+ alts[KTimer].c = nil;
+ alts[KTimer].v = nil;
+ alts[KTimer].op = CHANNOP;
+ alts[KKey].c = keyboardctl->c;
+ alts[KKey].v = &r;
+ alts[KKey].op = CHANRCV;
+ alts[NKALT].op = CHANEND;
+
+ timer = nil;
+ typetext = nil;
+ threadsetname("keyboardthread");
+ for(;;){
+ switch(alt(alts)){
+ case KTimer:
+ timerstop(timer);
+ t = typetext;
+ if(t!=nil && t->what==Tag){
+ winlock(t->w, 'K');
+ wincommit(t->w, t);
+ winunlock(t->w);
+ flushimage(display, 1);
+ }
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ break;
+ case KKey:
+ casekeyboard:
+ typetext = rowtype(&row, r, mouse->xy);
+ t = typetext;
+ if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
+ activecol = t->col;
+ if(t!=nil && t->w!=nil)
+ t->w->body.file->curtext = &t->w->body;
+ if(timer != nil)
+ timercancel(timer);
+ if(t!=nil && t->what==Tag) {
+ timer = timerstart(500);
+ alts[KTimer].c = timer->c;
+ alts[KTimer].op = CHANRCV;
+ }else{
+ timer = nil;
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ }
+ if(nbrecv(keyboardctl->c, &r) > 0)
+ goto casekeyboard;
+ flushimage(display, 1);
+ break;
+ }
+ }
+}
+
+void
+mousethread(void *)
+{
+ Text *t, *argt;
+ int but;
+ uint q0, q1;
+ Window *w;
+ Plumbmsg *pm;
+ Mouse m;
+ char *act;
+ enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
+ static Alt alts[NMALT+1];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ alts[MMouse].op = CHANRCV;
+ alts[MPlumb].c = cplumb;
+ alts[MPlumb].v = &pm;
+ alts[MPlumb].op = CHANRCV;
+ alts[MWarnings].c = cwarn;
+ alts[MWarnings].v = nil;
+ alts[MWarnings].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row);
+ flushwarnings();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ scrlresize();
+ rowresize(&row, screen->clipr);
+ break;
+ case MPlumb:
+ if(strcmp(pm->type, "text") == 0){
+ act = plumblookup(pm->attr, "action");
+ if(act==nil || strcmp(act, "showfile")==0)
+ plumblook(pm);
+ else if(strcmp(act, "showdata")==0)
+ plumbshow(pm);
+ }
+ plumbfree(pm);
+ break;
+ case MWarnings:
+ break;
+ case MMouse:
+ /*
+ * Make a copy so decisions are consistent; mousectl changes
+ * underfoot. Can't just receive into m because this introduces
+ * another race; see /sys/src/libdraw/mouse.c.
+ */
+ m = mousectl->Mouse;
+ qlock(&row);
+ t = rowwhich(&row, m.xy);
+
+ if((t!=mousetext && t!=nil && t->w!=nil) &&
+ (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
+ xfidlog(t->w, "focus");
+ }
+
+ if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
+ winlock(mousetext->w, 'M');
+ mousetext->eq0 = ~0;
+ wincommit(mousetext->w, mousetext);
+ winunlock(mousetext->w);
+ }
+ mousetext = t;
+ if(t == nil)
+ goto Continue;
+ w = t->w;
+ if(t==nil || m.buttons==0)
+ goto Continue;
+ but = 0;
+ if(m.buttons == 1)
+ but = 1;
+ else if(m.buttons == 2)
+ but = 2;
+ else if(m.buttons == 4)
+ but = 3;
+ barttext = t;
+ if(t->what==Body && ptinrect(m.xy, t->scrollr)){
+ if(but){
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(t->what==Body && w != nil && (m.buttons & (8|16))){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ texttype(t, but);
+ winunlock(w);
+ goto Continue;
+ }
+ if(ptinrect(m.xy, t->scrollr)){
+ if(but){
+ if(t->what == Columntag)
+ rowdragcol(&row, t->col, but);
+ else if(t->what == Tag){
+ coldragwin(t->col, t->w, but);
+ if(t->w)
+ barttext = &t->w->body;
+ }
+ if(t->col)
+ activecol = t->col;
+ }
+ goto Continue;
+ }
+ if(m.buttons){
+ if(w)
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ if(w)
+ wincommit(w, t);
+ else
+ textcommit(t, TRUE);
+ if(m.buttons & 1){
+ textselect(t);
+ if(w)
+ winsettag(w);
+ argtext = t;
+ seltext = t;
+ if(t->col)
+ activecol = t->col; /* button 1 only */
+ if(t->w!=nil && t==&t->w->body)
+ activewin = t->w;
+ }else if(m.buttons & 2){
+ if(textselect2(t, &q0, &q1, &argt))
+ execute(t, q0, q1, FALSE, argt);
+ }else if(m.buttons & 4){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1, FALSE);
+ }
+ if(w)
+ winunlock(w);
+ goto Continue;
+ }
+ Continue:
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+/*
+ * There is a race between process exiting and our finding out it was ever created.
+ * This structure keeps a list of processes that have exited we haven't heard of.
+ */
+typedef struct Pid Pid;
+struct Pid
+{
+ int pid;
+ char msg[ERRMAX];
+ Pid *next;
+};
+
+void
+waitthread(void *)
+{
+ Waitmsg *w;
+ Command *c, *lc;
+ uint pid;
+ int found, ncmd;
+ Rune *cmd;
+ char *err;
+ Text *t;
+ Pid *pids, *p, *lastp;
+ enum { WErr, WKill, WWait, WCmd, NWALT };
+ Alt alts[NWALT+1];
+
+ threadsetname("waitthread");
+ pids = nil;
+ alts[WErr].c = cerr;
+ alts[WErr].v = &err;
+ alts[WErr].op = CHANRCV;
+ alts[WKill].c = ckill;
+ alts[WKill].v = &cmd;
+ alts[WKill].op = CHANRCV;
+ alts[WWait].c = cwait;
+ alts[WWait].v = &w;
+ alts[WWait].op = CHANRCV;
+ alts[WCmd].c = ccommand;
+ alts[WCmd].v = &c;
+ alts[WCmd].op = CHANRCV;
+ alts[NWALT].op = CHANEND;
+
+ command = nil;
+ for(;;){
+ switch(alt(alts)){
+ case WErr:
+ qlock(&row);
+ warning(nil, "%s", err);
+ free(err);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ case WKill:
+ found = FALSE;
+ ncmd = runestrlen(cmd);
+ for(c=command; c; c=c->next){
+ /* -1 for blank */
+ if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
+ if(postnote(PNGROUP, c->pid, "kill") < 0)
+ warning(nil, "kill %S: %r\n", cmd);
+ found = TRUE;
+ }
+ }
+ if(!found)
+ warning(nil, "Kill: no process %S\n", cmd);
+ free(cmd);
+ break;
+ case WWait:
+ pid = w->pid;
+ lc = nil;
+ for(c=command; c; c=c->next){
+ if(c->pid == pid){
+ if(lc)
+ lc->next = c->next;
+ else
+ command = c->next;
+ break;
+ }
+ lc = c;
+ }
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ if(c == nil){
+ /* helper processes use this exit status */
+ if(strncmp(w->msg, "libthread", 9) != 0){
+ p = emalloc(sizeof(Pid));
+ p->pid = pid;
+ strncpy(p->msg, w->msg, sizeof(p->msg));
+ p->next = pids;
+ pids = p;
+ }
+ }else{
+ if(search(t, c->name, c->nname)){
+ textdelete(t, t->q0, t->q1, TRUE);
+ textsetselect(t, 0, 0);
+ }
+ if(w->msg[0])
+ warning(c->md, "%s\n", w->msg);
+ flushimage(display, 1);
+ }
+ qunlock(&row);
+ free(w);
+ Freecmd:
+ if(c){
+ if(c->iseditcmd)
+ sendul(cedit, 0);
+ free(c->text);
+ free(c->name);
+ fsysdelid(c->md);
+ free(c);
+ }
+ break;
+ case WCmd:
+ /* has this command already exited? */
+ lastp = nil;
+ for(p=pids; p!=nil; p=p->next){
+ if(p->pid == c->pid){
+ if(p->msg[0])
+ warning(c->md, "%s\n", p->msg);
+ if(lastp == nil)
+ pids = p->next;
+ else
+ lastp->next = p->next;
+ free(p);
+ goto Freecmd;
+ }
+ lastp = p;
+ }
+ c->next = command;
+ command = c;
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ textinsert(t, 0, c->name, c->nname, TRUE);
+ textsetselect(t, 0, 0);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+void
+xfidallocthread(void*)
+{
+ Xfid *xfree, *x;
+ enum { Alloc, Free, N };
+ static Alt alts[N+1];
+
+ threadsetname("xfidallocthread");
+ alts[Alloc].c = cxfidalloc;
+ alts[Alloc].v = nil;
+ alts[Alloc].op = CHANRCV;
+ alts[Free].c = cxfidfree;
+ alts[Free].v = &x;
+ alts[Free].op = CHANRCV;
+ alts[N].op = CHANEND;
+
+ xfree = nil;
+ for(;;){
+ switch(alt(alts)){
+ case Alloc:
+ x = xfree;
+ if(x)
+ xfree = x->next;
+ else{
+ x = emalloc(sizeof(Xfid));
+ x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+ x->arg = x;
+ threadcreate(xfidctl, x->arg, STACK);
+ }
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ x->next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void*)
+{
+ Window *w;
+
+ threadsetname("newwindowthread");
+
+ for(;;){
+ /* only fsysproc is talking to us, so synchronization is trivial */
+ recvp(cnewwindow);
+ w = makenewwindow(nil);
+ winsettag(w);
+ xfidlog(w, "new");
+ sendp(cnewwindow, w);
+ }
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+ Reffont *r;
+ Font *f;
+ int i;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(strcmp(name, fontcache[i]->f->name) == 0){
+ r = fontcache[i];
+ goto Found;
+ }
+ f = openfont(display, name);
+ if(f == nil){
+ warning(nil, "can't open font file %s: %r\n", name);
+ return nil;
+ }
+ r = emalloc(sizeof(Reffont));
+ r->f = f;
+ fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(r);
+ if(reffonts[fix])
+ rfclose(reffonts[fix]);
+ reffonts[fix] = r;
+ if(name != fontnames[fix]){
+ free(fontnames[fix]);
+ fontnames[fix] = estrdup(name);
+ }
+ }
+ if(setfont){
+ reffont.f = r->f;
+ incref(r);
+ rfclose(reffonts[0]);
+ font = r->f;
+ reffonts[0] = r;
+ incref(r);
+ iconinit();
+ }
+ incref(r);
+ return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+ int i;
+
+ if(decref(r) == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ nfontcache--;
+ memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+ }
+ freefont(r->f);
+ free(r);
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ /* Blue */
+ tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Yellow */
+ textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ r = Rect(0, 0, Scrollwid+2, font->height+1);
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(modbutton, r, 2, tagcols[BORD], ZP);
+ r = insetrect(r, 2);
+ tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+ draw(modbutton, r, tmp, nil, ZP);
+ freeimage(tmp);
+
+ r = button->r;
+ colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+ but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+ but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fprint(fd, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf()
+{
+ int nulls;
+
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ if(snarffd < 0)
+ return;
+ seek(snarffd, 0, 0);
+ bufreset(&snarfbuf);
+ bufload(&snarfbuf, 0, snarffd, &nulls);
+}
diff --git a/patch/acme-fixfont/acme.c.backup b/patch/acme-fixfont/acme.c.backup
new file mode 100644
index 0000000..25a2477
--- /dev/null
+++ b/patch/acme-fixfont/acme.c.backup
@@ -0,0 +1,968 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+ /* for generating syms in mkfile only: */
+ #include <bio.h>
+ #include "edit.h"
+
+void mousethread(void*);
+void keyboardthread(void*);
+void waitthread(void*);
+void xfidallocthread(void*);
+void newwindowthread(void*);
+void plumbproc(void*);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int plumbsendfd;
+int plumbeditfd;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2];
+
+Command *command;
+
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+int shutdown(void*, char*);
+
+void
+derror(Display*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+ char *p, *loadfile;
+ char buf[256];
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ loadfile = nil;
+ ARGBEGIN{
+ case 'a':
+ globalindent[AUTOINDENT] = TRUE;
+ break;
+ case 'b':
+ bartflag = TRUE;
+ break;
+ case 'c':
+ p = ARGF();
+ if(p == nil)
+ goto Usage;
+ ncol = atoi(p);
+ if(ncol <= 0)
+ goto Usage;
+ break;
+ case 'f':
+ fontnames[0] = ARGF();
+ if(fontnames[0] == nil)
+ goto Usage;
+ break;
+ case 'F':
+ fontnames[1] = ARGF();
+ if(fontnames[1] == nil)
+ goto Usage;
+ break;
+ case 'i':
+ globalindent[SPACESINDENT] = TRUE;
+ break;
+ case 'l':
+ loadfile = ARGF();
+ if(loadfile == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
+ exits("usage");
+ }ARGEND
+
+ if(fontnames[0] == nil)
+ fontnames[0] = getenv("font");
+ if(fontnames[0] == nil)
+ fontnames[0] = "/lib/font/bit/vga/unicode.font";
+ if(access(fontnames[0], 0) < 0){
+ fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
+ exits("font open");
+ }
+ if(fontnames[1] == nil)
+ fontnames[1] = fontnames[0];
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("home");
+ p = getenv("tabstop");
+ if(p != nil){
+ maxtab = strtoul(p, nil, 0);
+ free(p);
+ }
+ if(maxtab == 0)
+ maxtab = 4;
+ if(loadfile)
+ rowloadfonts(loadfile);
+ putenv("font", fontnames[0]);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(cputype){
+ sprint(buf, "/acme/bin/%s", cputype);
+ bind(buf, "/bin", MBEFORE);
+ }
+ bind("/acme/bin", "/bin", MBEFORE);
+ getwd(wdir, sizeof wdir);
+
+ if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ exits("geninitdraw");
+ }
+ d = display;
+ font = d->defaultfont;
+
+ reffont.f = font;
+ reffonts[0] = &reffont;
+ incref(&reffont); /* one to hold up 'font' variable */
+ incref(&reffont); /* one to hold up reffonts[0] */
+ fontcache = emalloc(sizeof(Reffont*));
+ nfontcache = 1;
+ fontcache[0] = &reffont;
+
+ iconinit();
+ timerinit();
+ rxinit();
+
+ cwait = threadwaitchan();
+ ccommand = chancreate(sizeof(Command**), 0);
+ ckill = chancreate(sizeof(Rune*), 0);
+ cxfidalloc = chancreate(sizeof(Xfid*), 0);
+ cxfidfree = chancreate(sizeof(Xfid*), 0);
+ cnewwindow = chancreate(sizeof(Channel*), 0);
+ cerr = chancreate(sizeof(char*), 0);
+ cedit = chancreate(sizeof(int), 0);
+ cexit = chancreate(sizeof(int), 0);
+ cwarn = chancreate(sizeof(void*), 1);
+ if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ rowinit(&row, screen->clipr);
+ if(ncol < 0){
+ if(argc == 0)
+ ncol = 2;
+ else{
+ ncol = (argc+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(argc == 0){
+ Window *w = errorwin(nil, 'E');
+ winunlock(w);
+ readfile(row.col[0], wdir);
+ }else
+ for(i=0; i<argc; i++){
+ p = utfrrune(argv[i], '/');
+ if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+ readfile(c, argv[i]);
+ else
+ readfile(row.col[i/WPERCOL], argv[i]);
+ }
+ }
+ flushimage(display, 1);
+
+ acmeerrorinit();
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(waitthread, nil, STACK);
+ threadcreate(xfidallocthread, nil, STACK);
+ threadcreate(newwindowthread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nb, nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ textload(&w->body, 0, s, 1);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
+ xfidlog(w, "new");
+}
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+ dumping = TRUE;
+ rowdump(&row, nil);
+ }
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("acme: %s\n", msg);
+ abort();
+ return 0;
+}
+
+void
+killprocs(void)
+{
+ Command *c;
+
+ fsysclose();
+// if(display)
+// flushimage(display, 1);
+
+ for(c=command; c; c=c->next)
+ postnote(PNGROUP, c->pid, "hangup");
+ remove(acmeerrorfile);
+}
+
+static int errorfd;
+
+void
+acmeerrorproc(void *)
+{
+ char *buf, *s;
+ int n;
+
+ threadsetname("acmeerrorproc");
+ buf = emalloc(8192+1);
+ while((n=read(errorfd, buf, 8192)) >= 0){
+ buf[n] = '\0';
+ s = estrdup(buf);
+ sendp(cerr, s);
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int fd, pfd[2];
+ char buf[64];
+
+ if(pipe(pfd) < 0)
+ error("can't create pipe");
+ sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0){
+ remove(acmeerrorfile);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0)
+ error("can't create acmeerror file");
+ }
+ sprint(buf, "%d", pfd[0]);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ /* reopen pfd[1] close on exec */
+ sprint(buf, "/fd/%d", pfd[1]);
+ errorfd = open(buf, OREAD|OCEXEC);
+ if(errorfd < 0)
+ error("can't re-open acmeerror file");
+ close(pfd[1]);
+ close(pfd[0]);
+ proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbeditfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+void
+keyboardthread(void *)
+{
+ Rune r;
+ Timer *timer;
+ Text *t;
+ enum { KTimer, KKey, NKALT };
+ static Alt alts[NKALT+1];
+
+ alts[KTimer].c = nil;
+ alts[KTimer].v = nil;
+ alts[KTimer].op = CHANNOP;
+ alts[KKey].c = keyboardctl->c;
+ alts[KKey].v = &r;
+ alts[KKey].op = CHANRCV;
+ alts[NKALT].op = CHANEND;
+
+ timer = nil;
+ typetext = nil;
+ threadsetname("keyboardthread");
+ for(;;){
+ switch(alt(alts)){
+ case KTimer:
+ timerstop(timer);
+ t = typetext;
+ if(t!=nil && t->what==Tag){
+ winlock(t->w, 'K');
+ wincommit(t->w, t);
+ winunlock(t->w);
+ flushimage(display, 1);
+ }
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ break;
+ case KKey:
+ casekeyboard:
+ typetext = rowtype(&row, r, mouse->xy);
+ t = typetext;
+ if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
+ activecol = t->col;
+ if(t!=nil && t->w!=nil)
+ t->w->body.file->curtext = &t->w->body;
+ if(timer != nil)
+ timercancel(timer);
+ if(t!=nil && t->what==Tag) {
+ timer = timerstart(500);
+ alts[KTimer].c = timer->c;
+ alts[KTimer].op = CHANRCV;
+ }else{
+ timer = nil;
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ }
+ if(nbrecv(keyboardctl->c, &r) > 0)
+ goto casekeyboard;
+ flushimage(display, 1);
+ break;
+ }
+ }
+}
+
+void
+mousethread(void *)
+{
+ Text *t, *argt;
+ int but;
+ uint q0, q1;
+ Window *w;
+ Plumbmsg *pm;
+ Mouse m;
+ char *act;
+ enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
+ static Alt alts[NMALT+1];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ alts[MMouse].op = CHANRCV;
+ alts[MPlumb].c = cplumb;
+ alts[MPlumb].v = &pm;
+ alts[MPlumb].op = CHANRCV;
+ alts[MWarnings].c = cwarn;
+ alts[MWarnings].v = nil;
+ alts[MWarnings].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row);
+ flushwarnings();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ scrlresize();
+ rowresize(&row, screen->clipr);
+ break;
+ case MPlumb:
+ if(strcmp(pm->type, "text") == 0){
+ act = plumblookup(pm->attr, "action");
+ if(act==nil || strcmp(act, "showfile")==0)
+ plumblook(pm);
+ else if(strcmp(act, "showdata")==0)
+ plumbshow(pm);
+ }
+ plumbfree(pm);
+ break;
+ case MWarnings:
+ break;
+ case MMouse:
+ /*
+ * Make a copy so decisions are consistent; mousectl changes
+ * underfoot. Can't just receive into m because this introduces
+ * another race; see /sys/src/libdraw/mouse.c.
+ */
+ m = mousectl->Mouse;
+ qlock(&row);
+ t = rowwhich(&row, m.xy);
+
+ if((t!=mousetext && t!=nil && t->w!=nil) &&
+ (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
+ xfidlog(t->w, "focus");
+ }
+
+ if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
+ winlock(mousetext->w, 'M');
+ mousetext->eq0 = ~0;
+ wincommit(mousetext->w, mousetext);
+ winunlock(mousetext->w);
+ }
+ mousetext = t;
+ if(t == nil)
+ goto Continue;
+ w = t->w;
+ if(t==nil || m.buttons==0)
+ goto Continue;
+ but = 0;
+ if(m.buttons == 1)
+ but = 1;
+ else if(m.buttons == 2)
+ but = 2;
+ else if(m.buttons == 4)
+ but = 3;
+ barttext = t;
+ if(t->what==Body && ptinrect(m.xy, t->scrollr)){
+ if(but){
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(t->what==Body && w != nil && (m.buttons & (8|16))){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ texttype(t, but);
+ winunlock(w);
+ goto Continue;
+ }
+ if(ptinrect(m.xy, t->scrollr)){
+ if(but){
+ if(t->what == Columntag)
+ rowdragcol(&row, t->col, but);
+ else if(t->what == Tag){
+ coldragwin(t->col, t->w, but);
+ if(t->w)
+ barttext = &t->w->body;
+ }
+ if(t->col)
+ activecol = t->col;
+ }
+ goto Continue;
+ }
+ if(m.buttons){
+ if(w)
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ if(w)
+ wincommit(w, t);
+ else
+ textcommit(t, TRUE);
+ if(m.buttons & 1){
+ textselect(t);
+ if(w)
+ winsettag(w);
+ argtext = t;
+ seltext = t;
+ if(t->col)
+ activecol = t->col; /* button 1 only */
+ if(t->w!=nil && t==&t->w->body)
+ activewin = t->w;
+ }else if(m.buttons & 2){
+ if(textselect2(t, &q0, &q1, &argt))
+ execute(t, q0, q1, FALSE, argt);
+ }else if(m.buttons & 4){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1, FALSE);
+ }
+ if(w)
+ winunlock(w);
+ goto Continue;
+ }
+ Continue:
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+/*
+ * There is a race between process exiting and our finding out it was ever created.
+ * This structure keeps a list of processes that have exited we haven't heard of.
+ */
+typedef struct Pid Pid;
+struct Pid
+{
+ int pid;
+ char msg[ERRMAX];
+ Pid *next;
+};
+
+void
+waitthread(void *)
+{
+ Waitmsg *w;
+ Command *c, *lc;
+ uint pid;
+ int found, ncmd;
+ Rune *cmd;
+ char *err;
+ Text *t;
+ Pid *pids, *p, *lastp;
+ enum { WErr, WKill, WWait, WCmd, NWALT };
+ Alt alts[NWALT+1];
+
+ threadsetname("waitthread");
+ pids = nil;
+ alts[WErr].c = cerr;
+ alts[WErr].v = &err;
+ alts[WErr].op = CHANRCV;
+ alts[WKill].c = ckill;
+ alts[WKill].v = &cmd;
+ alts[WKill].op = CHANRCV;
+ alts[WWait].c = cwait;
+ alts[WWait].v = &w;
+ alts[WWait].op = CHANRCV;
+ alts[WCmd].c = ccommand;
+ alts[WCmd].v = &c;
+ alts[WCmd].op = CHANRCV;
+ alts[NWALT].op = CHANEND;
+
+ command = nil;
+ for(;;){
+ switch(alt(alts)){
+ case WErr:
+ qlock(&row);
+ warning(nil, "%s", err);
+ free(err);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ case WKill:
+ found = FALSE;
+ ncmd = runestrlen(cmd);
+ for(c=command; c; c=c->next){
+ /* -1 for blank */
+ if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
+ if(postnote(PNGROUP, c->pid, "kill") < 0)
+ warning(nil, "kill %S: %r\n", cmd);
+ found = TRUE;
+ }
+ }
+ if(!found)
+ warning(nil, "Kill: no process %S\n", cmd);
+ free(cmd);
+ break;
+ case WWait:
+ pid = w->pid;
+ lc = nil;
+ for(c=command; c; c=c->next){
+ if(c->pid == pid){
+ if(lc)
+ lc->next = c->next;
+ else
+ command = c->next;
+ break;
+ }
+ lc = c;
+ }
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ if(c == nil){
+ /* helper processes use this exit status */
+ if(strncmp(w->msg, "libthread", 9) != 0){
+ p = emalloc(sizeof(Pid));
+ p->pid = pid;
+ strncpy(p->msg, w->msg, sizeof(p->msg));
+ p->next = pids;
+ pids = p;
+ }
+ }else{
+ if(search(t, c->name, c->nname)){
+ textdelete(t, t->q0, t->q1, TRUE);
+ textsetselect(t, 0, 0);
+ }
+ if(w->msg[0])
+ warning(c->md, "%s\n", w->msg);
+ flushimage(display, 1);
+ }
+ qunlock(&row);
+ free(w);
+ Freecmd:
+ if(c){
+ if(c->iseditcmd)
+ sendul(cedit, 0);
+ free(c->text);
+ free(c->name);
+ fsysdelid(c->md);
+ free(c);
+ }
+ break;
+ case WCmd:
+ /* has this command already exited? */
+ lastp = nil;
+ for(p=pids; p!=nil; p=p->next){
+ if(p->pid == c->pid){
+ if(p->msg[0])
+ warning(c->md, "%s\n", p->msg);
+ if(lastp == nil)
+ pids = p->next;
+ else
+ lastp->next = p->next;
+ free(p);
+ goto Freecmd;
+ }
+ lastp = p;
+ }
+ c->next = command;
+ command = c;
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ textinsert(t, 0, c->name, c->nname, TRUE);
+ textsetselect(t, 0, 0);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+void
+xfidallocthread(void*)
+{
+ Xfid *xfree, *x;
+ enum { Alloc, Free, N };
+ static Alt alts[N+1];
+
+ threadsetname("xfidallocthread");
+ alts[Alloc].c = cxfidalloc;
+ alts[Alloc].v = nil;
+ alts[Alloc].op = CHANRCV;
+ alts[Free].c = cxfidfree;
+ alts[Free].v = &x;
+ alts[Free].op = CHANRCV;
+ alts[N].op = CHANEND;
+
+ xfree = nil;
+ for(;;){
+ switch(alt(alts)){
+ case Alloc:
+ x = xfree;
+ if(x)
+ xfree = x->next;
+ else{
+ x = emalloc(sizeof(Xfid));
+ x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+ x->arg = x;
+ threadcreate(xfidctl, x->arg, STACK);
+ }
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ x->next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void*)
+{
+ Window *w;
+
+ threadsetname("newwindowthread");
+
+ for(;;){
+ /* only fsysproc is talking to us, so synchronization is trivial */
+ recvp(cnewwindow);
+ w = makenewwindow(nil);
+ winsettag(w);
+ xfidlog(w, "new");
+ sendp(cnewwindow, w);
+ }
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+ Reffont *r;
+ Font *f;
+ int i;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(strcmp(name, fontcache[i]->f->name) == 0){
+ r = fontcache[i];
+ goto Found;
+ }
+ f = openfont(display, name);
+ if(f == nil){
+ warning(nil, "can't open font file %s: %r\n", name);
+ return nil;
+ }
+ r = emalloc(sizeof(Reffont));
+ r->f = f;
+ fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(r);
+ if(reffonts[fix])
+ rfclose(reffonts[fix]);
+ reffonts[fix] = r;
+ if(name != fontnames[fix]){
+ free(fontnames[fix]);
+ fontnames[fix] = estrdup(name);
+ }
+ }
+ if(setfont){
+ reffont.f = r->f;
+ incref(r);
+ rfclose(reffonts[0]);
+ font = r->f;
+ reffonts[0] = r;
+ incref(r);
+ iconinit();
+ }
+ incref(r);
+ return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+ int i;
+
+ if(decref(r) == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ nfontcache--;
+ memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+ }
+ freefont(r->f);
+ free(r);
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ /* Blue */
+ tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Yellow */
+ textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ r = Rect(0, 0, Scrollwid+2, font->height+1);
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(modbutton, r, 2, tagcols[BORD], ZP);
+ r = insetrect(r, 2);
+ tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+ draw(modbutton, r, tmp, nil, ZP);
+ freeimage(tmp);
+
+ r = button->r;
+ colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+ but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+ but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fprint(fd, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf()
+{
+ int nulls;
+
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ if(snarffd < 0)
+ return;
+ seek(snarffd, 0, 0);
+ bufreset(&snarfbuf);
+ bufload(&snarfbuf, 0, snarffd, &nulls);
+}
diff --git a/patch/acme-fixfont/acme.c.new b/patch/acme-fixfont/acme.c.new
new file mode 100644
index 0000000..90ff9c5
--- /dev/null
+++ b/patch/acme-fixfont/acme.c.new
@@ -0,0 +1,971 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+ /* for generating syms in mkfile only: */
+ #include <bio.h>
+ #include "edit.h"
+
+void mousethread(void*);
+void keyboardthread(void*);
+void waitthread(void*);
+void xfidallocthread(void*);
+void newwindowthread(void*);
+void plumbproc(void*);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int plumbsendfd;
+int plumbeditfd;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2];
+
+Command *command;
+
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+int shutdown(void*, char*);
+
+void
+derror(Display*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+ char *p, *loadfile;
+ char buf[256];
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ loadfile = nil;
+ ARGBEGIN{
+ case 'a':
+ globalindent[AUTOINDENT] = TRUE;
+ break;
+ case 'b':
+ bartflag = TRUE;
+ break;
+ case 'c':
+ p = ARGF();
+ if(p == nil)
+ goto Usage;
+ ncol = atoi(p);
+ if(ncol <= 0)
+ goto Usage;
+ break;
+ case 'f':
+ fontnames[0] = ARGF();
+ if(fontnames[0] == nil)
+ goto Usage;
+ break;
+ case 'F':
+ fontnames[1] = ARGF();
+ if(fontnames[1] == nil)
+ goto Usage;
+ break;
+ case 'i':
+ globalindent[SPACESINDENT] = TRUE;
+ break;
+ case 'l':
+ loadfile = ARGF();
+ if(loadfile == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
+ exits("usage");
+ }ARGEND
+
+ if(fontnames[0] == nil)
+ fontnames[0] = getenv("font");
+ if(fontnames[0] == nil)
+ fontnames[0] = "/lib/font/bit/vga/unicode.font";
+ if(access(fontnames[0], 0) < 0){
+ fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
+ exits("font open");
+ }
+ if(fontnames[1] == nil)
+ fontnames[1] = getenv("fixfont");
+ if(fontnames[1] == nil)
+ fontnames[1] = fontnames[0];
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("home");
+ p = getenv("tabstop");
+ if(p != nil){
+ maxtab = strtoul(p, nil, 0);
+ free(p);
+ }
+ if(maxtab == 0)
+ maxtab = 4;
+ if(loadfile)
+ rowloadfonts(loadfile);
+ putenv("font", fontnames[0]);
+ putenv("fixfont", fontnames[1]);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(cputype){
+ sprint(buf, "/acme/bin/%s", cputype);
+ bind(buf, "/bin", MBEFORE);
+ }
+ bind("/acme/bin", "/bin", MBEFORE);
+ getwd(wdir, sizeof wdir);
+
+ if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ exits("geninitdraw");
+ }
+ d = display;
+ font = d->defaultfont;
+
+ reffont.f = font;
+ reffonts[0] = &reffont;
+ incref(&reffont); /* one to hold up 'font' variable */
+ incref(&reffont); /* one to hold up reffonts[0] */
+ fontcache = emalloc(sizeof(Reffont*));
+ nfontcache = 1;
+ fontcache[0] = &reffont;
+
+ iconinit();
+ timerinit();
+ rxinit();
+
+ cwait = threadwaitchan();
+ ccommand = chancreate(sizeof(Command**), 0);
+ ckill = chancreate(sizeof(Rune*), 0);
+ cxfidalloc = chancreate(sizeof(Xfid*), 0);
+ cxfidfree = chancreate(sizeof(Xfid*), 0);
+ cnewwindow = chancreate(sizeof(Channel*), 0);
+ cerr = chancreate(sizeof(char*), 0);
+ cedit = chancreate(sizeof(int), 0);
+ cexit = chancreate(sizeof(int), 0);
+ cwarn = chancreate(sizeof(void*), 1);
+ if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ rowinit(&row, screen->clipr);
+ if(ncol < 0){
+ if(argc == 0)
+ ncol = 2;
+ else{
+ ncol = (argc+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(argc == 0){
+ Window *w = errorwin(nil, 'E');
+ winunlock(w);
+ readfile(row.col[0], wdir);
+ }else
+ for(i=0; i<argc; i++){
+ p = utfrrune(argv[i], '/');
+ if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+ readfile(c, argv[i]);
+ else
+ readfile(row.col[i/WPERCOL], argv[i]);
+ }
+ }
+ flushimage(display, 1);
+
+ acmeerrorinit();
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(waitthread, nil, STACK);
+ threadcreate(xfidallocthread, nil, STACK);
+ threadcreate(newwindowthread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nb, nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ textload(&w->body, 0, s, 1);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
+ xfidlog(w, "new");
+}
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+ dumping = TRUE;
+ rowdump(&row, nil);
+ }
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("acme: %s\n", msg);
+ abort();
+ return 0;
+}
+
+void
+killprocs(void)
+{
+ Command *c;
+
+ fsysclose();
+// if(display)
+// flushimage(display, 1);
+
+ for(c=command; c; c=c->next)
+ postnote(PNGROUP, c->pid, "hangup");
+ remove(acmeerrorfile);
+}
+
+static int errorfd;
+
+void
+acmeerrorproc(void *)
+{
+ char *buf, *s;
+ int n;
+
+ threadsetname("acmeerrorproc");
+ buf = emalloc(8192+1);
+ while((n=read(errorfd, buf, 8192)) >= 0){
+ buf[n] = '\0';
+ s = estrdup(buf);
+ sendp(cerr, s);
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int fd, pfd[2];
+ char buf[64];
+
+ if(pipe(pfd) < 0)
+ error("can't create pipe");
+ sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0){
+ remove(acmeerrorfile);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0)
+ error("can't create acmeerror file");
+ }
+ sprint(buf, "%d", pfd[0]);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ /* reopen pfd[1] close on exec */
+ sprint(buf, "/fd/%d", pfd[1]);
+ errorfd = open(buf, OREAD|OCEXEC);
+ if(errorfd < 0)
+ error("can't re-open acmeerror file");
+ close(pfd[1]);
+ close(pfd[0]);
+ proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbeditfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+void
+keyboardthread(void *)
+{
+ Rune r;
+ Timer *timer;
+ Text *t;
+ enum { KTimer, KKey, NKALT };
+ static Alt alts[NKALT+1];
+
+ alts[KTimer].c = nil;
+ alts[KTimer].v = nil;
+ alts[KTimer].op = CHANNOP;
+ alts[KKey].c = keyboardctl->c;
+ alts[KKey].v = &r;
+ alts[KKey].op = CHANRCV;
+ alts[NKALT].op = CHANEND;
+
+ timer = nil;
+ typetext = nil;
+ threadsetname("keyboardthread");
+ for(;;){
+ switch(alt(alts)){
+ case KTimer:
+ timerstop(timer);
+ t = typetext;
+ if(t!=nil && t->what==Tag){
+ winlock(t->w, 'K');
+ wincommit(t->w, t);
+ winunlock(t->w);
+ flushimage(display, 1);
+ }
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ break;
+ case KKey:
+ casekeyboard:
+ typetext = rowtype(&row, r, mouse->xy);
+ t = typetext;
+ if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
+ activecol = t->col;
+ if(t!=nil && t->w!=nil)
+ t->w->body.file->curtext = &t->w->body;
+ if(timer != nil)
+ timercancel(timer);
+ if(t!=nil && t->what==Tag) {
+ timer = timerstart(500);
+ alts[KTimer].c = timer->c;
+ alts[KTimer].op = CHANRCV;
+ }else{
+ timer = nil;
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ }
+ if(nbrecv(keyboardctl->c, &r) > 0)
+ goto casekeyboard;
+ flushimage(display, 1);
+ break;
+ }
+ }
+}
+
+void
+mousethread(void *)
+{
+ Text *t, *argt;
+ int but;
+ uint q0, q1;
+ Window *w;
+ Plumbmsg *pm;
+ Mouse m;
+ char *act;
+ enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
+ static Alt alts[NMALT+1];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ alts[MMouse].op = CHANRCV;
+ alts[MPlumb].c = cplumb;
+ alts[MPlumb].v = &pm;
+ alts[MPlumb].op = CHANRCV;
+ alts[MWarnings].c = cwarn;
+ alts[MWarnings].v = nil;
+ alts[MWarnings].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row);
+ flushwarnings();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ scrlresize();
+ rowresize(&row, screen->clipr);
+ break;
+ case MPlumb:
+ if(strcmp(pm->type, "text") == 0){
+ act = plumblookup(pm->attr, "action");
+ if(act==nil || strcmp(act, "showfile")==0)
+ plumblook(pm);
+ else if(strcmp(act, "showdata")==0)
+ plumbshow(pm);
+ }
+ plumbfree(pm);
+ break;
+ case MWarnings:
+ break;
+ case MMouse:
+ /*
+ * Make a copy so decisions are consistent; mousectl changes
+ * underfoot. Can't just receive into m because this introduces
+ * another race; see /sys/src/libdraw/mouse.c.
+ */
+ m = mousectl->Mouse;
+ qlock(&row);
+ t = rowwhich(&row, m.xy);
+
+ if((t!=mousetext && t!=nil && t->w!=nil) &&
+ (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
+ xfidlog(t->w, "focus");
+ }
+
+ if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
+ winlock(mousetext->w, 'M');
+ mousetext->eq0 = ~0;
+ wincommit(mousetext->w, mousetext);
+ winunlock(mousetext->w);
+ }
+ mousetext = t;
+ if(t == nil)
+ goto Continue;
+ w = t->w;
+ if(t==nil || m.buttons==0)
+ goto Continue;
+ but = 0;
+ if(m.buttons == 1)
+ but = 1;
+ else if(m.buttons == 2)
+ but = 2;
+ else if(m.buttons == 4)
+ but = 3;
+ barttext = t;
+ if(t->what==Body && ptinrect(m.xy, t->scrollr)){
+ if(but){
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(t->what==Body && w != nil && (m.buttons & (8|16))){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ texttype(t, but);
+ winunlock(w);
+ goto Continue;
+ }
+ if(ptinrect(m.xy, t->scrollr)){
+ if(but){
+ if(t->what == Columntag)
+ rowdragcol(&row, t->col, but);
+ else if(t->what == Tag){
+ coldragwin(t->col, t->w, but);
+ if(t->w)
+ barttext = &t->w->body;
+ }
+ if(t->col)
+ activecol = t->col;
+ }
+ goto Continue;
+ }
+ if(m.buttons){
+ if(w)
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ if(w)
+ wincommit(w, t);
+ else
+ textcommit(t, TRUE);
+ if(m.buttons & 1){
+ textselect(t);
+ if(w)
+ winsettag(w);
+ argtext = t;
+ seltext = t;
+ if(t->col)
+ activecol = t->col; /* button 1 only */
+ if(t->w!=nil && t==&t->w->body)
+ activewin = t->w;
+ }else if(m.buttons & 2){
+ if(textselect2(t, &q0, &q1, &argt))
+ execute(t, q0, q1, FALSE, argt);
+ }else if(m.buttons & 4){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1, FALSE);
+ }
+ if(w)
+ winunlock(w);
+ goto Continue;
+ }
+ Continue:
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+/*
+ * There is a race between process exiting and our finding out it was ever created.
+ * This structure keeps a list of processes that have exited we haven't heard of.
+ */
+typedef struct Pid Pid;
+struct Pid
+{
+ int pid;
+ char msg[ERRMAX];
+ Pid *next;
+};
+
+void
+waitthread(void *)
+{
+ Waitmsg *w;
+ Command *c, *lc;
+ uint pid;
+ int found, ncmd;
+ Rune *cmd;
+ char *err;
+ Text *t;
+ Pid *pids, *p, *lastp;
+ enum { WErr, WKill, WWait, WCmd, NWALT };
+ Alt alts[NWALT+1];
+
+ threadsetname("waitthread");
+ pids = nil;
+ alts[WErr].c = cerr;
+ alts[WErr].v = &err;
+ alts[WErr].op = CHANRCV;
+ alts[WKill].c = ckill;
+ alts[WKill].v = &cmd;
+ alts[WKill].op = CHANRCV;
+ alts[WWait].c = cwait;
+ alts[WWait].v = &w;
+ alts[WWait].op = CHANRCV;
+ alts[WCmd].c = ccommand;
+ alts[WCmd].v = &c;
+ alts[WCmd].op = CHANRCV;
+ alts[NWALT].op = CHANEND;
+
+ command = nil;
+ for(;;){
+ switch(alt(alts)){
+ case WErr:
+ qlock(&row);
+ warning(nil, "%s", err);
+ free(err);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ case WKill:
+ found = FALSE;
+ ncmd = runestrlen(cmd);
+ for(c=command; c; c=c->next){
+ /* -1 for blank */
+ if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
+ if(postnote(PNGROUP, c->pid, "kill") < 0)
+ warning(nil, "kill %S: %r\n", cmd);
+ found = TRUE;
+ }
+ }
+ if(!found)
+ warning(nil, "Kill: no process %S\n", cmd);
+ free(cmd);
+ break;
+ case WWait:
+ pid = w->pid;
+ lc = nil;
+ for(c=command; c; c=c->next){
+ if(c->pid == pid){
+ if(lc)
+ lc->next = c->next;
+ else
+ command = c->next;
+ break;
+ }
+ lc = c;
+ }
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ if(c == nil){
+ /* helper processes use this exit status */
+ if(strncmp(w->msg, "libthread", 9) != 0){
+ p = emalloc(sizeof(Pid));
+ p->pid = pid;
+ strncpy(p->msg, w->msg, sizeof(p->msg));
+ p->next = pids;
+ pids = p;
+ }
+ }else{
+ if(search(t, c->name, c->nname)){
+ textdelete(t, t->q0, t->q1, TRUE);
+ textsetselect(t, 0, 0);
+ }
+ if(w->msg[0])
+ warning(c->md, "%s\n", w->msg);
+ flushimage(display, 1);
+ }
+ qunlock(&row);
+ free(w);
+ Freecmd:
+ if(c){
+ if(c->iseditcmd)
+ sendul(cedit, 0);
+ free(c->text);
+ free(c->name);
+ fsysdelid(c->md);
+ free(c);
+ }
+ break;
+ case WCmd:
+ /* has this command already exited? */
+ lastp = nil;
+ for(p=pids; p!=nil; p=p->next){
+ if(p->pid == c->pid){
+ if(p->msg[0])
+ warning(c->md, "%s\n", p->msg);
+ if(lastp == nil)
+ pids = p->next;
+ else
+ lastp->next = p->next;
+ free(p);
+ goto Freecmd;
+ }
+ lastp = p;
+ }
+ c->next = command;
+ command = c;
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ textinsert(t, 0, c->name, c->nname, TRUE);
+ textsetselect(t, 0, 0);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+void
+xfidallocthread(void*)
+{
+ Xfid *xfree, *x;
+ enum { Alloc, Free, N };
+ static Alt alts[N+1];
+
+ threadsetname("xfidallocthread");
+ alts[Alloc].c = cxfidalloc;
+ alts[Alloc].v = nil;
+ alts[Alloc].op = CHANRCV;
+ alts[Free].c = cxfidfree;
+ alts[Free].v = &x;
+ alts[Free].op = CHANRCV;
+ alts[N].op = CHANEND;
+
+ xfree = nil;
+ for(;;){
+ switch(alt(alts)){
+ case Alloc:
+ x = xfree;
+ if(x)
+ xfree = x->next;
+ else{
+ x = emalloc(sizeof(Xfid));
+ x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+ x->arg = x;
+ threadcreate(xfidctl, x->arg, STACK);
+ }
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ x->next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void*)
+{
+ Window *w;
+
+ threadsetname("newwindowthread");
+
+ for(;;){
+ /* only fsysproc is talking to us, so synchronization is trivial */
+ recvp(cnewwindow);
+ w = makenewwindow(nil);
+ winsettag(w);
+ xfidlog(w, "new");
+ sendp(cnewwindow, w);
+ }
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+ Reffont *r;
+ Font *f;
+ int i;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(strcmp(name, fontcache[i]->f->name) == 0){
+ r = fontcache[i];
+ goto Found;
+ }
+ f = openfont(display, name);
+ if(f == nil){
+ warning(nil, "can't open font file %s: %r\n", name);
+ return nil;
+ }
+ r = emalloc(sizeof(Reffont));
+ r->f = f;
+ fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(r);
+ if(reffonts[fix])
+ rfclose(reffonts[fix]);
+ reffonts[fix] = r;
+ if(name != fontnames[fix]){
+ free(fontnames[fix]);
+ fontnames[fix] = estrdup(name);
+ }
+ }
+ if(setfont){
+ reffont.f = r->f;
+ incref(r);
+ rfclose(reffonts[0]);
+ font = r->f;
+ reffonts[0] = r;
+ incref(r);
+ iconinit();
+ }
+ incref(r);
+ return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+ int i;
+
+ if(decref(r) == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ nfontcache--;
+ memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+ }
+ freefont(r->f);
+ free(r);
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ /* Blue */
+ tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Yellow */
+ textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ r = Rect(0, 0, Scrollwid+2, font->height+1);
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(modbutton, r, 2, tagcols[BORD], ZP);
+ r = insetrect(r, 2);
+ tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+ draw(modbutton, r, tmp, nil, ZP);
+ freeimage(tmp);
+
+ r = button->r;
+ colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+ but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+ but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fprint(fd, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf()
+{
+ int nulls;
+
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ if(snarffd < 0)
+ return;
+ seek(snarffd, 0, 0);
+ bufreset(&snarfbuf);
+ bufload(&snarfbuf, 0, snarffd, &nulls);
+}
diff --git a/patch/acme-fixfont/acme.c.orig b/patch/acme-fixfont/acme.c.orig
new file mode 100644
index 0000000..e874d35
--- /dev/null
+++ b/patch/acme-fixfont/acme.c.orig
@@ -0,0 +1,966 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+ /* for generating syms in mkfile only: */
+ #include <bio.h>
+ #include "edit.h"
+
+void mousethread(void*);
+void keyboardthread(void*);
+void waitthread(void*);
+void xfidallocthread(void*);
+void newwindowthread(void*);
+void plumbproc(void*);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int plumbsendfd;
+int plumbeditfd;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2];
+
+Command *command;
+
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+int shutdown(void*, char*);
+
+void
+derror(Display*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+ char *p, *loadfile;
+ char buf[256];
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ loadfile = nil;
+ ARGBEGIN{
+ case 'a':
+ globalindent[AUTOINDENT] = TRUE;
+ break;
+ case 'b':
+ bartflag = TRUE;
+ break;
+ case 'c':
+ p = ARGF();
+ if(p == nil)
+ goto Usage;
+ ncol = atoi(p);
+ if(ncol <= 0)
+ goto Usage;
+ break;
+ case 'f':
+ fontnames[0] = ARGF();
+ if(fontnames[0] == nil)
+ goto Usage;
+ break;
+ case 'F':
+ fontnames[1] = ARGF();
+ if(fontnames[1] == nil)
+ goto Usage;
+ break;
+ case 'i':
+ globalindent[SPACESINDENT] = TRUE;
+ break;
+ case 'l':
+ loadfile = ARGF();
+ if(loadfile == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
+ exits("usage");
+ }ARGEND
+
+ if(fontnames[0] == nil)
+ fontnames[0] = getenv("font");
+ if(fontnames[0] == nil)
+ fontnames[0] = "/lib/font/bit/vga/unicode.font";
+ if(access(fontnames[0], 0) < 0){
+ fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
+ exits("font open");
+ }
+ if(fontnames[1] == nil)
+ fontnames[1] = fontnames[0];
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("home");
+ p = getenv("tabstop");
+ if(p != nil){
+ maxtab = strtoul(p, nil, 0);
+ free(p);
+ }
+ if(maxtab == 0)
+ maxtab = 4;
+ if(loadfile)
+ rowloadfonts(loadfile);
+ putenv("font", fontnames[0]);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(cputype){
+ sprint(buf, "/acme/bin/%s", cputype);
+ bind(buf, "/bin", MBEFORE);
+ }
+ bind("/acme/bin", "/bin", MBEFORE);
+ getwd(wdir, sizeof wdir);
+
+ if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ exits("geninitdraw");
+ }
+ d = display;
+ font = d->defaultfont;
+
+ reffont.f = font;
+ reffonts[0] = &reffont;
+ incref(&reffont); /* one to hold up 'font' variable */
+ incref(&reffont); /* one to hold up reffonts[0] */
+ fontcache = emalloc(sizeof(Reffont*));
+ nfontcache = 1;
+ fontcache[0] = &reffont;
+
+ iconinit();
+ timerinit();
+ rxinit();
+
+ cwait = threadwaitchan();
+ ccommand = chancreate(sizeof(Command**), 0);
+ ckill = chancreate(sizeof(Rune*), 0);
+ cxfidalloc = chancreate(sizeof(Xfid*), 0);
+ cxfidfree = chancreate(sizeof(Xfid*), 0);
+ cnewwindow = chancreate(sizeof(Channel*), 0);
+ cerr = chancreate(sizeof(char*), 0);
+ cedit = chancreate(sizeof(int), 0);
+ cexit = chancreate(sizeof(int), 0);
+ cwarn = chancreate(sizeof(void*), 1);
+ if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ rowinit(&row, screen->clipr);
+ if(ncol < 0){
+ if(argc == 0)
+ ncol = 2;
+ else{
+ ncol = (argc+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(argc == 0)
+ readfile(c, wdir);
+ else
+ for(i=0; i<argc; i++){
+ p = utfrrune(argv[i], '/');
+ if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+ readfile(c, argv[i]);
+ else
+ readfile(row.col[i/WPERCOL], argv[i]);
+ }
+ }
+ flushimage(display, 1);
+
+ acmeerrorinit();
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(waitthread, nil, STACK);
+ threadcreate(xfidallocthread, nil, STACK);
+ threadcreate(newwindowthread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nb, nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ textload(&w->body, 0, s, 1);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
+ xfidlog(w, "new");
+}
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+ dumping = TRUE;
+ rowdump(&row, nil);
+ }
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("acme: %s\n", msg);
+ abort();
+ return 0;
+}
+
+void
+killprocs(void)
+{
+ Command *c;
+
+ fsysclose();
+// if(display)
+// flushimage(display, 1);
+
+ for(c=command; c; c=c->next)
+ postnote(PNGROUP, c->pid, "hangup");
+ remove(acmeerrorfile);
+}
+
+static int errorfd;
+
+void
+acmeerrorproc(void *)
+{
+ char *buf, *s;
+ int n;
+
+ threadsetname("acmeerrorproc");
+ buf = emalloc(8192+1);
+ while((n=read(errorfd, buf, 8192)) >= 0){
+ buf[n] = '\0';
+ s = estrdup(buf);
+ sendp(cerr, s);
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int fd, pfd[2];
+ char buf[64];
+
+ if(pipe(pfd) < 0)
+ error("can't create pipe");
+ sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0){
+ remove(acmeerrorfile);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0)
+ error("can't create acmeerror file");
+ }
+ sprint(buf, "%d", pfd[0]);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ /* reopen pfd[1] close on exec */
+ sprint(buf, "/fd/%d", pfd[1]);
+ errorfd = open(buf, OREAD|OCEXEC);
+ if(errorfd < 0)
+ error("can't re-open acmeerror file");
+ close(pfd[1]);
+ close(pfd[0]);
+ proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbeditfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+void
+keyboardthread(void *)
+{
+ Rune r;
+ Timer *timer;
+ Text *t;
+ enum { KTimer, KKey, NKALT };
+ static Alt alts[NKALT+1];
+
+ alts[KTimer].c = nil;
+ alts[KTimer].v = nil;
+ alts[KTimer].op = CHANNOP;
+ alts[KKey].c = keyboardctl->c;
+ alts[KKey].v = &r;
+ alts[KKey].op = CHANRCV;
+ alts[NKALT].op = CHANEND;
+
+ timer = nil;
+ typetext = nil;
+ threadsetname("keyboardthread");
+ for(;;){
+ switch(alt(alts)){
+ case KTimer:
+ timerstop(timer);
+ t = typetext;
+ if(t!=nil && t->what==Tag){
+ winlock(t->w, 'K');
+ wincommit(t->w, t);
+ winunlock(t->w);
+ flushimage(display, 1);
+ }
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ break;
+ case KKey:
+ casekeyboard:
+ typetext = rowtype(&row, r, mouse->xy);
+ t = typetext;
+ if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
+ activecol = t->col;
+ if(t!=nil && t->w!=nil)
+ t->w->body.file->curtext = &t->w->body;
+ if(timer != nil)
+ timercancel(timer);
+ if(t!=nil && t->what==Tag) {
+ timer = timerstart(500);
+ alts[KTimer].c = timer->c;
+ alts[KTimer].op = CHANRCV;
+ }else{
+ timer = nil;
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ }
+ if(nbrecv(keyboardctl->c, &r) > 0)
+ goto casekeyboard;
+ flushimage(display, 1);
+ break;
+ }
+ }
+}
+
+void
+mousethread(void *)
+{
+ Text *t, *argt;
+ int but;
+ uint q0, q1;
+ Window *w;
+ Plumbmsg *pm;
+ Mouse m;
+ char *act;
+ enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
+ static Alt alts[NMALT+1];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ alts[MMouse].op = CHANRCV;
+ alts[MPlumb].c = cplumb;
+ alts[MPlumb].v = &pm;
+ alts[MPlumb].op = CHANRCV;
+ alts[MWarnings].c = cwarn;
+ alts[MWarnings].v = nil;
+ alts[MWarnings].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row);
+ flushwarnings();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ scrlresize();
+ rowresize(&row, screen->clipr);
+ break;
+ case MPlumb:
+ if(strcmp(pm->type, "text") == 0){
+ act = plumblookup(pm->attr, "action");
+ if(act==nil || strcmp(act, "showfile")==0)
+ plumblook(pm);
+ else if(strcmp(act, "showdata")==0)
+ plumbshow(pm);
+ }
+ plumbfree(pm);
+ break;
+ case MWarnings:
+ break;
+ case MMouse:
+ /*
+ * Make a copy so decisions are consistent; mousectl changes
+ * underfoot. Can't just receive into m because this introduces
+ * another race; see /sys/src/libdraw/mouse.c.
+ */
+ m = mousectl->Mouse;
+ qlock(&row);
+ t = rowwhich(&row, m.xy);
+
+ if((t!=mousetext && t!=nil && t->w!=nil) &&
+ (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
+ xfidlog(t->w, "focus");
+ }
+
+ if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
+ winlock(mousetext->w, 'M');
+ mousetext->eq0 = ~0;
+ wincommit(mousetext->w, mousetext);
+ winunlock(mousetext->w);
+ }
+ mousetext = t;
+ if(t == nil)
+ goto Continue;
+ w = t->w;
+ if(t==nil || m.buttons==0)
+ goto Continue;
+ but = 0;
+ if(m.buttons == 1)
+ but = 1;
+ else if(m.buttons == 2)
+ but = 2;
+ else if(m.buttons == 4)
+ but = 3;
+ barttext = t;
+ if(t->what==Body && ptinrect(m.xy, t->scrollr)){
+ if(but){
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(t->what==Body && w != nil && (m.buttons & (8|16))){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ texttype(t, but);
+ winunlock(w);
+ goto Continue;
+ }
+ if(ptinrect(m.xy, t->scrollr)){
+ if(but){
+ if(t->what == Columntag)
+ rowdragcol(&row, t->col, but);
+ else if(t->what == Tag){
+ coldragwin(t->col, t->w, but);
+ if(t->w)
+ barttext = &t->w->body;
+ }
+ if(t->col)
+ activecol = t->col;
+ }
+ goto Continue;
+ }
+ if(m.buttons){
+ if(w)
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ if(w)
+ wincommit(w, t);
+ else
+ textcommit(t, TRUE);
+ if(m.buttons & 1){
+ textselect(t);
+ if(w)
+ winsettag(w);
+ argtext = t;
+ seltext = t;
+ if(t->col)
+ activecol = t->col; /* button 1 only */
+ if(t->w!=nil && t==&t->w->body)
+ activewin = t->w;
+ }else if(m.buttons & 2){
+ if(textselect2(t, &q0, &q1, &argt))
+ execute(t, q0, q1, FALSE, argt);
+ }else if(m.buttons & 4){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1, FALSE);
+ }
+ if(w)
+ winunlock(w);
+ goto Continue;
+ }
+ Continue:
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+/*
+ * There is a race between process exiting and our finding out it was ever created.
+ * This structure keeps a list of processes that have exited we haven't heard of.
+ */
+typedef struct Pid Pid;
+struct Pid
+{
+ int pid;
+ char msg[ERRMAX];
+ Pid *next;
+};
+
+void
+waitthread(void *)
+{
+ Waitmsg *w;
+ Command *c, *lc;
+ uint pid;
+ int found, ncmd;
+ Rune *cmd;
+ char *err;
+ Text *t;
+ Pid *pids, *p, *lastp;
+ enum { WErr, WKill, WWait, WCmd, NWALT };
+ Alt alts[NWALT+1];
+
+ threadsetname("waitthread");
+ pids = nil;
+ alts[WErr].c = cerr;
+ alts[WErr].v = &err;
+ alts[WErr].op = CHANRCV;
+ alts[WKill].c = ckill;
+ alts[WKill].v = &cmd;
+ alts[WKill].op = CHANRCV;
+ alts[WWait].c = cwait;
+ alts[WWait].v = &w;
+ alts[WWait].op = CHANRCV;
+ alts[WCmd].c = ccommand;
+ alts[WCmd].v = &c;
+ alts[WCmd].op = CHANRCV;
+ alts[NWALT].op = CHANEND;
+
+ command = nil;
+ for(;;){
+ switch(alt(alts)){
+ case WErr:
+ qlock(&row);
+ warning(nil, "%s", err);
+ free(err);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ case WKill:
+ found = FALSE;
+ ncmd = runestrlen(cmd);
+ for(c=command; c; c=c->next){
+ /* -1 for blank */
+ if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
+ if(postnote(PNGROUP, c->pid, "kill") < 0)
+ warning(nil, "kill %S: %r\n", cmd);
+ found = TRUE;
+ }
+ }
+ if(!found)
+ warning(nil, "Kill: no process %S\n", cmd);
+ free(cmd);
+ break;
+ case WWait:
+ pid = w->pid;
+ lc = nil;
+ for(c=command; c; c=c->next){
+ if(c->pid == pid){
+ if(lc)
+ lc->next = c->next;
+ else
+ command = c->next;
+ break;
+ }
+ lc = c;
+ }
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ if(c == nil){
+ /* helper processes use this exit status */
+ if(strncmp(w->msg, "libthread", 9) != 0){
+ p = emalloc(sizeof(Pid));
+ p->pid = pid;
+ strncpy(p->msg, w->msg, sizeof(p->msg));
+ p->next = pids;
+ pids = p;
+ }
+ }else{
+ if(search(t, c->name, c->nname)){
+ textdelete(t, t->q0, t->q1, TRUE);
+ textsetselect(t, 0, 0);
+ }
+ if(w->msg[0])
+ warning(c->md, "%s\n", w->msg);
+ flushimage(display, 1);
+ }
+ qunlock(&row);
+ free(w);
+ Freecmd:
+ if(c){
+ if(c->iseditcmd)
+ sendul(cedit, 0);
+ free(c->text);
+ free(c->name);
+ fsysdelid(c->md);
+ free(c);
+ }
+ break;
+ case WCmd:
+ /* has this command already exited? */
+ lastp = nil;
+ for(p=pids; p!=nil; p=p->next){
+ if(p->pid == c->pid){
+ if(p->msg[0])
+ warning(c->md, "%s\n", p->msg);
+ if(lastp == nil)
+ pids = p->next;
+ else
+ lastp->next = p->next;
+ free(p);
+ goto Freecmd;
+ }
+ lastp = p;
+ }
+ c->next = command;
+ command = c;
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ textinsert(t, 0, c->name, c->nname, TRUE);
+ textsetselect(t, 0, 0);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+void
+xfidallocthread(void*)
+{
+ Xfid *xfree, *x;
+ enum { Alloc, Free, N };
+ static Alt alts[N+1];
+
+ threadsetname("xfidallocthread");
+ alts[Alloc].c = cxfidalloc;
+ alts[Alloc].v = nil;
+ alts[Alloc].op = CHANRCV;
+ alts[Free].c = cxfidfree;
+ alts[Free].v = &x;
+ alts[Free].op = CHANRCV;
+ alts[N].op = CHANEND;
+
+ xfree = nil;
+ for(;;){
+ switch(alt(alts)){
+ case Alloc:
+ x = xfree;
+ if(x)
+ xfree = x->next;
+ else{
+ x = emalloc(sizeof(Xfid));
+ x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+ x->arg = x;
+ threadcreate(xfidctl, x->arg, STACK);
+ }
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ x->next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void*)
+{
+ Window *w;
+
+ threadsetname("newwindowthread");
+
+ for(;;){
+ /* only fsysproc is talking to us, so synchronization is trivial */
+ recvp(cnewwindow);
+ w = makenewwindow(nil);
+ winsettag(w);
+ xfidlog(w, "new");
+ sendp(cnewwindow, w);
+ }
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+ Reffont *r;
+ Font *f;
+ int i;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(strcmp(name, fontcache[i]->f->name) == 0){
+ r = fontcache[i];
+ goto Found;
+ }
+ f = openfont(display, name);
+ if(f == nil){
+ warning(nil, "can't open font file %s: %r\n", name);
+ return nil;
+ }
+ r = emalloc(sizeof(Reffont));
+ r->f = f;
+ fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(r);
+ if(reffonts[fix])
+ rfclose(reffonts[fix]);
+ reffonts[fix] = r;
+ if(name != fontnames[fix]){
+ free(fontnames[fix]);
+ fontnames[fix] = estrdup(name);
+ }
+ }
+ if(setfont){
+ reffont.f = r->f;
+ incref(r);
+ rfclose(reffonts[0]);
+ font = r->f;
+ reffonts[0] = r;
+ incref(r);
+ iconinit();
+ }
+ incref(r);
+ return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+ int i;
+
+ if(decref(r) == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ nfontcache--;
+ memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+ }
+ freefont(r->f);
+ free(r);
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ /* Blue */
+ tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Yellow */
+ textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ r = Rect(0, 0, Scrollwid+2, font->height+1);
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(modbutton, r, 2, tagcols[BORD], ZP);
+ r = insetrect(r, 2);
+ tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+ draw(modbutton, r, tmp, nil, ZP);
+ freeimage(tmp);
+
+ r = button->r;
+ colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+ but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+ but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fprint(fd, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf()
+{
+ int nulls;
+
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ if(snarffd < 0)
+ return;
+ seek(snarffd, 0, 0);
+ bufreset(&snarfbuf);
+ bufload(&snarfbuf, 0, snarffd, &nulls);
+}
diff --git a/patch/acme-fixfont/email b/patch/acme-fixfont/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/acme-fixfont/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/acme-fixfont/files b/patch/acme-fixfont/files
new file mode 100644
index 0000000..0350059
--- /dev/null
+++ b/patch/acme-fixfont/files
@@ -0,0 +1 @@
+/sys/src/cmd/acme/acme.c acme.c
diff --git a/patch/acme-fixfont/notes b/patch/acme-fixfont/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/acme-fixfont/notes
diff --git a/patch/acme-fixfont/readme b/patch/acme-fixfont/readme
new file mode 100644
index 0000000..2714bbc
--- /dev/null
+++ b/patch/acme-fixfont/readme
@@ -0,0 +1,5 @@
+Set fontnames[1] to $fixfont
+
+fontnames[1] is the "alternate" font.
+The Font command without an argument
+toggles between fontnames[0] and [1].
diff --git a/patch/acme-moveto-undo/acme.c b/patch/acme-moveto-undo/acme.c
new file mode 100644
index 0000000..c394e4e
--- /dev/null
+++ b/patch/acme-moveto-undo/acme.c
@@ -0,0 +1,972 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+ /* for generating syms in mkfile only: */
+ #include <bio.h>
+ #include "edit.h"
+
+void mousethread(void*);
+void keyboardthread(void*);
+void waitthread(void*);
+void xfidallocthread(void*);
+void newwindowthread(void*);
+void plumbproc(void*);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int plumbsendfd;
+int plumbeditfd;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2];
+
+Command *command;
+
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+int shutdown(void*, char*);
+
+void
+derror(Display*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+ char *p, *loadfile;
+ char buf[256];
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ loadfile = nil;
+ ARGBEGIN{
+ case 'a':
+ globalindent[AUTOINDENT] = TRUE;
+ break;
+ case 'b':
+ bartflag = TRUE;
+ break;
+ case 'c':
+ p = ARGF();
+ if(p == nil)
+ goto Usage;
+ ncol = atoi(p);
+ if(ncol <= 0)
+ goto Usage;
+ break;
+ case 'f':
+ fontnames[0] = ARGF();
+ if(fontnames[0] == nil)
+ goto Usage;
+ break;
+ case 'F':
+ fontnames[1] = ARGF();
+ if(fontnames[1] == nil)
+ goto Usage;
+ break;
+ case 'i':
+ globalindent[SPACESINDENT] = TRUE;
+ break;
+ case 'l':
+ loadfile = ARGF();
+ if(loadfile == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
+ exits("usage");
+ }ARGEND
+
+ if(fontnames[0] == nil)
+ fontnames[0] = getenv("font");
+ if(fontnames[0] == nil)
+ fontnames[0] = "/lib/font/bit/vga/unicode.font";
+ if(access(fontnames[0], 0) < 0){
+ fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
+ exits("font open");
+ }
+ if(fontnames[1] == nil)
+ fontnames[1] = fontnames[0];
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("home");
+ p = getenv("tabstop");
+ if(p != nil){
+ maxtab = strtoul(p, nil, 0);
+ free(p);
+ }
+ if(maxtab == 0)
+ maxtab = 4;
+ if(loadfile)
+ rowloadfonts(loadfile);
+ putenv("font", fontnames[0]);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(cputype){
+ sprint(buf, "/acme/bin/%s", cputype);
+ bind(buf, "/bin", MBEFORE);
+ }
+ bind("/acme/bin", "/bin", MBEFORE);
+ getwd(wdir, sizeof wdir);
+
+ if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ exits("geninitdraw");
+ }
+ d = display;
+ font = d->defaultfont;
+
+ reffont.f = font;
+ reffonts[0] = &reffont;
+ incref(&reffont); /* one to hold up 'font' variable */
+ incref(&reffont); /* one to hold up reffonts[0] */
+ fontcache = emalloc(sizeof(Reffont*));
+ nfontcache = 1;
+ fontcache[0] = &reffont;
+
+ iconinit();
+ timerinit();
+ rxinit();
+
+ cwait = threadwaitchan();
+ ccommand = chancreate(sizeof(Command**), 0);
+ ckill = chancreate(sizeof(Rune*), 0);
+ cxfidalloc = chancreate(sizeof(Xfid*), 0);
+ cxfidfree = chancreate(sizeof(Xfid*), 0);
+ cnewwindow = chancreate(sizeof(Channel*), 0);
+ cerr = chancreate(sizeof(char*), 0);
+ cedit = chancreate(sizeof(int), 0);
+ cexit = chancreate(sizeof(int), 0);
+ cwarn = chancreate(sizeof(void*), 1);
+ if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ rowinit(&row, screen->clipr);
+ if(ncol < 0){
+ if(argc == 0)
+ ncol = 2;
+ else{
+ ncol = (argc+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(argc == 0)
+ readfile(c, wdir);
+ else
+ for(i=0; i<argc; i++){
+ p = utfrrune(argv[i], '/');
+ if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+ readfile(c, argv[i]);
+ else
+ readfile(row.col[i/WPERCOL], argv[i]);
+ }
+ }
+ flushimage(display, 1);
+
+ acmeerrorinit();
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(waitthread, nil, STACK);
+ threadcreate(xfidallocthread, nil, STACK);
+ threadcreate(newwindowthread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nb, nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ textload(&w->body, 0, s, 1);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
+ xfidlog(w, "new");
+}
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+ dumping = TRUE;
+ rowdump(&row, nil);
+ }
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("acme: %s\n", msg);
+ abort();
+ return 0;
+}
+
+void
+killprocs(void)
+{
+ Command *c;
+
+ fsysclose();
+// if(display)
+// flushimage(display, 1);
+
+ for(c=command; c; c=c->next)
+ postnote(PNGROUP, c->pid, "hangup");
+ remove(acmeerrorfile);
+}
+
+static int errorfd;
+
+void
+acmeerrorproc(void *)
+{
+ char *buf, *s;
+ int n;
+
+ threadsetname("acmeerrorproc");
+ buf = emalloc(8192+1);
+ while((n=read(errorfd, buf, 8192)) >= 0){
+ buf[n] = '\0';
+ s = estrdup(buf);
+ sendp(cerr, s);
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int fd, pfd[2];
+ char buf[64];
+
+ if(pipe(pfd) < 0)
+ error("can't create pipe");
+ sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0){
+ remove(acmeerrorfile);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0)
+ error("can't create acmeerror file");
+ }
+ sprint(buf, "%d", pfd[0]);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ /* reopen pfd[1] close on exec */
+ sprint(buf, "/fd/%d", pfd[1]);
+ errorfd = open(buf, OREAD|OCEXEC);
+ if(errorfd < 0)
+ error("can't re-open acmeerror file");
+ close(pfd[1]);
+ close(pfd[0]);
+ proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbeditfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+void
+keyboardthread(void *)
+{
+ Rune r;
+ Timer *timer;
+ Text *t;
+ enum { KTimer, KKey, NKALT };
+ static Alt alts[NKALT+1];
+
+ alts[KTimer].c = nil;
+ alts[KTimer].v = nil;
+ alts[KTimer].op = CHANNOP;
+ alts[KKey].c = keyboardctl->c;
+ alts[KKey].v = &r;
+ alts[KKey].op = CHANRCV;
+ alts[NKALT].op = CHANEND;
+
+ timer = nil;
+ typetext = nil;
+ threadsetname("keyboardthread");
+ for(;;){
+ switch(alt(alts)){
+ case KTimer:
+ timerstop(timer);
+ t = typetext;
+ if(t!=nil && t->what==Tag){
+ winlock(t->w, 'K');
+ wincommit(t->w, t);
+ winunlock(t->w);
+ flushimage(display, 1);
+ }
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ break;
+ case KKey:
+ casekeyboard:
+ switch(r){
+ case KF|1:
+ undomoveto(mousectl);
+ goto skip;
+ }
+ typetext = rowtype(&row, r, mouse->xy);
+ t = typetext;
+ if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
+ activecol = t->col;
+ if(t!=nil && t->w!=nil)
+ t->w->body.file->curtext = &t->w->body;
+ if(timer != nil)
+ timercancel(timer);
+ if(t!=nil && t->what==Tag) {
+ timer = timerstart(500);
+ alts[KTimer].c = timer->c;
+ alts[KTimer].op = CHANRCV;
+ }else{
+ timer = nil;
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ }
+ skip:
+ if(nbrecv(keyboardctl->c, &r) > 0)
+ goto casekeyboard;
+ flushimage(display, 1);
+ break;
+ }
+ }
+}
+
+void
+mousethread(void *)
+{
+ Text *t, *argt;
+ int but;
+ uint q0, q1;
+ Window *w;
+ Plumbmsg *pm;
+ Mouse m;
+ char *act;
+ enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
+ static Alt alts[NMALT+1];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ alts[MMouse].op = CHANRCV;
+ alts[MPlumb].c = cplumb;
+ alts[MPlumb].v = &pm;
+ alts[MPlumb].op = CHANRCV;
+ alts[MWarnings].c = cwarn;
+ alts[MWarnings].v = nil;
+ alts[MWarnings].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row);
+ flushwarnings();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ scrlresize();
+ rowresize(&row, screen->clipr);
+ break;
+ case MPlumb:
+ if(strcmp(pm->type, "text") == 0){
+ act = plumblookup(pm->attr, "action");
+ if(act==nil || strcmp(act, "showfile")==0)
+ plumblook(pm);
+ else if(strcmp(act, "showdata")==0)
+ plumbshow(pm);
+ }
+ plumbfree(pm);
+ break;
+ case MWarnings:
+ break;
+ case MMouse:
+ /*
+ * Make a copy so decisions are consistent; mousectl changes
+ * underfoot. Can't just receive into m because this introduces
+ * another race; see /sys/src/libdraw/mouse.c.
+ */
+ m = mousectl->Mouse;
+ qlock(&row);
+ t = rowwhich(&row, m.xy);
+
+ if((t!=mousetext && t!=nil && t->w!=nil) &&
+ (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
+ xfidlog(t->w, "focus");
+ }
+
+ if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
+ winlock(mousetext->w, 'M');
+ mousetext->eq0 = ~0;
+ wincommit(mousetext->w, mousetext);
+ winunlock(mousetext->w);
+ }
+ mousetext = t;
+ if(t == nil)
+ goto Continue;
+ w = t->w;
+ if(t==nil || m.buttons==0)
+ goto Continue;
+ but = 0;
+ if(m.buttons == 1)
+ but = 1;
+ else if(m.buttons == 2)
+ but = 2;
+ else if(m.buttons == 4)
+ but = 3;
+ barttext = t;
+ if(t->what==Body && ptinrect(m.xy, t->scrollr)){
+ if(but){
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(t->what==Body && w != nil && (m.buttons & (8|16))){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ texttype(t, but);
+ winunlock(w);
+ goto Continue;
+ }
+ if(ptinrect(m.xy, t->scrollr)){
+ if(but){
+ if(t->what == Columntag)
+ rowdragcol(&row, t->col, but);
+ else if(t->what == Tag){
+ coldragwin(t->col, t->w, but);
+ if(t->w)
+ barttext = &t->w->body;
+ }
+ if(t->col)
+ activecol = t->col;
+ }
+ goto Continue;
+ }
+ if(m.buttons){
+ if(w)
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ if(w)
+ wincommit(w, t);
+ else
+ textcommit(t, TRUE);
+ if(m.buttons & 1){
+ textselect(t);
+ if(w)
+ winsettag(w);
+ argtext = t;
+ seltext = t;
+ if(t->col)
+ activecol = t->col; /* button 1 only */
+ if(t->w!=nil && t==&t->w->body)
+ activewin = t->w;
+ }else if(m.buttons & 2){
+ if(textselect2(t, &q0, &q1, &argt))
+ execute(t, q0, q1, FALSE, argt);
+ }else if(m.buttons & 4){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1, FALSE);
+ }
+ if(w)
+ winunlock(w);
+ goto Continue;
+ }
+ Continue:
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+/*
+ * There is a race between process exiting and our finding out it was ever created.
+ * This structure keeps a list of processes that have exited we haven't heard of.
+ */
+typedef struct Pid Pid;
+struct Pid
+{
+ int pid;
+ char msg[ERRMAX];
+ Pid *next;
+};
+
+void
+waitthread(void *)
+{
+ Waitmsg *w;
+ Command *c, *lc;
+ uint pid;
+ int found, ncmd;
+ Rune *cmd;
+ char *err;
+ Text *t;
+ Pid *pids, *p, *lastp;
+ enum { WErr, WKill, WWait, WCmd, NWALT };
+ Alt alts[NWALT+1];
+
+ threadsetname("waitthread");
+ pids = nil;
+ alts[WErr].c = cerr;
+ alts[WErr].v = &err;
+ alts[WErr].op = CHANRCV;
+ alts[WKill].c = ckill;
+ alts[WKill].v = &cmd;
+ alts[WKill].op = CHANRCV;
+ alts[WWait].c = cwait;
+ alts[WWait].v = &w;
+ alts[WWait].op = CHANRCV;
+ alts[WCmd].c = ccommand;
+ alts[WCmd].v = &c;
+ alts[WCmd].op = CHANRCV;
+ alts[NWALT].op = CHANEND;
+
+ command = nil;
+ for(;;){
+ switch(alt(alts)){
+ case WErr:
+ qlock(&row);
+ warning(nil, "%s", err);
+ free(err);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ case WKill:
+ found = FALSE;
+ ncmd = runestrlen(cmd);
+ for(c=command; c; c=c->next){
+ /* -1 for blank */
+ if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
+ if(postnote(PNGROUP, c->pid, "kill") < 0)
+ warning(nil, "kill %S: %r\n", cmd);
+ found = TRUE;
+ }
+ }
+ if(!found)
+ warning(nil, "Kill: no process %S\n", cmd);
+ free(cmd);
+ break;
+ case WWait:
+ pid = w->pid;
+ lc = nil;
+ for(c=command; c; c=c->next){
+ if(c->pid == pid){
+ if(lc)
+ lc->next = c->next;
+ else
+ command = c->next;
+ break;
+ }
+ lc = c;
+ }
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ if(c == nil){
+ /* helper processes use this exit status */
+ if(strncmp(w->msg, "libthread", 9) != 0){
+ p = emalloc(sizeof(Pid));
+ p->pid = pid;
+ strncpy(p->msg, w->msg, sizeof(p->msg));
+ p->next = pids;
+ pids = p;
+ }
+ }else{
+ if(search(t, c->name, c->nname)){
+ textdelete(t, t->q0, t->q1, TRUE);
+ textsetselect(t, 0, 0);
+ }
+ if(w->msg[0])
+ warning(c->md, "%s\n", w->msg);
+ flushimage(display, 1);
+ }
+ qunlock(&row);
+ free(w);
+ Freecmd:
+ if(c){
+ if(c->iseditcmd)
+ sendul(cedit, 0);
+ free(c->text);
+ free(c->name);
+ fsysdelid(c->md);
+ free(c);
+ }
+ break;
+ case WCmd:
+ /* has this command already exited? */
+ lastp = nil;
+ for(p=pids; p!=nil; p=p->next){
+ if(p->pid == c->pid){
+ if(p->msg[0])
+ warning(c->md, "%s\n", p->msg);
+ if(lastp == nil)
+ pids = p->next;
+ else
+ lastp->next = p->next;
+ free(p);
+ goto Freecmd;
+ }
+ lastp = p;
+ }
+ c->next = command;
+ command = c;
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ textinsert(t, 0, c->name, c->nname, TRUE);
+ textsetselect(t, 0, 0);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+void
+xfidallocthread(void*)
+{
+ Xfid *xfree, *x;
+ enum { Alloc, Free, N };
+ static Alt alts[N+1];
+
+ threadsetname("xfidallocthread");
+ alts[Alloc].c = cxfidalloc;
+ alts[Alloc].v = nil;
+ alts[Alloc].op = CHANRCV;
+ alts[Free].c = cxfidfree;
+ alts[Free].v = &x;
+ alts[Free].op = CHANRCV;
+ alts[N].op = CHANEND;
+
+ xfree = nil;
+ for(;;){
+ switch(alt(alts)){
+ case Alloc:
+ x = xfree;
+ if(x)
+ xfree = x->next;
+ else{
+ x = emalloc(sizeof(Xfid));
+ x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+ x->arg = x;
+ threadcreate(xfidctl, x->arg, STACK);
+ }
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ x->next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void*)
+{
+ Window *w;
+
+ threadsetname("newwindowthread");
+
+ for(;;){
+ /* only fsysproc is talking to us, so synchronization is trivial */
+ recvp(cnewwindow);
+ w = makenewwindow(nil);
+ winsettag(w);
+ xfidlog(w, "new");
+ sendp(cnewwindow, w);
+ }
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+ Reffont *r;
+ Font *f;
+ int i;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(strcmp(name, fontcache[i]->f->name) == 0){
+ r = fontcache[i];
+ goto Found;
+ }
+ f = openfont(display, name);
+ if(f == nil){
+ warning(nil, "can't open font file %s: %r\n", name);
+ return nil;
+ }
+ r = emalloc(sizeof(Reffont));
+ r->f = f;
+ fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(r);
+ if(reffonts[fix])
+ rfclose(reffonts[fix]);
+ reffonts[fix] = r;
+ if(name != fontnames[fix]){
+ free(fontnames[fix]);
+ fontnames[fix] = estrdup(name);
+ }
+ }
+ if(setfont){
+ reffont.f = r->f;
+ incref(r);
+ rfclose(reffonts[0]);
+ font = r->f;
+ reffonts[0] = r;
+ incref(r);
+ iconinit();
+ }
+ incref(r);
+ return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+ int i;
+
+ if(decref(r) == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ nfontcache--;
+ memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+ }
+ freefont(r->f);
+ free(r);
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ /* Blue */
+ tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Yellow */
+ textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ r = Rect(0, 0, Scrollwid+2, font->height+1);
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(modbutton, r, 2, tagcols[BORD], ZP);
+ r = insetrect(r, 2);
+ tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+ draw(modbutton, r, tmp, nil, ZP);
+ freeimage(tmp);
+
+ r = button->r;
+ colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+ but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+ but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fprint(fd, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf()
+{
+ int nulls;
+
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ if(snarffd < 0)
+ return;
+ seek(snarffd, 0, 0);
+ bufreset(&snarfbuf);
+ bufload(&snarfbuf, 0, snarffd, &nulls);
+}
diff --git a/patch/acme-moveto-undo/acme.c.orig b/patch/acme-moveto-undo/acme.c.orig
new file mode 100644
index 0000000..e874d35
--- /dev/null
+++ b/patch/acme-moveto-undo/acme.c.orig
@@ -0,0 +1,966 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+ /* for generating syms in mkfile only: */
+ #include <bio.h>
+ #include "edit.h"
+
+void mousethread(void*);
+void keyboardthread(void*);
+void waitthread(void*);
+void xfidallocthread(void*);
+void newwindowthread(void*);
+void plumbproc(void*);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int plumbsendfd;
+int plumbeditfd;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2];
+
+Command *command;
+
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+int shutdown(void*, char*);
+
+void
+derror(Display*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+ char *p, *loadfile;
+ char buf[256];
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ loadfile = nil;
+ ARGBEGIN{
+ case 'a':
+ globalindent[AUTOINDENT] = TRUE;
+ break;
+ case 'b':
+ bartflag = TRUE;
+ break;
+ case 'c':
+ p = ARGF();
+ if(p == nil)
+ goto Usage;
+ ncol = atoi(p);
+ if(ncol <= 0)
+ goto Usage;
+ break;
+ case 'f':
+ fontnames[0] = ARGF();
+ if(fontnames[0] == nil)
+ goto Usage;
+ break;
+ case 'F':
+ fontnames[1] = ARGF();
+ if(fontnames[1] == nil)
+ goto Usage;
+ break;
+ case 'i':
+ globalindent[SPACESINDENT] = TRUE;
+ break;
+ case 'l':
+ loadfile = ARGF();
+ if(loadfile == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
+ exits("usage");
+ }ARGEND
+
+ if(fontnames[0] == nil)
+ fontnames[0] = getenv("font");
+ if(fontnames[0] == nil)
+ fontnames[0] = "/lib/font/bit/vga/unicode.font";
+ if(access(fontnames[0], 0) < 0){
+ fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
+ exits("font open");
+ }
+ if(fontnames[1] == nil)
+ fontnames[1] = fontnames[0];
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("home");
+ p = getenv("tabstop");
+ if(p != nil){
+ maxtab = strtoul(p, nil, 0);
+ free(p);
+ }
+ if(maxtab == 0)
+ maxtab = 4;
+ if(loadfile)
+ rowloadfonts(loadfile);
+ putenv("font", fontnames[0]);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(cputype){
+ sprint(buf, "/acme/bin/%s", cputype);
+ bind(buf, "/bin", MBEFORE);
+ }
+ bind("/acme/bin", "/bin", MBEFORE);
+ getwd(wdir, sizeof wdir);
+
+ if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ exits("geninitdraw");
+ }
+ d = display;
+ font = d->defaultfont;
+
+ reffont.f = font;
+ reffonts[0] = &reffont;
+ incref(&reffont); /* one to hold up 'font' variable */
+ incref(&reffont); /* one to hold up reffonts[0] */
+ fontcache = emalloc(sizeof(Reffont*));
+ nfontcache = 1;
+ fontcache[0] = &reffont;
+
+ iconinit();
+ timerinit();
+ rxinit();
+
+ cwait = threadwaitchan();
+ ccommand = chancreate(sizeof(Command**), 0);
+ ckill = chancreate(sizeof(Rune*), 0);
+ cxfidalloc = chancreate(sizeof(Xfid*), 0);
+ cxfidfree = chancreate(sizeof(Xfid*), 0);
+ cnewwindow = chancreate(sizeof(Channel*), 0);
+ cerr = chancreate(sizeof(char*), 0);
+ cedit = chancreate(sizeof(int), 0);
+ cexit = chancreate(sizeof(int), 0);
+ cwarn = chancreate(sizeof(void*), 1);
+ if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd >= 0){
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ rowinit(&row, screen->clipr);
+ if(ncol < 0){
+ if(argc == 0)
+ ncol = 2;
+ else{
+ ncol = (argc+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = rowadd(&row, nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(argc == 0)
+ readfile(c, wdir);
+ else
+ for(i=0; i<argc; i++){
+ p = utfrrune(argv[i], '/');
+ if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
+ readfile(c, argv[i]);
+ else
+ readfile(row.col[i/WPERCOL], argv[i]);
+ }
+ }
+ flushimage(display, 1);
+
+ acmeerrorinit();
+ threadcreate(keyboardthread, nil, STACK);
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(waitthread, nil, STACK);
+ threadcreate(xfidallocthread, nil, STACK);
+ threadcreate(newwindowthread, nil, STACK);
+
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nb, nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
+ rs = cleanrname((Runestr){rb, nr});
+ winsetname(w, rs.r, rs.nr);
+ textload(&w->body, 0, s, 1);
+ w->body.file->mod = FALSE;
+ w->dirty = FALSE;
+ winsettag(w);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
+ xfidlog(w, "new");
+}
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+int
+shutdown(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
+ dumping = TRUE;
+ rowdump(&row, nil);
+ }
+ for(i=0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ print("acme: %s\n", msg);
+ abort();
+ return 0;
+}
+
+void
+killprocs(void)
+{
+ Command *c;
+
+ fsysclose();
+// if(display)
+// flushimage(display, 1);
+
+ for(c=command; c; c=c->next)
+ postnote(PNGROUP, c->pid, "hangup");
+ remove(acmeerrorfile);
+}
+
+static int errorfd;
+
+void
+acmeerrorproc(void *)
+{
+ char *buf, *s;
+ int n;
+
+ threadsetname("acmeerrorproc");
+ buf = emalloc(8192+1);
+ while((n=read(errorfd, buf, 8192)) >= 0){
+ buf[n] = '\0';
+ s = estrdup(buf);
+ sendp(cerr, s);
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int fd, pfd[2];
+ char buf[64];
+
+ if(pipe(pfd) < 0)
+ error("can't create pipe");
+ sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0){
+ remove(acmeerrorfile);
+ fd = create(acmeerrorfile, OWRITE, 0666);
+ if(fd < 0)
+ error("can't create acmeerror file");
+ }
+ sprint(buf, "%d", pfd[0]);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ /* reopen pfd[1] close on exec */
+ sprint(buf, "/fd/%d", pfd[1]);
+ errorfd = open(buf, OREAD|OCEXEC);
+ if(errorfd < 0)
+ error("can't re-open acmeerror file");
+ close(pfd[1]);
+ close(pfd[0]);
+ proccreate(acmeerrorproc, nil, STACK);
+}
+
+void
+plumbproc(void *)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbeditfd);
+ if(m == nil)
+ threadexits(nil);
+ sendp(cplumb, m);
+ }
+}
+
+void
+keyboardthread(void *)
+{
+ Rune r;
+ Timer *timer;
+ Text *t;
+ enum { KTimer, KKey, NKALT };
+ static Alt alts[NKALT+1];
+
+ alts[KTimer].c = nil;
+ alts[KTimer].v = nil;
+ alts[KTimer].op = CHANNOP;
+ alts[KKey].c = keyboardctl->c;
+ alts[KKey].v = &r;
+ alts[KKey].op = CHANRCV;
+ alts[NKALT].op = CHANEND;
+
+ timer = nil;
+ typetext = nil;
+ threadsetname("keyboardthread");
+ for(;;){
+ switch(alt(alts)){
+ case KTimer:
+ timerstop(timer);
+ t = typetext;
+ if(t!=nil && t->what==Tag){
+ winlock(t->w, 'K');
+ wincommit(t->w, t);
+ winunlock(t->w);
+ flushimage(display, 1);
+ }
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ break;
+ case KKey:
+ casekeyboard:
+ typetext = rowtype(&row, r, mouse->xy);
+ t = typetext;
+ if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
+ activecol = t->col;
+ if(t!=nil && t->w!=nil)
+ t->w->body.file->curtext = &t->w->body;
+ if(timer != nil)
+ timercancel(timer);
+ if(t!=nil && t->what==Tag) {
+ timer = timerstart(500);
+ alts[KTimer].c = timer->c;
+ alts[KTimer].op = CHANRCV;
+ }else{
+ timer = nil;
+ alts[KTimer].c = nil;
+ alts[KTimer].op = CHANNOP;
+ }
+ if(nbrecv(keyboardctl->c, &r) > 0)
+ goto casekeyboard;
+ flushimage(display, 1);
+ break;
+ }
+ }
+}
+
+void
+mousethread(void *)
+{
+ Text *t, *argt;
+ int but;
+ uint q0, q1;
+ Window *w;
+ Plumbmsg *pm;
+ Mouse m;
+ char *act;
+ enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
+ static Alt alts[NMALT+1];
+
+ threadsetname("mousethread");
+ alts[MResize].c = mousectl->resizec;
+ alts[MResize].v = nil;
+ alts[MResize].op = CHANRCV;
+ alts[MMouse].c = mousectl->c;
+ alts[MMouse].v = &mousectl->Mouse;
+ alts[MMouse].op = CHANRCV;
+ alts[MPlumb].c = cplumb;
+ alts[MPlumb].v = &pm;
+ alts[MPlumb].op = CHANRCV;
+ alts[MWarnings].c = cwarn;
+ alts[MWarnings].v = nil;
+ alts[MWarnings].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row);
+ flushwarnings();
+ qunlock(&row);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ scrlresize();
+ rowresize(&row, screen->clipr);
+ break;
+ case MPlumb:
+ if(strcmp(pm->type, "text") == 0){
+ act = plumblookup(pm->attr, "action");
+ if(act==nil || strcmp(act, "showfile")==0)
+ plumblook(pm);
+ else if(strcmp(act, "showdata")==0)
+ plumbshow(pm);
+ }
+ plumbfree(pm);
+ break;
+ case MWarnings:
+ break;
+ case MMouse:
+ /*
+ * Make a copy so decisions are consistent; mousectl changes
+ * underfoot. Can't just receive into m because this introduces
+ * another race; see /sys/src/libdraw/mouse.c.
+ */
+ m = mousectl->Mouse;
+ qlock(&row);
+ t = rowwhich(&row, m.xy);
+
+ if((t!=mousetext && t!=nil && t->w!=nil) &&
+ (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
+ xfidlog(t->w, "focus");
+ }
+
+ if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
+ winlock(mousetext->w, 'M');
+ mousetext->eq0 = ~0;
+ wincommit(mousetext->w, mousetext);
+ winunlock(mousetext->w);
+ }
+ mousetext = t;
+ if(t == nil)
+ goto Continue;
+ w = t->w;
+ if(t==nil || m.buttons==0)
+ goto Continue;
+ but = 0;
+ if(m.buttons == 1)
+ but = 1;
+ else if(m.buttons == 2)
+ but = 2;
+ else if(m.buttons == 4)
+ but = 3;
+ barttext = t;
+ if(t->what==Body && ptinrect(m.xy, t->scrollr)){
+ if(but){
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(t->what==Body && w != nil && (m.buttons & (8|16))){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ texttype(t, but);
+ winunlock(w);
+ goto Continue;
+ }
+ if(ptinrect(m.xy, t->scrollr)){
+ if(but){
+ if(t->what == Columntag)
+ rowdragcol(&row, t->col, but);
+ else if(t->what == Tag){
+ coldragwin(t->col, t->w, but);
+ if(t->w)
+ barttext = &t->w->body;
+ }
+ if(t->col)
+ activecol = t->col;
+ }
+ goto Continue;
+ }
+ if(m.buttons){
+ if(w)
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ if(w)
+ wincommit(w, t);
+ else
+ textcommit(t, TRUE);
+ if(m.buttons & 1){
+ textselect(t);
+ if(w)
+ winsettag(w);
+ argtext = t;
+ seltext = t;
+ if(t->col)
+ activecol = t->col; /* button 1 only */
+ if(t->w!=nil && t==&t->w->body)
+ activewin = t->w;
+ }else if(m.buttons & 2){
+ if(textselect2(t, &q0, &q1, &argt))
+ execute(t, q0, q1, FALSE, argt);
+ }else if(m.buttons & 4){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1, FALSE);
+ }
+ if(w)
+ winunlock(w);
+ goto Continue;
+ }
+ Continue:
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+/*
+ * There is a race between process exiting and our finding out it was ever created.
+ * This structure keeps a list of processes that have exited we haven't heard of.
+ */
+typedef struct Pid Pid;
+struct Pid
+{
+ int pid;
+ char msg[ERRMAX];
+ Pid *next;
+};
+
+void
+waitthread(void *)
+{
+ Waitmsg *w;
+ Command *c, *lc;
+ uint pid;
+ int found, ncmd;
+ Rune *cmd;
+ char *err;
+ Text *t;
+ Pid *pids, *p, *lastp;
+ enum { WErr, WKill, WWait, WCmd, NWALT };
+ Alt alts[NWALT+1];
+
+ threadsetname("waitthread");
+ pids = nil;
+ alts[WErr].c = cerr;
+ alts[WErr].v = &err;
+ alts[WErr].op = CHANRCV;
+ alts[WKill].c = ckill;
+ alts[WKill].v = &cmd;
+ alts[WKill].op = CHANRCV;
+ alts[WWait].c = cwait;
+ alts[WWait].v = &w;
+ alts[WWait].op = CHANRCV;
+ alts[WCmd].c = ccommand;
+ alts[WCmd].v = &c;
+ alts[WCmd].op = CHANRCV;
+ alts[NWALT].op = CHANEND;
+
+ command = nil;
+ for(;;){
+ switch(alt(alts)){
+ case WErr:
+ qlock(&row);
+ warning(nil, "%s", err);
+ free(err);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ case WKill:
+ found = FALSE;
+ ncmd = runestrlen(cmd);
+ for(c=command; c; c=c->next){
+ /* -1 for blank */
+ if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
+ if(postnote(PNGROUP, c->pid, "kill") < 0)
+ warning(nil, "kill %S: %r\n", cmd);
+ found = TRUE;
+ }
+ }
+ if(!found)
+ warning(nil, "Kill: no process %S\n", cmd);
+ free(cmd);
+ break;
+ case WWait:
+ pid = w->pid;
+ lc = nil;
+ for(c=command; c; c=c->next){
+ if(c->pid == pid){
+ if(lc)
+ lc->next = c->next;
+ else
+ command = c->next;
+ break;
+ }
+ lc = c;
+ }
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ if(c == nil){
+ /* helper processes use this exit status */
+ if(strncmp(w->msg, "libthread", 9) != 0){
+ p = emalloc(sizeof(Pid));
+ p->pid = pid;
+ strncpy(p->msg, w->msg, sizeof(p->msg));
+ p->next = pids;
+ pids = p;
+ }
+ }else{
+ if(search(t, c->name, c->nname)){
+ textdelete(t, t->q0, t->q1, TRUE);
+ textsetselect(t, 0, 0);
+ }
+ if(w->msg[0])
+ warning(c->md, "%s\n", w->msg);
+ flushimage(display, 1);
+ }
+ qunlock(&row);
+ free(w);
+ Freecmd:
+ if(c){
+ if(c->iseditcmd)
+ sendul(cedit, 0);
+ free(c->text);
+ free(c->name);
+ fsysdelid(c->md);
+ free(c);
+ }
+ break;
+ case WCmd:
+ /* has this command already exited? */
+ lastp = nil;
+ for(p=pids; p!=nil; p=p->next){
+ if(p->pid == c->pid){
+ if(p->msg[0])
+ warning(c->md, "%s\n", p->msg);
+ if(lastp == nil)
+ pids = p->next;
+ else
+ lastp->next = p->next;
+ free(p);
+ goto Freecmd;
+ }
+ lastp = p;
+ }
+ c->next = command;
+ command = c;
+ qlock(&row);
+ t = &row.tag;
+ textcommit(t, TRUE);
+ textinsert(t, 0, c->name, c->nname, TRUE);
+ textsetselect(t, 0, 0);
+ flushimage(display, 1);
+ qunlock(&row);
+ break;
+ }
+ }
+}
+
+void
+xfidallocthread(void*)
+{
+ Xfid *xfree, *x;
+ enum { Alloc, Free, N };
+ static Alt alts[N+1];
+
+ threadsetname("xfidallocthread");
+ alts[Alloc].c = cxfidalloc;
+ alts[Alloc].v = nil;
+ alts[Alloc].op = CHANRCV;
+ alts[Free].c = cxfidfree;
+ alts[Free].v = &x;
+ alts[Free].op = CHANRCV;
+ alts[N].op = CHANEND;
+
+ xfree = nil;
+ for(;;){
+ switch(alt(alts)){
+ case Alloc:
+ x = xfree;
+ if(x)
+ xfree = x->next;
+ else{
+ x = emalloc(sizeof(Xfid));
+ x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+ x->arg = x;
+ threadcreate(xfidctl, x->arg, STACK);
+ }
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ x->next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
+void
+newwindowthread(void*)
+{
+ Window *w;
+
+ threadsetname("newwindowthread");
+
+ for(;;){
+ /* only fsysproc is talking to us, so synchronization is trivial */
+ recvp(cnewwindow);
+ w = makenewwindow(nil);
+ winsettag(w);
+ xfidlog(w, "new");
+ sendp(cnewwindow, w);
+ }
+}
+
+Reffont*
+rfget(int fix, int save, int setfont, char *name)
+{
+ Reffont *r;
+ Font *f;
+ int i;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(strcmp(name, fontcache[i]->f->name) == 0){
+ r = fontcache[i];
+ goto Found;
+ }
+ f = openfont(display, name);
+ if(f == nil){
+ warning(nil, "can't open font file %s: %r\n", name);
+ return nil;
+ }
+ r = emalloc(sizeof(Reffont));
+ r->f = f;
+ fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(r);
+ if(reffonts[fix])
+ rfclose(reffonts[fix]);
+ reffonts[fix] = r;
+ if(name != fontnames[fix]){
+ free(fontnames[fix]);
+ fontnames[fix] = estrdup(name);
+ }
+ }
+ if(setfont){
+ reffont.f = r->f;
+ incref(r);
+ rfclose(reffonts[0]);
+ font = r->f;
+ reffonts[0] = r;
+ incref(r);
+ iconinit();
+ }
+ incref(r);
+ return r;
+}
+
+void
+rfclose(Reffont *r)
+{
+ int i;
+
+ if(decref(r) == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ nfontcache--;
+ memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
+ }
+ freefont(r->f);
+ free(r);
+ }
+}
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ /* Blue */
+ tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ tagcols[TEXT] = display->black;
+ tagcols[HTEXT] = display->black;
+
+ /* Yellow */
+ textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
+ textcols[TEXT] = display->black;
+ textcols[HTEXT] = display->black;
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ r = Rect(0, 0, Scrollwid+2, font->height+1);
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(button, r, 2, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ border(modbutton, r, 2, tagcols[BORD], ZP);
+ r = insetrect(r, 2);
+ tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
+ draw(modbutton, r, tmp, nil, ZP);
+ freeimage(tmp);
+
+ r = button->r;
+ colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
+
+ but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
+ but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+
+/* rio truncates larges snarf buffers, so this avoids using the
+ * service if the string is huge */
+
+#define MAXSNARF 100*1024
+
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ fd = open("/dev/snarf", OWRITE);
+ if(fd < 0)
+ return;
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fprint(fd, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+getsnarf()
+{
+ int nulls;
+
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+ if(snarffd < 0)
+ return;
+ seek(snarffd, 0, 0);
+ bufreset(&snarfbuf);
+ bufload(&snarfbuf, 0, snarffd, &nulls);
+}
diff --git a/patch/acme-moveto-undo/email b/patch/acme-moveto-undo/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/acme-moveto-undo/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/acme-moveto-undo/files b/patch/acme-moveto-undo/files
new file mode 100644
index 0000000..610da9a
--- /dev/null
+++ b/patch/acme-moveto-undo/files
@@ -0,0 +1,5 @@
+/sys/src/cmd/acme/acme.c acme.c
+/sys/src/cmd/acme/fns.h fns.h
+/sys/src/cmd/acme/moveto.c moveto.c
+/sys/src/cmd/acme/text.c text.c
+/sys/src/cmd/acme/mkfile mkfile
diff --git a/patch/acme-moveto-undo/fns.h b/patch/acme-moveto-undo/fns.h
new file mode 100644
index 0000000..4ba641b
--- /dev/null
+++ b/patch/acme-moveto-undo/fns.h
@@ -0,0 +1,100 @@
+#pragma varargck argpos warning 2
+
+void warning(Mntdir*, char*, ...);
+
+#define fbufalloc() emalloc(BUFSIZE)
+#define fbuffree(x) free(x)
+
+#define moveto(mc, pt) recmoveto(mc, pt)
+void recmoveto(Mousectl *mc, Point pt);
+void undomoveto(Mousectl *mc);
+
+void plumblook(Plumbmsg*m);
+void plumbshow(Plumbmsg*m);
+void putsnarf(void);
+void getsnarf(void);
+int tempfile(void);
+void scrlresize(void);
+Font* getfont(int, int, char*);
+char* getarg(Text*, int, int, Rune**, int*);
+char* getbytearg(Text*, int, int, char**);
+void new(Text*, Text*, Text*, int, int, Rune*, int);
+void undo(Text*, Text*, Text*, int, int, Rune*, int);
+char* getname(Text*, Text*, Rune*, int, int);
+void scrsleep(uint);
+void savemouse(Window*);
+int restoremouse(Window*);
+void clearmouse(void);
+void allwindows(void(*)(Window*, void*), void*);
+uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*);
+void movetodel(Window*);
+
+Window* errorwin(Mntdir*, int);
+Window* errorwinforwin(Window*);
+Runestr cleanrname(Runestr);
+void run(Window*, char*, Rune*, int, int, char*, char*, int);
+void fsysclose(void);
+void setcurtext(Text*, int);
+int isfilec(Rune);
+void rxinit(void);
+int rxnull(void);
+Runestr dirname(Text*, Rune*, int);
+void error(char*);
+void cvttorunes(char*, int, Rune*, int*, int*, int*);
+void* tmalloc(uint);
+void tfree(void);
+void killprocs(void);
+void killtasks(void);
+int runeeq(Rune*, uint, Rune*, uint);
+int ALEF_tid(void);
+void iconinit(void);
+Timer* timerstart(int);
+void timerstop(Timer*);
+void timercancel(Timer*);
+void timerinit(void);
+void cut(Text*, Text*, Text*, int, int, Rune*, int);
+void paste(Text*, Text*, Text*, int, int, Rune*, int);
+void get(Text*, Text*, Text*, int, int, Rune*, int);
+void put(Text*, Text*, Text*, int, int, Rune*, int);
+void putfile(File*, int, int, Rune*, int);
+void fontx(Text*, Text*, Text*, int, int, Rune*, int);
+int isspace(Rune);
+int isalnum(Rune);
+void execute(Text*, uint, uint, int, Text*);
+int search(Text*, Rune*, uint);
+void look3(Text*, uint, uint, int);
+void editcmd(Text*, Rune*, uint);
+uint min(uint, uint);
+uint max(uint, uint);
+Window* lookfile(Rune*, int);
+Window* lookid(int, int);
+char* runetobyte(Rune*, int);
+Rune* bytetorune(char*, int*);
+void fsysinit(void);
+Mntdir* fsysmount(Rune*, int, Rune**, int);
+void fsysincid(Mntdir*);
+void fsysdelid(Mntdir*);
+Xfid* respond(Xfid*, Fcall*, char*);
+int rxcompile(Rune*);
+int rgetc(void*, uint);
+int tgetc(void*, uint);
+int isaddrc(int);
+int isregexc(int);
+void *emalloc(uint);
+void *erealloc(void*, uint);
+char *estrdup(char*);
+Range address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*);
+int rxexecute(Text*, Rune*, uint, uint, Rangeset*);
+int rxbexecute(Text*, uint, Rangeset*);
+Window* makenewwindow(Text *t);
+int expand(Text*, uint, uint, Expand*);
+Rune* skipbl(Rune*, int, int*);
+Rune* findbl(Rune*, int, int*);
+char* edittext(Window*, int, Rune*, int);
+void flushwarnings(void);
+long nlcount(Text*, long, long, long*);
+long nlcounttopos(Text*, long, long, long);
+
+#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune))
+#define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune))
+#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune))
diff --git a/patch/acme-moveto-undo/fns.h.orig b/patch/acme-moveto-undo/fns.h.orig
new file mode 100644
index 0000000..02cc06d
--- /dev/null
+++ b/patch/acme-moveto-undo/fns.h.orig
@@ -0,0 +1,96 @@
+#pragma varargck argpos warning 2
+
+void warning(Mntdir*, char*, ...);
+
+#define fbufalloc() emalloc(BUFSIZE)
+#define fbuffree(x) free(x)
+
+void plumblook(Plumbmsg*m);
+void plumbshow(Plumbmsg*m);
+void putsnarf(void);
+void getsnarf(void);
+int tempfile(void);
+void scrlresize(void);
+Font* getfont(int, int, char*);
+char* getarg(Text*, int, int, Rune**, int*);
+char* getbytearg(Text*, int, int, char**);
+void new(Text*, Text*, Text*, int, int, Rune*, int);
+void undo(Text*, Text*, Text*, int, int, Rune*, int);
+char* getname(Text*, Text*, Rune*, int, int);
+void scrsleep(uint);
+void savemouse(Window*);
+int restoremouse(Window*);
+void clearmouse(void);
+void allwindows(void(*)(Window*, void*), void*);
+uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*);
+void movetodel(Window*);
+
+Window* errorwin(Mntdir*, int);
+Window* errorwinforwin(Window*);
+Runestr cleanrname(Runestr);
+void run(Window*, char*, Rune*, int, int, char*, char*, int);
+void fsysclose(void);
+void setcurtext(Text*, int);
+int isfilec(Rune);
+void rxinit(void);
+int rxnull(void);
+Runestr dirname(Text*, Rune*, int);
+void error(char*);
+void cvttorunes(char*, int, Rune*, int*, int*, int*);
+void* tmalloc(uint);
+void tfree(void);
+void killprocs(void);
+void killtasks(void);
+int runeeq(Rune*, uint, Rune*, uint);
+int ALEF_tid(void);
+void iconinit(void);
+Timer* timerstart(int);
+void timerstop(Timer*);
+void timercancel(Timer*);
+void timerinit(void);
+void cut(Text*, Text*, Text*, int, int, Rune*, int);
+void paste(Text*, Text*, Text*, int, int, Rune*, int);
+void get(Text*, Text*, Text*, int, int, Rune*, int);
+void put(Text*, Text*, Text*, int, int, Rune*, int);
+void putfile(File*, int, int, Rune*, int);
+void fontx(Text*, Text*, Text*, int, int, Rune*, int);
+int isspace(Rune);
+int isalnum(Rune);
+void execute(Text*, uint, uint, int, Text*);
+int search(Text*, Rune*, uint);
+void look3(Text*, uint, uint, int);
+void editcmd(Text*, Rune*, uint);
+uint min(uint, uint);
+uint max(uint, uint);
+Window* lookfile(Rune*, int);
+Window* lookid(int, int);
+char* runetobyte(Rune*, int);
+Rune* bytetorune(char*, int*);
+void fsysinit(void);
+Mntdir* fsysmount(Rune*, int, Rune**, int);
+void fsysincid(Mntdir*);
+void fsysdelid(Mntdir*);
+Xfid* respond(Xfid*, Fcall*, char*);
+int rxcompile(Rune*);
+int rgetc(void*, uint);
+int tgetc(void*, uint);
+int isaddrc(int);
+int isregexc(int);
+void *emalloc(uint);
+void *erealloc(void*, uint);
+char *estrdup(char*);
+Range address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*);
+int rxexecute(Text*, Rune*, uint, uint, Rangeset*);
+int rxbexecute(Text*, uint, Rangeset*);
+Window* makenewwindow(Text *t);
+int expand(Text*, uint, uint, Expand*);
+Rune* skipbl(Rune*, int, int*);
+Rune* findbl(Rune*, int, int*);
+char* edittext(Window*, int, Rune*, int);
+void flushwarnings(void);
+long nlcount(Text*, long, long, long*);
+long nlcounttopos(Text*, long, long, long);
+
+#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune))
+#define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune))
+#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune))
diff --git a/patch/acme-moveto-undo/mkfile b/patch/acme-moveto-undo/mkfile
new file mode 100644
index 0000000..f5048ca
--- /dev/null
+++ b/patch/acme-moveto-undo/mkfile
@@ -0,0 +1,47 @@
+</$objtype/mkfile
+BIN=/$objtype/bin
+
+TARG=acme
+
+OFILES=\
+ acme.$O\
+ addr.$O\
+ buff.$O\
+ cols.$O\
+ disk.$O\
+ ecmd.$O\
+ edit.$O\
+ elog.$O\
+ exec.$O\
+ file.$O\
+ fsys.$O\
+ logf.$O\
+ look.$O\
+ moveto.$O\
+ regx.$O\
+ rows.$O\
+ scrl.$O\
+ text.$O\
+ time.$O\
+ util.$O\
+ wind.$O\
+ xfid.$O\
+
+HFILES=dat.h\
+ edit.h\
+ fns.h\
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+$O.out: /$objtype/lib/libframe.a /$objtype/lib/libdraw.a /$objtype/lib/libthread.a
+
+edit.$O ecmd.$O elog.$O: edit.h
+
+syms:V:
+ $CC -a acme.c > syms
+ for(i in ????.c) $CC -aa $i >> syms
diff --git a/patch/acme-moveto-undo/mkfile.orig b/patch/acme-moveto-undo/mkfile.orig
new file mode 100644
index 0000000..22ad647
--- /dev/null
+++ b/patch/acme-moveto-undo/mkfile.orig
@@ -0,0 +1,46 @@
+</$objtype/mkfile
+BIN=/$objtype/bin
+
+TARG=acme
+
+OFILES=\
+ acme.$O\
+ addr.$O\
+ buff.$O\
+ cols.$O\
+ disk.$O\
+ ecmd.$O\
+ edit.$O\
+ elog.$O\
+ exec.$O\
+ file.$O\
+ fsys.$O\
+ logf.$O\
+ look.$O\
+ regx.$O\
+ rows.$O\
+ scrl.$O\
+ text.$O\
+ time.$O\
+ util.$O\
+ wind.$O\
+ xfid.$O\
+
+HFILES=dat.h\
+ edit.h\
+ fns.h\
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+$O.out: /$objtype/lib/libframe.a /$objtype/lib/libdraw.a /$objtype/lib/libthread.a
+
+edit.$O ecmd.$O elog.$O: edit.h
+
+syms:V:
+ $CC -a acme.c > syms
+ for(i in ????.c) $CC -aa $i >> syms
diff --git a/patch/acme-moveto-undo/moveto.c b/patch/acme-moveto-undo/moveto.c
new file mode 100644
index 0000000..dae0a6b
--- /dev/null
+++ b/patch/acme-moveto-undo/moveto.c
@@ -0,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+
+static Point newmouse;
+static Point oldmouse;
+
+/* call moveto, but record old and new mouse positions */
+void
+recmoveto(Mousectl *mc, Point pt){
+ static vlong prevtime = 0;
+ vlong curtime = nsec();
+ if(curtime-prevtime>500000000){ /* 500ms */
+ newmouse = pt;
+ oldmouse = mouse->xy;
+ }
+ prevtime = curtime;
+ moveto(mc, pt);
+}
+
+void
+undomoveto(Mousectl *mc){
+ Point tmp;
+ moveto(mc, oldmouse);
+ tmp = newmouse;
+ newmouse = oldmouse;
+ oldmouse = tmp;
+}
+
diff --git a/patch/acme-moveto-undo/notes b/patch/acme-moveto-undo/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/acme-moveto-undo/notes
diff --git a/patch/acme-moveto-undo/readme b/patch/acme-moveto-undo/readme
new file mode 100644
index 0000000..770cb83
--- /dev/null
+++ b/patch/acme-moveto-undo/readme
@@ -0,0 +1,5 @@
+Press F1 to undo automatic mouse movement
+
+Many Acme operations call moveto, moving the mouse cursor
+without the user's involvement. This patch allows the user to
+undo calls to moveto by pressing F1. Another F1 undoes the undo.
diff --git a/patch/acme-moveto-undo/text.c b/patch/acme-moveto-undo/text.c
new file mode 100644
index 0000000..484ec3d
--- /dev/null
+++ b/patch/acme-moveto-undo/text.c
@@ -0,0 +1,1460 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include <complete.h>
+#include "dat.h"
+#include "fns.h"
+
+Image *tagcols[NCOL];
+Image *textcols[NCOL];
+
+enum{
+ TABDIR = 3 /* width of tabs in directory windows */
+};
+
+void
+textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
+{
+ t->file = f;
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollwid;
+ t->lastsr = nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ t->eq0 = ~0;
+ t->ncache = 0;
+ t->reffont = rf;
+ t->tabstop = maxtab;
+ memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
+ textredraw(t, r, rf->f, screen, -1);
+}
+
+void
+textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
+{
+ int maxt;
+ Rectangle rr;
+
+ frinit(t, r, f, b, t->Frame.cols);
+ rr = t->r;
+ rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */
+ draw(t->b, rr, t->cols[BACK], nil, ZP);
+ /* use no wider than 3-space tabs in a directory */
+ maxt = maxtab;
+ if(t->what == Body){
+ if(t->w->isdir)
+ maxt = min(TABDIR, maxtab);
+ else
+ maxt = t->tabstop;
+ }
+ t->maxtab = maxt*stringwidth(f, "0");
+ if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
+ if(t->maxlines > 0){
+ textreset(t);
+ textcolumnate(t, t->w->dlp, t->w->ndl);
+ textshow(t, 0, 0, 1);
+ }
+ }else{
+ textfill(t);
+ textsetselect(t, t->q0, t->q1);
+ }
+}
+
+int
+textresize(Text *t, Rectangle r)
+{
+ int odx;
+
+ if(Dy(r) > 0)
+ r.max.y -= Dy(r)%t->font->height;
+ else
+ r.max.y = r.min.y;
+ odx = Dx(t->all);
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollwid;
+ t->lastsr = nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ frclear(t, 0);
+ textredraw(t, r, t->font, t->b, odx);
+ return r.max.y;
+}
+
+void
+textclose(Text *t)
+{
+ free(t->cache);
+ frclear(t, 1);
+ filedeltext(t->file, t);
+ t->file = nil;
+ rfclose(t->reffont);
+ if(argtext == t)
+ argtext = nil;
+ if(typetext == t)
+ typetext = nil;
+ if(seltext == t)
+ seltext = nil;
+ if(mousetext == t)
+ mousetext = nil;
+ if(barttext == t)
+ barttext = nil;
+}
+
+int
+dircmp(void *a, void *b)
+{
+ Dirlist *da, *db;
+ int i, n;
+
+ da = *(Dirlist**)a;
+ db = *(Dirlist**)b;
+ n = min(da->nr, db->nr);
+ i = memcmp(da->r, db->r, n*sizeof(Rune));
+ if(i)
+ return i;
+ return da->nr - db->nr;
+}
+
+void
+textcolumnate(Text *t, Dirlist **dlp, int ndl)
+{
+ int i, j, w, colw, mint, maxt, ncol, nrow;
+ Dirlist *dl;
+ uint q1;
+
+ if(t->file->ntext > 1)
+ return;
+ mint = stringwidth(t->font, "0");
+ /* go for narrower tabs if set more than 3 wide */
+ t->maxtab = min(maxtab, TABDIR)*mint;
+ maxt = t->maxtab;
+ colw = 0;
+ for(i=0; i<ndl; i++){
+ dl = dlp[i];
+ w = dl->wid;
+ if(maxt-w%maxt < mint || w%maxt==0)
+ w += mint;
+ if(w % maxt)
+ w += maxt-(w%maxt);
+ if(w > colw)
+ colw = w;
+ }
+ if(colw == 0)
+ ncol = 1;
+ else
+ ncol = max(1, Dx(t->r)/colw);
+ nrow = (ndl+ncol-1)/ncol;
+
+ q1 = 0;
+ for(i=0; i<nrow; i++){
+ for(j=i; j<ndl; j+=nrow){
+ dl = dlp[j];
+ fileinsert(t->file, q1, dl->r, dl->nr);
+ q1 += dl->nr;
+ if(j+nrow >= ndl)
+ break;
+ w = dl->wid;
+ if(maxt-w%maxt < mint){
+ fileinsert(t->file, q1, L"\t", 1);
+ q1++;
+ w += mint;
+ }
+ do{
+ fileinsert(t->file, q1, L"\t", 1);
+ q1++;
+ w += maxt-(w%maxt);
+ }while(w < colw);
+ }
+ fileinsert(t->file, q1, L"\n", 1);
+ q1++;
+ }
+}
+
+uint
+textload(Text *t, uint q0, char *file, int setqid)
+{
+ Rune *rp;
+ Dirlist *dl, **dlp;
+ int fd, i, j, n, ndl, nulls;
+ uint q, q1;
+ Dir *d, *dbuf;
+ char *tmp;
+ Text *u;
+
+ if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body)
+ error("text.load");
+ if(t->w->isdir && t->file->nname==0){
+ warning(nil, "empty directory name\n");
+ return 0;
+ }
+ fd = open(file, OREAD);
+ if(fd < 0){
+ warning(nil, "can't open %s: %r\n", file);
+ return 0;
+ }
+ d = dirfstat(fd);
+ if(d == nil){
+ warning(nil, "can't fstat %s: %r\n", file);
+ goto Rescue;
+ }
+ nulls = FALSE;
+ if(d->qid.type & QTDIR){
+ /* this is checked in get() but it's possible the file changed underfoot */
+ if(t->file->ntext > 1){
+ warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
+ goto Rescue;
+ }
+ t->w->isdir = TRUE;
+ t->w->filemenu = FALSE;
+ if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){
+ rp = runemalloc(t->file->nname+1);
+ runemove(rp, t->file->name, t->file->nname);
+ rp[t->file->nname] = '/';
+ winsetname(t->w, rp, t->file->nname+1);
+ free(rp);
+ }
+ dlp = nil;
+ ndl = 0;
+ dbuf = nil;
+ while((n=dirread(fd, &dbuf)) > 0){
+ for(i=0; i<n; i++){
+ dl = emalloc(sizeof(Dirlist));
+ j = strlen(dbuf[i].name);
+ tmp = emalloc(j+1+1);
+ memmove(tmp, dbuf[i].name, j);
+ if(dbuf[i].qid.type & QTDIR)
+ tmp[j++] = '/';
+ tmp[j] = '\0';
+ dl->r = bytetorune(tmp, &dl->nr);
+ dl->wid = stringwidth(t->font, tmp);
+ free(tmp);
+ ndl++;
+ dlp = realloc(dlp, ndl*sizeof(Dirlist*));
+ dlp[ndl-1] = dl;
+ }
+ free(dbuf);
+ }
+ qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
+ t->w->dlp = dlp;
+ t->w->ndl = ndl;
+ textcolumnate(t, dlp, ndl);
+ q1 = t->file->nc;
+ }else{
+ t->w->isdir = FALSE;
+ t->w->filemenu = TRUE;
+ q1 = q0 + fileload(t->file, q0, fd, &nulls);
+ }
+ if(setqid){
+ t->file->dev = d->dev;
+ t->file->mtime = d->mtime;
+ t->file->qidpath = d->qid.path;
+ }
+ close(fd);
+ rp = fbufalloc();
+ for(q=q0; q<q1; q+=n){
+ n = q1-q;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(t->file, q, rp, n);
+ if(q < t->org)
+ t->org += n;
+ else if(q <= t->org+t->nchars)
+ frinsert(t, rp, rp+n, q-t->org);
+ if(t->lastlinefull)
+ break;
+ }
+ fbuffree(rp);
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ if(u->org > u->file->nc) /* will be 0 because of reset(), but safety first */
+ u->org = 0;
+ textresize(u, u->all);
+ textbacknl(u, u->org, 0); /* go to beginning of line */
+ }
+ textsetselect(u, q0, q0);
+ }
+ if(nulls)
+ warning(nil, "%s: NUL bytes elided\n", file);
+ free(d);
+ return q1-q0;
+
+ Rescue:
+ close(fd);
+ return 0;
+}
+
+uint
+textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
+{
+ Rune *bp, *tp, *up;
+ int i, initial;
+
+ if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
+ Err:
+ textinsert(t, q0, r, n, tofile);
+ *nrp = n;
+ return q0;
+ }
+ bp = r;
+ for(i=0; i<n; i++)
+ if(*bp++ == '\b'){
+ --bp;
+ initial = 0;
+ tp = runemalloc(n);
+ runemove(tp, r, i);
+ up = tp+i;
+ for(; i<n; i++){
+ *up = *bp++;
+ if(*up == '\b')
+ if(up == tp)
+ initial++;
+ else
+ --up;
+ else
+ up++;
+ }
+ if(initial){
+ if(initial > q0)
+ initial = q0;
+ q0 -= initial;
+ textdelete(t, q0, q0+initial, tofile);
+ }
+ n = up-tp;
+ textinsert(t, q0, tp, n, tofile);
+ free(tp);
+ *nrp = n;
+ return q0;
+ }
+ goto Err;
+}
+
+void
+textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
+{
+ int c, i;
+ Text *u;
+
+ if(tofile && t->ncache != 0)
+ error("text.insert");
+ if(n == 0)
+ return;
+ if(tofile){
+ fileinsert(t->file, q0, r, n);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ if(t->file->ntext > 1)
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ u->w->dirty = TRUE; /* always a body */
+ textinsert(u, q0, r, n, FALSE);
+ textsetselect(u, u->q0, u->q1);
+ textscrdraw(u);
+ }
+ }
+
+ }
+ if(q0 < t->q1)
+ t->q1 += n;
+ if(q0 < t->q0)
+ t->q0 += n;
+ if(q0 < t->org)
+ t->org += n;
+ else if(q0 <= t->org+t->nchars)
+ frinsert(t, r, r+n, q0-t->org);
+ if(t->w){
+ c = 'i';
+ if(t->what == Body)
+ c = 'I';
+ if(n <= EVENTSIZE)
+ winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
+ else
+ winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
+ }
+}
+
+void
+typecommit(Text *t)
+{
+ if(t->w != nil)
+ wincommit(t->w, t);
+ else
+ textcommit(t, TRUE);
+}
+
+void
+textfill(Text *t)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ if(t->lastlinefull || t->nofill)
+ return;
+ if(t->ncache > 0)
+ typecommit(t);
+ rp = fbufalloc();
+ do{
+ n = t->file->nc-(t->org+t->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ bufread(t->file, t->org+t->nchars, rp, n);
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = t->maxlines-t->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(t, rp, rp+i, t->nchars);
+ }while(t->lastlinefull == FALSE);
+ fbuffree(rp);
+}
+
+void
+textdelete(Text *t, uint q0, uint q1, int tofile)
+{
+ uint n, p0, p1;
+ int i, c;
+ Text *u;
+
+ if(tofile && t->ncache != 0)
+ error("text.delete");
+ n = q1-q0;
+ if(n == 0)
+ return;
+ if(tofile){
+ filedelete(t->file, q0, q1);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ if(t->file->ntext > 1)
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ u->w->dirty = TRUE; /* always a body */
+ textdelete(u, q0, q1, FALSE);
+ textsetselect(u, u->q0, u->q1);
+ textscrdraw(u);
+ }
+ }
+ }
+ if(q0 < t->q0)
+ t->q0 -= min(n, t->q0-q0);
+ if(q0 < t->q1)
+ t->q1 -= min(n, t->q1-q0);
+ if(q1 <= t->org)
+ t->org -= n;
+ else if(q0 < t->org+t->nchars){
+ p1 = q1 - t->org;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(q0 < t->org){
+ t->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - t->org;
+ frdelete(t, p0, p1);
+ textfill(t);
+ }
+ if(t->w){
+ c = 'd';
+ if(t->what == Body)
+ c = 'D';
+ winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
+ }
+}
+
+void
+textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
+{
+ *p0 = min(q0, t->file->nc);
+ *p1 = min(q1, t->file->nc);
+}
+
+Rune
+textreadc(Text *t, uint q)
+{
+ Rune r;
+
+ if(t->cq0<=q && q<t->cq0+t->ncache)
+ r = t->cache[q-t->cq0];
+ else
+ bufread(t->file, q, &r, 1);
+ return r;
+}
+
+static int
+spacesindentbswidth(Text *t)
+{
+ uint q, col;
+ Rune r;
+
+ col = textbswidth(t, 0x15);
+ q = t->q0;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r != ' ')
+ break;
+ q--;
+ if(--col % t->tabstop == 0)
+ break;
+ }
+ if(t->q0 == q)
+ return 1;
+ return t->q0-q;
+}
+
+int
+textbswidth(Text *t, Rune c)
+{
+ uint q, eq;
+ Rune r;
+ int skipping;
+
+ /* there is known to be at least one character to erase */
+ if(c == 0x08){ /* ^H: erase character */
+ if(t->what == Body && t->w->indent[SPACESINDENT])
+ return spacesindentbswidth(t);
+ return 1;
+ }
+ q = t->q0;
+ skipping = TRUE;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == t->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ if(c == 0x17){
+ eq = isalnum(r);
+ if(eq && skipping) /* found one; stop skipping */
+ skipping = FALSE;
+ else if(!eq && !skipping)
+ break;
+ }
+ --q;
+ }
+ return t->q0-q;
+}
+
+int
+textfilewidth(Text *t, uint q0, int oneelement)
+{
+ uint q;
+ Rune r;
+
+ q = q0;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r <= ' ')
+ break;
+ if(oneelement && r=='/')
+ break;
+ --q;
+ }
+ return q0-q;
+}
+
+Rune*
+textcomplete(Text *t)
+{
+ int i, nstr, npath;
+ uint q;
+ Rune tmp[200];
+ Rune *str, *path;
+ Rune *rp;
+ Completion *c;
+ char *s, *dirs;
+ Runestr dir;
+
+ /* control-f: filename completion; works back to white space or / */
+ if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */
+ return nil;
+ nstr = textfilewidth(t, t->q0, TRUE);
+ str = runemalloc(nstr);
+ npath = textfilewidth(t, t->q0-nstr, FALSE);
+ path = runemalloc(npath);
+
+ c = nil;
+ rp = nil;
+ dirs = nil;
+
+ q = t->q0-nstr;
+ for(i=0; i<nstr; i++)
+ str[i] = textreadc(t, q++);
+ q = t->q0-nstr-npath;
+ for(i=0; i<npath; i++)
+ path[i] = textreadc(t, q++);
+ /* is path rooted? if not, we need to make it relative to window path */
+ if(npath>0 && path[0]=='/')
+ dir = (Runestr){path, npath};
+ else{
+ dir = dirname(t, nil, 0);
+ if(dir.nr + 1 + npath > nelem(tmp)){
+ free(dir.r);
+ goto Return;
+ }
+ if(dir.nr == 0){
+ dir.nr = 1;
+ dir.r = runestrdup(L".");
+ }
+ runemove(tmp, dir.r, dir.nr);
+ tmp[dir.nr] = '/';
+ runemove(tmp+dir.nr+1, path, npath);
+ free(dir.r);
+ dir.r = tmp;
+ dir.nr += 1+npath;
+ dir = cleanrname(dir);
+ }
+
+ s = smprint("%.*S", nstr, str);
+ dirs = smprint("%.*S", dir.nr, dir.r);
+ c = complete(dirs, s);
+ free(s);
+ if(c == nil){
+ warning(nil, "error attempting completion: %r\n");
+ goto Return;
+ }
+
+ if(!c->advance){
+ warning(nil, "%.*S%s%.*S*%s\n",
+ dir.nr, dir.r,
+ dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
+ nstr, str,
+ c->nmatch? "" : ": no matches in:");
+ for(i=0; i<c->nfile; i++)
+ warning(nil, " %s\n", c->filename[i]);
+ }
+
+ if(c->advance)
+ rp = runesmprint("%s", c->string);
+ else
+ rp = nil;
+ Return:
+ freecompletion(c);
+ free(dirs);
+ free(str);
+ free(path);
+ return rp;
+}
+
+void
+texttype(Text *t, Rune r)
+{
+ uint q0, q1;
+ int nnb, nb, n, i;
+ int nr;
+ Rune *rp;
+ Text *u;
+
+ nr = 1;
+ rp = &r;
+ switch(r){
+ case Kleft:
+ typecommit(t);
+ if(t->q0 > 0)
+ textshow(t, t->q0-1, t->q0-1, TRUE);
+ return;
+ case Kright:
+ typecommit(t);
+ if(t->q1 < t->file->nc)
+ textshow(t, t->q1+1, t->q1+1, TRUE);
+ return;
+ case Kdown:
+ n = t->maxlines/3;
+ goto case_Down;
+ case Kscrollonedown:
+ n = mousescrollsize(t->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Down;
+ case Kpgdown:
+ n = 2*t->maxlines/3;
+ case_Down:
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
+ if(t->what == Body)
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Kup:
+ n = t->maxlines/3;
+ goto case_Up;
+ case Kscrolloneup:
+ n = mousescrollsize(t->maxlines);
+ goto case_Up;
+ case Kpgup:
+ n = 2*t->maxlines/3;
+ case_Up:
+ q0 = textbacknl(t, t->org, n);
+ if(t->what == Body)
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Khome:
+ typecommit(t);
+ textshow(t, 0, 0, FALSE);
+ return;
+ case Kend:
+ typecommit(t);
+ textshow(t, t->file->nc, t->file->nc, FALSE);
+ return;
+ case 0x01: /* ^A: beginning of line */
+ typecommit(t);
+ /* go to where ^U would erase, if not already at BOL */
+ nnb = 0;
+ if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
+ nnb = textbswidth(t, 0x15);
+ textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
+ return;
+ case 0x05: /* ^E: end of line */
+ typecommit(t);
+ q0 = t->q0;
+ while(q0<t->file->nc && textreadc(t, q0)!='\n')
+ q0++;
+ textshow(t, q0, q0, TRUE);
+ return;
+ }
+ if(t->what == Body){
+ seq++;
+ filemark(t->file);
+ }
+ if(t->q1 > t->q0){
+ if(t->ncache != 0)
+ error("text.type");
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ t->eq0 = ~0;
+ }
+ textshow(t, t->q0, t->q0, 1);
+ switch(r){
+ case 0x06:
+ case Kins:
+ rp = textcomplete(t);
+ if(rp == nil)
+ return;
+ nr = runestrlen(rp);
+ break; /* fall through to normal insertion case */
+ case 0x1B:
+ if(t->eq0 != ~0)
+ textsetselect(t, t->eq0, t->q0);
+ if(t->ncache > 0)
+ typecommit(t);
+ return;
+ case 0x08: /* ^H: erase character */
+ case 0x15: /* ^U: erase line */
+ case 0x17: /* ^W: erase word */
+ if(t->q0 == 0) /* nothing to erase */
+ return;
+ nnb = textbswidth(t, r);
+ q1 = t->q0;
+ q0 = q1-nnb;
+ /* if selection is at beginning of window, avoid deleting invisible text */
+ if(q0 < t->org){
+ q0 = t->org;
+ nnb = q1-q0;
+ }
+ if(nnb <= 0)
+ return;
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ u->nofill = TRUE;
+ nb = nnb;
+ n = u->ncache;
+ if(n > 0){
+ if(q1 != u->cq0+n)
+ error("text.type backspace");
+ if(n > nb)
+ n = nb;
+ u->ncache -= n;
+ textdelete(u, q1-n, q1, FALSE);
+ nb -= n;
+ }
+ if(u->eq0==q1 || u->eq0==~0)
+ u->eq0 = q0;
+ if(nb && u==t)
+ textdelete(u, q0, q0+nb, TRUE);
+ if(u != t)
+ textsetselect(u, u->q0, u->q1);
+ else
+ textsetselect(t, q0, q0);
+ u->nofill = FALSE;
+ }
+ for(i=0; i<t->file->ntext; i++)
+ textfill(t->file->text[i]);
+ return;
+ case '\t':
+ if(t->what == Body && t->w->indent[SPACESINDENT]){
+ nnb = textbswidth(t, 0x15);
+ if(nnb == 1 && textreadc(t, t->q0-1) == '\n')
+ nnb = 0;
+ nnb = t->tabstop - nnb % t->tabstop;
+ rp = runemalloc(nnb);
+ for(nr = 0; nr < nnb; nr++)
+ rp[nr] = ' ';
+ }
+ break;
+ case '\n':
+ if(t->what == Body && t->w->indent[AUTOINDENT]){
+ /* find beginning of previous line using backspace code */
+ nnb = textbswidth(t, 0x15); /* ^U case */
+ rp = runemalloc(nnb + 1);
+ nr = 0;
+ rp[nr++] = r;
+ for(i=0; i<nnb; i++){
+ r = textreadc(t, t->q0-nnb+i);
+ if(r != ' ' && r != '\t')
+ break;
+ rp[nr++] = r;
+ }
+ }
+ break; /* fall through to normal code */
+ }
+ /* otherwise ordinary character; just insert, typically in caches of all texts */
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u->eq0 == ~0)
+ u->eq0 = t->q0;
+ if(u->ncache == 0)
+ u->cq0 = t->q0;
+ else if(t->q0 != u->cq0+u->ncache)
+ error("text.type cq1");
+ textinsert(u, t->q0, rp, nr, FALSE);
+ if(u != t)
+ textsetselect(u, u->q0, u->q1);
+ if(u->ncache+nr > u->ncachealloc){
+ u->ncachealloc += 10 + nr;
+ u->cache = runerealloc(u->cache, u->ncachealloc);
+ }
+ runemove(u->cache+u->ncache, rp, nr);
+ u->ncache += nr;
+ }
+ if(rp != &r)
+ free(rp);
+ textsetselect(t, t->q0+nr, t->q0+nr);
+ if(r=='\n' && t->w!=nil)
+ wincommit(t->w, t);
+}
+
+void
+textcommit(Text *t, int tofile)
+{
+ if(t->ncache == 0)
+ return;
+ if(tofile)
+ fileinsert(t->file, t->cq0, t->cache, t->ncache);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ t->ncache = 0;
+}
+
+static Text *clicktext;
+static uint clickmsec;
+static int clickcount;
+static Point clickpt;
+static Text *selecttext;
+static uint selectq;
+
+/*
+ * called from frame library
+ */
+void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selecttext->Frame)
+ error("frameselect not right frame");
+ textframescroll(selecttext, dl);
+}
+
+void
+textframescroll(Text *t, int dl)
+{
+ uint q0;
+
+ if(dl == 0){
+ scrsleep(100);
+ return;
+ }
+ if(dl < 0){
+ q0 = textbacknl(t, t->org, -dl);
+ if(selectq > t->org+t->p0)
+ textsetselect(t, t->org+t->p0, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p0);
+ }else{
+ if(t->org+t->nchars == t->file->nc)
+ return;
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
+ if(selectq > t->org+t->p1)
+ textsetselect(t, t->org+t->p1, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p1);
+ }
+ textsetorigin(t, q0, TRUE);
+ flushimage(display, 1);
+}
+
+
+void
+textselect(Text *t)
+{
+ uint q0, q1;
+ int b, x, y, dx, dy;
+ int state;
+
+ selecttext = t;
+ /*
+ * To have double-clicking and chording, we double-click
+ * immediately if it might make sense.
+ */
+ b = mouse->buttons;
+ q0 = t->q0;
+ q1 = t->q1;
+ dx = abs(clickpt.x - mouse->xy.x);
+ dy = abs(clickpt.y - mouse->xy.y);
+ clickpt = mouse->xy;
+ selectq = t->org+frcharofpt(t, mouse->xy);
+ clickcount++;
+ if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3)
+ clickcount = 0;
+ if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){
+ textstretchsel(t, selectq, &q0, &q1, clickcount);
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ x = mouse->xy.x;
+ y = mouse->xy.y;
+ /* stay here until something interesting happens */
+ while(1){
+ readmouse(mousectl);
+ dx = abs(mouse->xy.x - x);
+ dy = abs(mouse->xy.y - y);
+ if(mouse->buttons != b || dx >= 3 || dy >= 3)
+ break;
+ clickcount++;
+ clickmsec = mouse->msec;
+ }
+ mouse->xy.x = x; /* in case we're calling frselect */
+ mouse->xy.y = y;
+ q0 = t->q0; /* may have changed */
+ q1 = t->q1;
+ selectq = t->org+frcharofpt(t, mouse->xy);;
+ }
+ if(mouse->buttons == b && clickcount == 0){
+ t->Frame.scroll = framescroll;
+ frselect(t, mousectl);
+ /* horrible botch: while asleep, may have lost selection altogether */
+ if(selectq > t->file->nc)
+ selectq = t->org + t->p0;
+ t->Frame.scroll = nil;
+ if(selectq < t->org)
+ q0 = selectq;
+ else
+ q0 = t->org + t->p0;
+ if(selectq > t->org+t->nchars)
+ q1 = selectq;
+ else
+ q1 = t->org+t->p1;
+ }
+ if(q0 == q1){
+ if(q0==t->q0 && mouse->msec-clickmsec<500)
+ textstretchsel(t, selectq, &q0, &q1, clickcount);
+ else
+ clicktext = t;
+ clickmsec = mouse->msec;
+ }else
+ clicktext = nil;
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ state = 0; /* undo when possible; +1 for cut, -1 for paste */
+ while(mouse->buttons){
+ mouse->msec = 0;
+ b = mouse->buttons;
+ if((b&1) && (b&6)){
+ if(state==0 && t->what==Body){
+ seq++;
+ filemark(t->w->body.file);
+ }
+ if(b & 2){
+ if(state==-1 && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q0);
+ state = 0;
+ }else if(state != 1){
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ state = 1;
+ }
+ }else{
+ if(state==1 && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q1);
+ state = 0;
+ }else if(state != -1){
+ paste(t, t, nil, TRUE, FALSE, nil, 0);
+ state = -1;
+ }
+ }
+ textscrdraw(t);
+ clearmouse();
+ }
+ flushimage(display, 1);
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ if(mouse->msec-clickmsec >= 500)
+ clicktext = nil;
+ }
+}
+
+void
+textshow(Text *t, uint q0, uint q1, int doselect)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ if(t->what != Body){
+ if(doselect)
+ textsetselect(t, q0, q1);
+ return;
+ }
+ if(t->w!=nil && t->maxlines==0)
+ colgrow(t->col, t->w, 1);
+ if(doselect)
+ textsetselect(t, q0, q1);
+ qe = t->org+t->nchars;
+ if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
+ textscrdraw(t);
+ else{
+ if(t->w->nopen[QWevent] > 0)
+ nl = 3*t->maxlines/4;
+ else
+ nl = t->maxlines/4;
+ q = textbacknl(t, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>t->org && q<t->org))
+ textsetorigin(t, q, TRUE);
+ while(q0 > t->org+t->nchars)
+ textsetorigin(t, t->org+1, FALSE);
+ }
+}
+
+static
+int
+region(int a, int b)
+{
+ if(a < b)
+ return -1;
+ if(a == b)
+ return 0;
+ return 1;
+}
+
+void
+selrestore(Frame *f, Point pt0, uint p0, uint p1)
+{
+ if(p1<=f->p0 || p0>=f->p1){
+ /* no overlap */
+ frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
+ return;
+ }
+ if(p0>=f->p0 && p1<=f->p1){
+ /* entirely inside */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+ return;
+ }
+
+ /* they now are known to overlap */
+
+ /* before selection */
+ if(p0 < f->p0){
+ frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
+ p0 = f->p0;
+ pt0 = frptofchar(f, p0);
+ }
+ /* after selection */
+ if(p1 > f->p1){
+ frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
+ p1 = f->p1;
+ }
+ /* inside selection */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+}
+
+void
+textsetselect(Text *t, uint q0, uint q1)
+{
+ int p0, p1;
+
+ /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
+ t->q0 = q0;
+ t->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-t->org;
+ p1 = q1-t->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > t->nchars)
+ p0 = t->nchars;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(p0==t->p0 && p1==t->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
+ frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < t->p0){
+ /* extend selection backwards */
+ frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
+ }else if(p0 > t->p0){
+ /* trim first part of selection */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
+ }
+ if(p1 > t->p1){
+ /* extend selection forwards */
+ frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
+ }else if(p1 < t->p1){
+ /* trim last part of selection */
+ frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
+ }
+
+ Return:
+ t->p0 = p0;
+ t->p1 = p1;
+}
+
+/*
+ * Release the button in less than DELAY ms and it's considered a null selection
+ * if the mouse hardly moved, regardless of whether it crossed a char boundary.
+ */
+enum {
+ DELAY = 2,
+ MINMOVE = 4,
+};
+
+uint
+xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
+{
+ uint p0, p1, q, tmp;
+ ulong msec;
+ Point mp, pt0, pt1, qt;
+ int reg, b;
+
+ mp = mc->xy;
+ b = mc->buttons;
+ msec = mc->msec;
+
+ /* remove tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 0);
+ p0 = p1 = frcharofpt(f, mp);
+ pt0 = frptofchar(f, p0);
+ pt1 = frptofchar(f, p1);
+ reg = 0;
+ frtick(f, pt0, 1);
+ do{
+ q = frcharofpt(f, mc->xy);
+ if(p1 != q){
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ if(reg != region(q, p0)){ /* crossed starting point; reset */
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ pt1 = pt0;
+ reg = region(q, p0);
+ if(reg == 0)
+ frdrawsel0(f, pt0, p0, p1, col, display->white);
+ }
+ qt = frptofchar(f, q);
+ if(reg > 0){
+ if(q > p1)
+ frdrawsel0(f, pt1, p1, q, col, display->white);
+
+ else if(q < p1)
+ selrestore(f, qt, q, p1);
+ }else if(reg < 0){
+ if(q > p1)
+ selrestore(f, pt1, p1, q);
+ else
+ frdrawsel0(f, qt, q, p1, col, display->white);
+ }
+ p1 = q;
+ pt1 = qt;
+ }
+ if(p0 == p1)
+ frtick(f, pt0, 1);
+ flushimage(f->display, 1);
+ readmouse(mc);
+ }while(mc->buttons == b);
+ if(mc->msec-msec < DELAY && p0!=p1
+ && abs(mp.x-mc->xy.x)<MINMOVE
+ && abs(mp.y-mc->xy.y)<MINMOVE) {
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ }
+ if(p1 < p0){
+ tmp = p0;
+ p0 = p1;
+ p1 = tmp;
+ }
+ pt0 = frptofchar(f, p0);
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ selrestore(f, pt0, p0, p1);
+ /* restore tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 1);
+ flushimage(f->display, 1);
+ *p1p = p1;
+ return p0;
+}
+
+int
+textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
+{
+ uint p0, p1;
+ int buts;
+
+ p0 = xselect(t, mousectl, high, &p1);
+ buts = mousectl->buttons;
+ if((buts & mask) == 0){
+ *q0 = p0+t->org;
+ *q1 = p1+t->org;
+ }
+
+ while(mousectl->buttons)
+ readmouse(mousectl);
+ return buts;
+}
+
+int
+textselect2(Text *t, uint *q0, uint *q1, Text **tp)
+{
+ int buts;
+
+ *tp = nil;
+ buts = textselect23(t, q0, q1, but2col, 4);
+ if(buts & 4)
+ return 0;
+ if(buts & 1){ /* pick up argument */
+ *tp = argtext;
+ return 1;
+ }
+ return 1;
+}
+
+int
+textselect3(Text *t, uint *q0, uint *q1)
+{
+ int h;
+
+ h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
+ return h;
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\n', 0 };
+static Rune left3[] = { L'\'', L'"', L'`', 0 };
+
+static
+Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static
+Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? isalnum(r) : r && !isspace(r);
+}
+
+void
+textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ *q0 = mp;
+ *q1 = mp;
+ for(i=0; left[i]!=nil; i++){
+ q = *q0;
+ l = left[i];
+ r = right[i];
+ /* try matching character to left, looking right */
+ if(q == 0)
+ c = '\n';
+ else
+ c = textreadc(t, q-1);
+ p = runestrchr(l, c);
+ if(p != nil){
+ if(textclickmatch(t, c, r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == t->file->nc)
+ c = '\n';
+ else
+ c = textreadc(t, q);
+ p = runestrchr(r, c);
+ if(p != nil){
+ if(textclickmatch(t, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<t->file->nc && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<t->file->nc && inmode(textreadc(t, *q1), mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(textreadc(t, *q0-1), mode))
+ (*q0)--;
+}
+
+int
+textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == t->file->nc)
+ break;
+ c = textreadc(t, *q);
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = textreadc(t, *q);
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+uint
+textbacknl(Text *t, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && textreadc(t, p-1)!='\n')
+ n = 1;
+ i = n;
+ while(i-->0 && p>0){
+ --p; /* it's at a newline now; back over it */
+ if(p == 0)
+ break;
+ /* at 128 chars, call it a line anyway */
+ for(j=128; --j>0 && p>0; p--)
+ if(textreadc(t, p-1)=='\n')
+ break;
+ }
+ return p;
+}
+
+void
+textsetorigin(Text *t, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact && textreadc(t, org-1) != '\n'){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<t->file->nc; i++){
+ if(textreadc(t, org) == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-t->org;
+ fixup = 0;
+ if(a>=0 && a<t->nchars){
+ frdelete(t, 0, a);
+ fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+ }
+ else if(a<0 && -a<t->nchars){
+ n = t->org - org;
+ r = runemalloc(n);
+ bufread(t->file, org, r, n);
+ frinsert(t, r, r+n, 0);
+ free(r);
+ }else
+ frdelete(t, 0, t->nchars);
+ t->org = org;
+ textfill(t);
+ textscrdraw(t);
+ textsetselect(t, t->q0, t->q1);
+ if(fixup && t->p1 > t->p0)
+ frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
+}
+
+void
+textreset(Text *t)
+{
+ t->file->seq = 0;
+ t->eq0 = ~0;
+ /* do t->delete(0, t->nc, TRUE) without building backup stuff */
+ textsetselect(t, t->org, t->org);
+ frdelete(t, 0, t->nchars);
+ t->org = 0;
+ t->q0 = 0;
+ t->q1 = 0;
+ filereset(t->file);
+ bufreset(t->file);
+}
diff --git a/patch/acme-moveto-undo/text.c.orig b/patch/acme-moveto-undo/text.c.orig
new file mode 100644
index 0000000..484ec3d
--- /dev/null
+++ b/patch/acme-moveto-undo/text.c.orig
@@ -0,0 +1,1460 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include <complete.h>
+#include "dat.h"
+#include "fns.h"
+
+Image *tagcols[NCOL];
+Image *textcols[NCOL];
+
+enum{
+ TABDIR = 3 /* width of tabs in directory windows */
+};
+
+void
+textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
+{
+ t->file = f;
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollwid;
+ t->lastsr = nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ t->eq0 = ~0;
+ t->ncache = 0;
+ t->reffont = rf;
+ t->tabstop = maxtab;
+ memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
+ textredraw(t, r, rf->f, screen, -1);
+}
+
+void
+textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
+{
+ int maxt;
+ Rectangle rr;
+
+ frinit(t, r, f, b, t->Frame.cols);
+ rr = t->r;
+ rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */
+ draw(t->b, rr, t->cols[BACK], nil, ZP);
+ /* use no wider than 3-space tabs in a directory */
+ maxt = maxtab;
+ if(t->what == Body){
+ if(t->w->isdir)
+ maxt = min(TABDIR, maxtab);
+ else
+ maxt = t->tabstop;
+ }
+ t->maxtab = maxt*stringwidth(f, "0");
+ if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
+ if(t->maxlines > 0){
+ textreset(t);
+ textcolumnate(t, t->w->dlp, t->w->ndl);
+ textshow(t, 0, 0, 1);
+ }
+ }else{
+ textfill(t);
+ textsetselect(t, t->q0, t->q1);
+ }
+}
+
+int
+textresize(Text *t, Rectangle r)
+{
+ int odx;
+
+ if(Dy(r) > 0)
+ r.max.y -= Dy(r)%t->font->height;
+ else
+ r.max.y = r.min.y;
+ odx = Dx(t->all);
+ t->all = r;
+ t->scrollr = r;
+ t->scrollr.max.x = r.min.x+Scrollwid;
+ t->lastsr = nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ frclear(t, 0);
+ textredraw(t, r, t->font, t->b, odx);
+ return r.max.y;
+}
+
+void
+textclose(Text *t)
+{
+ free(t->cache);
+ frclear(t, 1);
+ filedeltext(t->file, t);
+ t->file = nil;
+ rfclose(t->reffont);
+ if(argtext == t)
+ argtext = nil;
+ if(typetext == t)
+ typetext = nil;
+ if(seltext == t)
+ seltext = nil;
+ if(mousetext == t)
+ mousetext = nil;
+ if(barttext == t)
+ barttext = nil;
+}
+
+int
+dircmp(void *a, void *b)
+{
+ Dirlist *da, *db;
+ int i, n;
+
+ da = *(Dirlist**)a;
+ db = *(Dirlist**)b;
+ n = min(da->nr, db->nr);
+ i = memcmp(da->r, db->r, n*sizeof(Rune));
+ if(i)
+ return i;
+ return da->nr - db->nr;
+}
+
+void
+textcolumnate(Text *t, Dirlist **dlp, int ndl)
+{
+ int i, j, w, colw, mint, maxt, ncol, nrow;
+ Dirlist *dl;
+ uint q1;
+
+ if(t->file->ntext > 1)
+ return;
+ mint = stringwidth(t->font, "0");
+ /* go for narrower tabs if set more than 3 wide */
+ t->maxtab = min(maxtab, TABDIR)*mint;
+ maxt = t->maxtab;
+ colw = 0;
+ for(i=0; i<ndl; i++){
+ dl = dlp[i];
+ w = dl->wid;
+ if(maxt-w%maxt < mint || w%maxt==0)
+ w += mint;
+ if(w % maxt)
+ w += maxt-(w%maxt);
+ if(w > colw)
+ colw = w;
+ }
+ if(colw == 0)
+ ncol = 1;
+ else
+ ncol = max(1, Dx(t->r)/colw);
+ nrow = (ndl+ncol-1)/ncol;
+
+ q1 = 0;
+ for(i=0; i<nrow; i++){
+ for(j=i; j<ndl; j+=nrow){
+ dl = dlp[j];
+ fileinsert(t->file, q1, dl->r, dl->nr);
+ q1 += dl->nr;
+ if(j+nrow >= ndl)
+ break;
+ w = dl->wid;
+ if(maxt-w%maxt < mint){
+ fileinsert(t->file, q1, L"\t", 1);
+ q1++;
+ w += mint;
+ }
+ do{
+ fileinsert(t->file, q1, L"\t", 1);
+ q1++;
+ w += maxt-(w%maxt);
+ }while(w < colw);
+ }
+ fileinsert(t->file, q1, L"\n", 1);
+ q1++;
+ }
+}
+
+uint
+textload(Text *t, uint q0, char *file, int setqid)
+{
+ Rune *rp;
+ Dirlist *dl, **dlp;
+ int fd, i, j, n, ndl, nulls;
+ uint q, q1;
+ Dir *d, *dbuf;
+ char *tmp;
+ Text *u;
+
+ if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body)
+ error("text.load");
+ if(t->w->isdir && t->file->nname==0){
+ warning(nil, "empty directory name\n");
+ return 0;
+ }
+ fd = open(file, OREAD);
+ if(fd < 0){
+ warning(nil, "can't open %s: %r\n", file);
+ return 0;
+ }
+ d = dirfstat(fd);
+ if(d == nil){
+ warning(nil, "can't fstat %s: %r\n", file);
+ goto Rescue;
+ }
+ nulls = FALSE;
+ if(d->qid.type & QTDIR){
+ /* this is checked in get() but it's possible the file changed underfoot */
+ if(t->file->ntext > 1){
+ warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
+ goto Rescue;
+ }
+ t->w->isdir = TRUE;
+ t->w->filemenu = FALSE;
+ if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){
+ rp = runemalloc(t->file->nname+1);
+ runemove(rp, t->file->name, t->file->nname);
+ rp[t->file->nname] = '/';
+ winsetname(t->w, rp, t->file->nname+1);
+ free(rp);
+ }
+ dlp = nil;
+ ndl = 0;
+ dbuf = nil;
+ while((n=dirread(fd, &dbuf)) > 0){
+ for(i=0; i<n; i++){
+ dl = emalloc(sizeof(Dirlist));
+ j = strlen(dbuf[i].name);
+ tmp = emalloc(j+1+1);
+ memmove(tmp, dbuf[i].name, j);
+ if(dbuf[i].qid.type & QTDIR)
+ tmp[j++] = '/';
+ tmp[j] = '\0';
+ dl->r = bytetorune(tmp, &dl->nr);
+ dl->wid = stringwidth(t->font, tmp);
+ free(tmp);
+ ndl++;
+ dlp = realloc(dlp, ndl*sizeof(Dirlist*));
+ dlp[ndl-1] = dl;
+ }
+ free(dbuf);
+ }
+ qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
+ t->w->dlp = dlp;
+ t->w->ndl = ndl;
+ textcolumnate(t, dlp, ndl);
+ q1 = t->file->nc;
+ }else{
+ t->w->isdir = FALSE;
+ t->w->filemenu = TRUE;
+ q1 = q0 + fileload(t->file, q0, fd, &nulls);
+ }
+ if(setqid){
+ t->file->dev = d->dev;
+ t->file->mtime = d->mtime;
+ t->file->qidpath = d->qid.path;
+ }
+ close(fd);
+ rp = fbufalloc();
+ for(q=q0; q<q1; q+=n){
+ n = q1-q;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(t->file, q, rp, n);
+ if(q < t->org)
+ t->org += n;
+ else if(q <= t->org+t->nchars)
+ frinsert(t, rp, rp+n, q-t->org);
+ if(t->lastlinefull)
+ break;
+ }
+ fbuffree(rp);
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ if(u->org > u->file->nc) /* will be 0 because of reset(), but safety first */
+ u->org = 0;
+ textresize(u, u->all);
+ textbacknl(u, u->org, 0); /* go to beginning of line */
+ }
+ textsetselect(u, q0, q0);
+ }
+ if(nulls)
+ warning(nil, "%s: NUL bytes elided\n", file);
+ free(d);
+ return q1-q0;
+
+ Rescue:
+ close(fd);
+ return 0;
+}
+
+uint
+textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
+{
+ Rune *bp, *tp, *up;
+ int i, initial;
+
+ if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
+ Err:
+ textinsert(t, q0, r, n, tofile);
+ *nrp = n;
+ return q0;
+ }
+ bp = r;
+ for(i=0; i<n; i++)
+ if(*bp++ == '\b'){
+ --bp;
+ initial = 0;
+ tp = runemalloc(n);
+ runemove(tp, r, i);
+ up = tp+i;
+ for(; i<n; i++){
+ *up = *bp++;
+ if(*up == '\b')
+ if(up == tp)
+ initial++;
+ else
+ --up;
+ else
+ up++;
+ }
+ if(initial){
+ if(initial > q0)
+ initial = q0;
+ q0 -= initial;
+ textdelete(t, q0, q0+initial, tofile);
+ }
+ n = up-tp;
+ textinsert(t, q0, tp, n, tofile);
+ free(tp);
+ *nrp = n;
+ return q0;
+ }
+ goto Err;
+}
+
+void
+textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
+{
+ int c, i;
+ Text *u;
+
+ if(tofile && t->ncache != 0)
+ error("text.insert");
+ if(n == 0)
+ return;
+ if(tofile){
+ fileinsert(t->file, q0, r, n);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ if(t->file->ntext > 1)
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ u->w->dirty = TRUE; /* always a body */
+ textinsert(u, q0, r, n, FALSE);
+ textsetselect(u, u->q0, u->q1);
+ textscrdraw(u);
+ }
+ }
+
+ }
+ if(q0 < t->q1)
+ t->q1 += n;
+ if(q0 < t->q0)
+ t->q0 += n;
+ if(q0 < t->org)
+ t->org += n;
+ else if(q0 <= t->org+t->nchars)
+ frinsert(t, r, r+n, q0-t->org);
+ if(t->w){
+ c = 'i';
+ if(t->what == Body)
+ c = 'I';
+ if(n <= EVENTSIZE)
+ winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
+ else
+ winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
+ }
+}
+
+void
+typecommit(Text *t)
+{
+ if(t->w != nil)
+ wincommit(t->w, t);
+ else
+ textcommit(t, TRUE);
+}
+
+void
+textfill(Text *t)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ if(t->lastlinefull || t->nofill)
+ return;
+ if(t->ncache > 0)
+ typecommit(t);
+ rp = fbufalloc();
+ do{
+ n = t->file->nc-(t->org+t->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ bufread(t->file, t->org+t->nchars, rp, n);
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = t->maxlines-t->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(t, rp, rp+i, t->nchars);
+ }while(t->lastlinefull == FALSE);
+ fbuffree(rp);
+}
+
+void
+textdelete(Text *t, uint q0, uint q1, int tofile)
+{
+ uint n, p0, p1;
+ int i, c;
+ Text *u;
+
+ if(tofile && t->ncache != 0)
+ error("text.delete");
+ n = q1-q0;
+ if(n == 0)
+ return;
+ if(tofile){
+ filedelete(t->file, q0, q1);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ if(t->file->ntext > 1)
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u != t){
+ u->w->dirty = TRUE; /* always a body */
+ textdelete(u, q0, q1, FALSE);
+ textsetselect(u, u->q0, u->q1);
+ textscrdraw(u);
+ }
+ }
+ }
+ if(q0 < t->q0)
+ t->q0 -= min(n, t->q0-q0);
+ if(q0 < t->q1)
+ t->q1 -= min(n, t->q1-q0);
+ if(q1 <= t->org)
+ t->org -= n;
+ else if(q0 < t->org+t->nchars){
+ p1 = q1 - t->org;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(q0 < t->org){
+ t->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - t->org;
+ frdelete(t, p0, p1);
+ textfill(t);
+ }
+ if(t->w){
+ c = 'd';
+ if(t->what == Body)
+ c = 'D';
+ winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
+ }
+}
+
+void
+textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
+{
+ *p0 = min(q0, t->file->nc);
+ *p1 = min(q1, t->file->nc);
+}
+
+Rune
+textreadc(Text *t, uint q)
+{
+ Rune r;
+
+ if(t->cq0<=q && q<t->cq0+t->ncache)
+ r = t->cache[q-t->cq0];
+ else
+ bufread(t->file, q, &r, 1);
+ return r;
+}
+
+static int
+spacesindentbswidth(Text *t)
+{
+ uint q, col;
+ Rune r;
+
+ col = textbswidth(t, 0x15);
+ q = t->q0;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r != ' ')
+ break;
+ q--;
+ if(--col % t->tabstop == 0)
+ break;
+ }
+ if(t->q0 == q)
+ return 1;
+ return t->q0-q;
+}
+
+int
+textbswidth(Text *t, Rune c)
+{
+ uint q, eq;
+ Rune r;
+ int skipping;
+
+ /* there is known to be at least one character to erase */
+ if(c == 0x08){ /* ^H: erase character */
+ if(t->what == Body && t->w->indent[SPACESINDENT])
+ return spacesindentbswidth(t);
+ return 1;
+ }
+ q = t->q0;
+ skipping = TRUE;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == t->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ if(c == 0x17){
+ eq = isalnum(r);
+ if(eq && skipping) /* found one; stop skipping */
+ skipping = FALSE;
+ else if(!eq && !skipping)
+ break;
+ }
+ --q;
+ }
+ return t->q0-q;
+}
+
+int
+textfilewidth(Text *t, uint q0, int oneelement)
+{
+ uint q;
+ Rune r;
+
+ q = q0;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r <= ' ')
+ break;
+ if(oneelement && r=='/')
+ break;
+ --q;
+ }
+ return q0-q;
+}
+
+Rune*
+textcomplete(Text *t)
+{
+ int i, nstr, npath;
+ uint q;
+ Rune tmp[200];
+ Rune *str, *path;
+ Rune *rp;
+ Completion *c;
+ char *s, *dirs;
+ Runestr dir;
+
+ /* control-f: filename completion; works back to white space or / */
+ if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */
+ return nil;
+ nstr = textfilewidth(t, t->q0, TRUE);
+ str = runemalloc(nstr);
+ npath = textfilewidth(t, t->q0-nstr, FALSE);
+ path = runemalloc(npath);
+
+ c = nil;
+ rp = nil;
+ dirs = nil;
+
+ q = t->q0-nstr;
+ for(i=0; i<nstr; i++)
+ str[i] = textreadc(t, q++);
+ q = t->q0-nstr-npath;
+ for(i=0; i<npath; i++)
+ path[i] = textreadc(t, q++);
+ /* is path rooted? if not, we need to make it relative to window path */
+ if(npath>0 && path[0]=='/')
+ dir = (Runestr){path, npath};
+ else{
+ dir = dirname(t, nil, 0);
+ if(dir.nr + 1 + npath > nelem(tmp)){
+ free(dir.r);
+ goto Return;
+ }
+ if(dir.nr == 0){
+ dir.nr = 1;
+ dir.r = runestrdup(L".");
+ }
+ runemove(tmp, dir.r, dir.nr);
+ tmp[dir.nr] = '/';
+ runemove(tmp+dir.nr+1, path, npath);
+ free(dir.r);
+ dir.r = tmp;
+ dir.nr += 1+npath;
+ dir = cleanrname(dir);
+ }
+
+ s = smprint("%.*S", nstr, str);
+ dirs = smprint("%.*S", dir.nr, dir.r);
+ c = complete(dirs, s);
+ free(s);
+ if(c == nil){
+ warning(nil, "error attempting completion: %r\n");
+ goto Return;
+ }
+
+ if(!c->advance){
+ warning(nil, "%.*S%s%.*S*%s\n",
+ dir.nr, dir.r,
+ dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
+ nstr, str,
+ c->nmatch? "" : ": no matches in:");
+ for(i=0; i<c->nfile; i++)
+ warning(nil, " %s\n", c->filename[i]);
+ }
+
+ if(c->advance)
+ rp = runesmprint("%s", c->string);
+ else
+ rp = nil;
+ Return:
+ freecompletion(c);
+ free(dirs);
+ free(str);
+ free(path);
+ return rp;
+}
+
+void
+texttype(Text *t, Rune r)
+{
+ uint q0, q1;
+ int nnb, nb, n, i;
+ int nr;
+ Rune *rp;
+ Text *u;
+
+ nr = 1;
+ rp = &r;
+ switch(r){
+ case Kleft:
+ typecommit(t);
+ if(t->q0 > 0)
+ textshow(t, t->q0-1, t->q0-1, TRUE);
+ return;
+ case Kright:
+ typecommit(t);
+ if(t->q1 < t->file->nc)
+ textshow(t, t->q1+1, t->q1+1, TRUE);
+ return;
+ case Kdown:
+ n = t->maxlines/3;
+ goto case_Down;
+ case Kscrollonedown:
+ n = mousescrollsize(t->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Down;
+ case Kpgdown:
+ n = 2*t->maxlines/3;
+ case_Down:
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
+ if(t->what == Body)
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Kup:
+ n = t->maxlines/3;
+ goto case_Up;
+ case Kscrolloneup:
+ n = mousescrollsize(t->maxlines);
+ goto case_Up;
+ case Kpgup:
+ n = 2*t->maxlines/3;
+ case_Up:
+ q0 = textbacknl(t, t->org, n);
+ if(t->what == Body)
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Khome:
+ typecommit(t);
+ textshow(t, 0, 0, FALSE);
+ return;
+ case Kend:
+ typecommit(t);
+ textshow(t, t->file->nc, t->file->nc, FALSE);
+ return;
+ case 0x01: /* ^A: beginning of line */
+ typecommit(t);
+ /* go to where ^U would erase, if not already at BOL */
+ nnb = 0;
+ if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
+ nnb = textbswidth(t, 0x15);
+ textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
+ return;
+ case 0x05: /* ^E: end of line */
+ typecommit(t);
+ q0 = t->q0;
+ while(q0<t->file->nc && textreadc(t, q0)!='\n')
+ q0++;
+ textshow(t, q0, q0, TRUE);
+ return;
+ }
+ if(t->what == Body){
+ seq++;
+ filemark(t->file);
+ }
+ if(t->q1 > t->q0){
+ if(t->ncache != 0)
+ error("text.type");
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ t->eq0 = ~0;
+ }
+ textshow(t, t->q0, t->q0, 1);
+ switch(r){
+ case 0x06:
+ case Kins:
+ rp = textcomplete(t);
+ if(rp == nil)
+ return;
+ nr = runestrlen(rp);
+ break; /* fall through to normal insertion case */
+ case 0x1B:
+ if(t->eq0 != ~0)
+ textsetselect(t, t->eq0, t->q0);
+ if(t->ncache > 0)
+ typecommit(t);
+ return;
+ case 0x08: /* ^H: erase character */
+ case 0x15: /* ^U: erase line */
+ case 0x17: /* ^W: erase word */
+ if(t->q0 == 0) /* nothing to erase */
+ return;
+ nnb = textbswidth(t, r);
+ q1 = t->q0;
+ q0 = q1-nnb;
+ /* if selection is at beginning of window, avoid deleting invisible text */
+ if(q0 < t->org){
+ q0 = t->org;
+ nnb = q1-q0;
+ }
+ if(nnb <= 0)
+ return;
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ u->nofill = TRUE;
+ nb = nnb;
+ n = u->ncache;
+ if(n > 0){
+ if(q1 != u->cq0+n)
+ error("text.type backspace");
+ if(n > nb)
+ n = nb;
+ u->ncache -= n;
+ textdelete(u, q1-n, q1, FALSE);
+ nb -= n;
+ }
+ if(u->eq0==q1 || u->eq0==~0)
+ u->eq0 = q0;
+ if(nb && u==t)
+ textdelete(u, q0, q0+nb, TRUE);
+ if(u != t)
+ textsetselect(u, u->q0, u->q1);
+ else
+ textsetselect(t, q0, q0);
+ u->nofill = FALSE;
+ }
+ for(i=0; i<t->file->ntext; i++)
+ textfill(t->file->text[i]);
+ return;
+ case '\t':
+ if(t->what == Body && t->w->indent[SPACESINDENT]){
+ nnb = textbswidth(t, 0x15);
+ if(nnb == 1 && textreadc(t, t->q0-1) == '\n')
+ nnb = 0;
+ nnb = t->tabstop - nnb % t->tabstop;
+ rp = runemalloc(nnb);
+ for(nr = 0; nr < nnb; nr++)
+ rp[nr] = ' ';
+ }
+ break;
+ case '\n':
+ if(t->what == Body && t->w->indent[AUTOINDENT]){
+ /* find beginning of previous line using backspace code */
+ nnb = textbswidth(t, 0x15); /* ^U case */
+ rp = runemalloc(nnb + 1);
+ nr = 0;
+ rp[nr++] = r;
+ for(i=0; i<nnb; i++){
+ r = textreadc(t, t->q0-nnb+i);
+ if(r != ' ' && r != '\t')
+ break;
+ rp[nr++] = r;
+ }
+ }
+ break; /* fall through to normal code */
+ }
+ /* otherwise ordinary character; just insert, typically in caches of all texts */
+ for(i=0; i<t->file->ntext; i++){
+ u = t->file->text[i];
+ if(u->eq0 == ~0)
+ u->eq0 = t->q0;
+ if(u->ncache == 0)
+ u->cq0 = t->q0;
+ else if(t->q0 != u->cq0+u->ncache)
+ error("text.type cq1");
+ textinsert(u, t->q0, rp, nr, FALSE);
+ if(u != t)
+ textsetselect(u, u->q0, u->q1);
+ if(u->ncache+nr > u->ncachealloc){
+ u->ncachealloc += 10 + nr;
+ u->cache = runerealloc(u->cache, u->ncachealloc);
+ }
+ runemove(u->cache+u->ncache, rp, nr);
+ u->ncache += nr;
+ }
+ if(rp != &r)
+ free(rp);
+ textsetselect(t, t->q0+nr, t->q0+nr);
+ if(r=='\n' && t->w!=nil)
+ wincommit(t->w, t);
+}
+
+void
+textcommit(Text *t, int tofile)
+{
+ if(t->ncache == 0)
+ return;
+ if(tofile)
+ fileinsert(t->file, t->cq0, t->cache, t->ncache);
+ if(t->what == Body){
+ t->w->dirty = TRUE;
+ t->w->utflastqid = -1;
+ }
+ t->ncache = 0;
+}
+
+static Text *clicktext;
+static uint clickmsec;
+static int clickcount;
+static Point clickpt;
+static Text *selecttext;
+static uint selectq;
+
+/*
+ * called from frame library
+ */
+void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selecttext->Frame)
+ error("frameselect not right frame");
+ textframescroll(selecttext, dl);
+}
+
+void
+textframescroll(Text *t, int dl)
+{
+ uint q0;
+
+ if(dl == 0){
+ scrsleep(100);
+ return;
+ }
+ if(dl < 0){
+ q0 = textbacknl(t, t->org, -dl);
+ if(selectq > t->org+t->p0)
+ textsetselect(t, t->org+t->p0, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p0);
+ }else{
+ if(t->org+t->nchars == t->file->nc)
+ return;
+ q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
+ if(selectq > t->org+t->p1)
+ textsetselect(t, t->org+t->p1, selectq);
+ else
+ textsetselect(t, selectq, t->org+t->p1);
+ }
+ textsetorigin(t, q0, TRUE);
+ flushimage(display, 1);
+}
+
+
+void
+textselect(Text *t)
+{
+ uint q0, q1;
+ int b, x, y, dx, dy;
+ int state;
+
+ selecttext = t;
+ /*
+ * To have double-clicking and chording, we double-click
+ * immediately if it might make sense.
+ */
+ b = mouse->buttons;
+ q0 = t->q0;
+ q1 = t->q1;
+ dx = abs(clickpt.x - mouse->xy.x);
+ dy = abs(clickpt.y - mouse->xy.y);
+ clickpt = mouse->xy;
+ selectq = t->org+frcharofpt(t, mouse->xy);
+ clickcount++;
+ if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3)
+ clickcount = 0;
+ if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){
+ textstretchsel(t, selectq, &q0, &q1, clickcount);
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ x = mouse->xy.x;
+ y = mouse->xy.y;
+ /* stay here until something interesting happens */
+ while(1){
+ readmouse(mousectl);
+ dx = abs(mouse->xy.x - x);
+ dy = abs(mouse->xy.y - y);
+ if(mouse->buttons != b || dx >= 3 || dy >= 3)
+ break;
+ clickcount++;
+ clickmsec = mouse->msec;
+ }
+ mouse->xy.x = x; /* in case we're calling frselect */
+ mouse->xy.y = y;
+ q0 = t->q0; /* may have changed */
+ q1 = t->q1;
+ selectq = t->org+frcharofpt(t, mouse->xy);;
+ }
+ if(mouse->buttons == b && clickcount == 0){
+ t->Frame.scroll = framescroll;
+ frselect(t, mousectl);
+ /* horrible botch: while asleep, may have lost selection altogether */
+ if(selectq > t->file->nc)
+ selectq = t->org + t->p0;
+ t->Frame.scroll = nil;
+ if(selectq < t->org)
+ q0 = selectq;
+ else
+ q0 = t->org + t->p0;
+ if(selectq > t->org+t->nchars)
+ q1 = selectq;
+ else
+ q1 = t->org+t->p1;
+ }
+ if(q0 == q1){
+ if(q0==t->q0 && mouse->msec-clickmsec<500)
+ textstretchsel(t, selectq, &q0, &q1, clickcount);
+ else
+ clicktext = t;
+ clickmsec = mouse->msec;
+ }else
+ clicktext = nil;
+ textsetselect(t, q0, q1);
+ flushimage(display, 1);
+ state = 0; /* undo when possible; +1 for cut, -1 for paste */
+ while(mouse->buttons){
+ mouse->msec = 0;
+ b = mouse->buttons;
+ if((b&1) && (b&6)){
+ if(state==0 && t->what==Body){
+ seq++;
+ filemark(t->w->body.file);
+ }
+ if(b & 2){
+ if(state==-1 && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q0);
+ state = 0;
+ }else if(state != 1){
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ state = 1;
+ }
+ }else{
+ if(state==1 && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q1);
+ state = 0;
+ }else if(state != -1){
+ paste(t, t, nil, TRUE, FALSE, nil, 0);
+ state = -1;
+ }
+ }
+ textscrdraw(t);
+ clearmouse();
+ }
+ flushimage(display, 1);
+ while(mouse->buttons == b)
+ readmouse(mousectl);
+ if(mouse->msec-clickmsec >= 500)
+ clicktext = nil;
+ }
+}
+
+void
+textshow(Text *t, uint q0, uint q1, int doselect)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ if(t->what != Body){
+ if(doselect)
+ textsetselect(t, q0, q1);
+ return;
+ }
+ if(t->w!=nil && t->maxlines==0)
+ colgrow(t->col, t->w, 1);
+ if(doselect)
+ textsetselect(t, q0, q1);
+ qe = t->org+t->nchars;
+ if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
+ textscrdraw(t);
+ else{
+ if(t->w->nopen[QWevent] > 0)
+ nl = 3*t->maxlines/4;
+ else
+ nl = t->maxlines/4;
+ q = textbacknl(t, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>t->org && q<t->org))
+ textsetorigin(t, q, TRUE);
+ while(q0 > t->org+t->nchars)
+ textsetorigin(t, t->org+1, FALSE);
+ }
+}
+
+static
+int
+region(int a, int b)
+{
+ if(a < b)
+ return -1;
+ if(a == b)
+ return 0;
+ return 1;
+}
+
+void
+selrestore(Frame *f, Point pt0, uint p0, uint p1)
+{
+ if(p1<=f->p0 || p0>=f->p1){
+ /* no overlap */
+ frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
+ return;
+ }
+ if(p0>=f->p0 && p1<=f->p1){
+ /* entirely inside */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+ return;
+ }
+
+ /* they now are known to overlap */
+
+ /* before selection */
+ if(p0 < f->p0){
+ frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
+ p0 = f->p0;
+ pt0 = frptofchar(f, p0);
+ }
+ /* after selection */
+ if(p1 > f->p1){
+ frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
+ p1 = f->p1;
+ }
+ /* inside selection */
+ frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
+}
+
+void
+textsetselect(Text *t, uint q0, uint q1)
+{
+ int p0, p1;
+
+ /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
+ t->q0 = q0;
+ t->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-t->org;
+ p1 = q1-t->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > t->nchars)
+ p0 = t->nchars;
+ if(p1 > t->nchars)
+ p1 = t->nchars;
+ if(p0==t->p0 && p1==t->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
+ frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < t->p0){
+ /* extend selection backwards */
+ frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
+ }else if(p0 > t->p0){
+ /* trim first part of selection */
+ frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
+ }
+ if(p1 > t->p1){
+ /* extend selection forwards */
+ frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
+ }else if(p1 < t->p1){
+ /* trim last part of selection */
+ frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
+ }
+
+ Return:
+ t->p0 = p0;
+ t->p1 = p1;
+}
+
+/*
+ * Release the button in less than DELAY ms and it's considered a null selection
+ * if the mouse hardly moved, regardless of whether it crossed a char boundary.
+ */
+enum {
+ DELAY = 2,
+ MINMOVE = 4,
+};
+
+uint
+xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
+{
+ uint p0, p1, q, tmp;
+ ulong msec;
+ Point mp, pt0, pt1, qt;
+ int reg, b;
+
+ mp = mc->xy;
+ b = mc->buttons;
+ msec = mc->msec;
+
+ /* remove tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 0);
+ p0 = p1 = frcharofpt(f, mp);
+ pt0 = frptofchar(f, p0);
+ pt1 = frptofchar(f, p1);
+ reg = 0;
+ frtick(f, pt0, 1);
+ do{
+ q = frcharofpt(f, mc->xy);
+ if(p1 != q){
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ if(reg != region(q, p0)){ /* crossed starting point; reset */
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ pt1 = pt0;
+ reg = region(q, p0);
+ if(reg == 0)
+ frdrawsel0(f, pt0, p0, p1, col, display->white);
+ }
+ qt = frptofchar(f, q);
+ if(reg > 0){
+ if(q > p1)
+ frdrawsel0(f, pt1, p1, q, col, display->white);
+
+ else if(q < p1)
+ selrestore(f, qt, q, p1);
+ }else if(reg < 0){
+ if(q > p1)
+ selrestore(f, pt1, p1, q);
+ else
+ frdrawsel0(f, qt, q, p1, col, display->white);
+ }
+ p1 = q;
+ pt1 = qt;
+ }
+ if(p0 == p1)
+ frtick(f, pt0, 1);
+ flushimage(f->display, 1);
+ readmouse(mc);
+ }while(mc->buttons == b);
+ if(mc->msec-msec < DELAY && p0!=p1
+ && abs(mp.x-mc->xy.x)<MINMOVE
+ && abs(mp.y-mc->xy.y)<MINMOVE) {
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ }
+ if(p1 < p0){
+ tmp = p0;
+ p0 = p1;
+ p1 = tmp;
+ }
+ pt0 = frptofchar(f, p0);
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ selrestore(f, pt0, p0, p1);
+ /* restore tick */
+ if(f->p0 == f->p1)
+ frtick(f, frptofchar(f, f->p0), 1);
+ flushimage(f->display, 1);
+ *p1p = p1;
+ return p0;
+}
+
+int
+textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
+{
+ uint p0, p1;
+ int buts;
+
+ p0 = xselect(t, mousectl, high, &p1);
+ buts = mousectl->buttons;
+ if((buts & mask) == 0){
+ *q0 = p0+t->org;
+ *q1 = p1+t->org;
+ }
+
+ while(mousectl->buttons)
+ readmouse(mousectl);
+ return buts;
+}
+
+int
+textselect2(Text *t, uint *q0, uint *q1, Text **tp)
+{
+ int buts;
+
+ *tp = nil;
+ buts = textselect23(t, q0, q1, but2col, 4);
+ if(buts & 4)
+ return 0;
+ if(buts & 1){ /* pick up argument */
+ *tp = argtext;
+ return 1;
+ }
+ return 1;
+}
+
+int
+textselect3(Text *t, uint *q0, uint *q1)
+{
+ int h;
+
+ h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
+ return h;
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\n', 0 };
+static Rune left3[] = { L'\'', L'"', L'`', 0 };
+
+static
+Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static
+Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? isalnum(r) : r && !isspace(r);
+}
+
+void
+textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ *q0 = mp;
+ *q1 = mp;
+ for(i=0; left[i]!=nil; i++){
+ q = *q0;
+ l = left[i];
+ r = right[i];
+ /* try matching character to left, looking right */
+ if(q == 0)
+ c = '\n';
+ else
+ c = textreadc(t, q-1);
+ p = runestrchr(l, c);
+ if(p != nil){
+ if(textclickmatch(t, c, r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == t->file->nc)
+ c = '\n';
+ else
+ c = textreadc(t, q);
+ p = runestrchr(r, c);
+ if(p != nil){
+ if(textclickmatch(t, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<t->file->nc && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<t->file->nc && inmode(textreadc(t, *q1), mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(textreadc(t, *q0-1), mode))
+ (*q0)--;
+}
+
+int
+textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == t->file->nc)
+ break;
+ c = textreadc(t, *q);
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = textreadc(t, *q);
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+uint
+textbacknl(Text *t, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && textreadc(t, p-1)!='\n')
+ n = 1;
+ i = n;
+ while(i-->0 && p>0){
+ --p; /* it's at a newline now; back over it */
+ if(p == 0)
+ break;
+ /* at 128 chars, call it a line anyway */
+ for(j=128; --j>0 && p>0; p--)
+ if(textreadc(t, p-1)=='\n')
+ break;
+ }
+ return p;
+}
+
+void
+textsetorigin(Text *t, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact && textreadc(t, org-1) != '\n'){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<t->file->nc; i++){
+ if(textreadc(t, org) == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-t->org;
+ fixup = 0;
+ if(a>=0 && a<t->nchars){
+ frdelete(t, 0, a);
+ fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+ }
+ else if(a<0 && -a<t->nchars){
+ n = t->org - org;
+ r = runemalloc(n);
+ bufread(t->file, org, r, n);
+ frinsert(t, r, r+n, 0);
+ free(r);
+ }else
+ frdelete(t, 0, t->nchars);
+ t->org = org;
+ textfill(t);
+ textscrdraw(t);
+ textsetselect(t, t->q0, t->q1);
+ if(fixup && t->p1 > t->p0)
+ frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
+}
+
+void
+textreset(Text *t)
+{
+ t->file->seq = 0;
+ t->eq0 = ~0;
+ /* do t->delete(0, t->nc, TRUE) without building backup stuff */
+ textsetselect(t, t->org, t->org);
+ frdelete(t, 0, t->nchars);
+ t->org = 0;
+ t->q0 = 0;
+ t->q1 = 0;
+ filereset(t->file);
+ bufreset(t->file);
+}
diff --git a/patch/diff-c-plus/diffio.c b/patch/diff-c-plus/diffio.c
new file mode 100644
index 0000000..146bef3
--- /dev/null
+++ b/patch/diff-c-plus/diffio.c
@@ -0,0 +1,401 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "diff.h"
+
+struct line {
+ int serial;
+ int value;
+};
+extern struct line *file[2];
+extern int len[2];
+extern long *ixold, *ixnew;
+extern int *J;
+
+static Biobuf *input[2];
+static char *file1, *file2;
+static int firstchange;
+
+#define MAXLINELEN 4096
+#define MIN(x, y) ((x) < (y) ? (x): (y))
+
+static int
+readline(Biobuf *bp, char *buf)
+{
+ int c;
+ char *p, *e;
+
+ p = buf;
+ e = p + MAXLINELEN-1;
+ do {
+ c = Bgetc(bp);
+ if (c < 0) {
+ if (p == buf)
+ return -1;
+ break;
+ }
+ if (c == '\n')
+ break;
+ *p++ = c;
+ } while (p < e);
+ *p = 0;
+ if (c != '\n' && c >= 0) {
+ do c = Bgetc(bp);
+ while (c >= 0 && c != '\n');
+ }
+ return p - buf;
+}
+
+#define HALFLONG 16
+#define low(x) (x&((1L<<HALFLONG)-1))
+#define high(x) (x>>HALFLONG)
+
+/*
+ * hashing has the effect of
+ * arranging line in 7-bit bytes and then
+ * summing 1-s complement in 16-bit hunks
+ */
+static int
+readhash(Biobuf *bp, char *buf)
+{
+ long sum;
+ unsigned shift;
+ char *p;
+ int len, space;
+
+ sum = 1;
+ shift = 0;
+ if ((len = readline(bp, buf)) == -1)
+ return 0;
+ p = buf;
+ switch(bflag) /* various types of white space handling */
+ {
+ case 0:
+ while (len--) {
+ sum += (long)*p++ << (shift &= (HALFLONG-1));
+ shift += 7;
+ }
+ break;
+ case 1:
+ /*
+ * coalesce multiple white-space
+ */
+ for (space = 0; len--; p++) {
+ if (isspace(*p)) {
+ space++;
+ continue;
+ }
+ if (space) {
+ shift += 7;
+ space = 0;
+ }
+ sum += (long)*p << (shift &= (HALFLONG-1));
+ shift += 7;
+ }
+ break;
+ default:
+ /*
+ * strip all white-space
+ */
+ while (len--) {
+ if (isspace(*p)) {
+ p++;
+ continue;
+ }
+ sum += (long)*p++ << (shift &= (HALFLONG-1));
+ shift += 7;
+ }
+ break;
+ }
+ sum = low(sum) + high(sum);
+ return ((short)low(sum) + (short)high(sum));
+}
+
+Biobuf *
+prepare(int i, char *arg)
+{
+ struct line *p;
+ int j, h;
+ Biobuf *bp;
+ char *cp, buf[MAXLINELEN];
+ int nbytes;
+ Rune r;
+
+ bp = Bopen(arg, OREAD);
+ if (!bp) {
+ panic(mflag ? 0: 2, "cannot open %s: %r\n", arg);
+ return 0;
+ }
+ if (binary)
+ return bp;
+ nbytes = Bread(bp, buf, MIN(1024, MAXLINELEN));
+ if (nbytes > 0) {
+ cp = buf;
+ while (cp < buf+nbytes-UTFmax) {
+ /*
+ * heuristic for a binary file in the
+ * brave new UNICODE world
+ */
+ cp += chartorune(&r, cp);
+ if (r == 0 || (r > 0x7f && r <= 0xa0)) {
+ binary++;
+ return bp;
+ }
+ }
+ Bseek(bp, 0, 0);
+ }
+ p = MALLOC(struct line, 3);
+ for (j = 0; h = readhash(bp, buf); p[j].value = h)
+ p = REALLOC(p, struct line, (++j+3));
+ len[i] = j;
+ file[i] = p;
+ input[i] = bp; /*fix*/
+ if (i == 0) { /*fix*/
+ file1 = arg;
+ firstchange = 0;
+ }
+ else
+ file2 = arg;
+ return bp;
+}
+
+static int
+squishspace(char *buf)
+{
+ char *p, *q;
+ int space;
+
+ for (space = 0, q = p = buf; *q; q++) {
+ if (isspace(*q)) {
+ space++;
+ continue;
+ }
+ if (space && bflag == 1) {
+ *p++ = ' ';
+ space = 0;
+ }
+ *p++ = *q;
+ }
+ *p = 0;
+ return p - buf;
+}
+
+/*
+ * need to fix up for unexpected EOF's
+ */
+void
+check(Biobuf *bf, Biobuf *bt)
+{
+ int f, t, flen, tlen;
+ char fbuf[MAXLINELEN], tbuf[MAXLINELEN];
+
+ ixold[0] = ixnew[0] = 0;
+ for (f = t = 1; f < len[0]; f++) {
+ flen = readline(bf, fbuf);
+ ixold[f] = ixold[f-1] + flen + 1; /* ftell(bf) */
+ if (J[f] == 0)
+ continue;
+ do {
+ tlen = readline(bt, tbuf);
+ ixnew[t] = ixnew[t-1] + tlen + 1; /* ftell(bt) */
+ } while (t++ < J[f]);
+ if (bflag) {
+ flen = squishspace(fbuf);
+ tlen = squishspace(tbuf);
+ }
+ if (flen != tlen || strcmp(fbuf, tbuf))
+ J[f] = 0;
+ }
+ while (t < len[1]) {
+ tlen = readline(bt, tbuf);
+ ixnew[t] = ixnew[t-1] + tlen + 1; /* fseek(bt) */
+ t++;
+ }
+}
+
+static void
+range(int a, int b, char *separator)
+{
+ Bprint(&stdout, "%d", a > b ? b: a);
+ if (a < b)
+ Bprint(&stdout, "%s%d", separator, b);
+}
+
+static void
+fetch(long *f, int a, int b, Biobuf *bp, char *s)
+{
+ char buf[MAXLINELEN];
+ int maxb;
+
+ if(a <= 1)
+ a = 1;
+ if(bp == input[0])
+ maxb = len[0];
+ else
+ maxb = len[1];
+ if(b > maxb)
+ b = maxb;
+ if(a > maxb)
+ return;
+ Bseek(bp, f[a-1], 0);
+ while (a++ <= b) {
+ readline(bp, buf);
+ Bprint(&stdout, "%s%s\n", s, buf);
+ }
+}
+
+typedef struct Change Change;
+struct Change
+{
+ int a;
+ int b;
+ int c;
+ int d;
+};
+
+Change *changes;
+int nchanges;
+
+void
+change(int a, int b, int c, int d)
+{
+ char verb;
+ char buf[4];
+ Change *ch;
+
+ if (a > b && c > d)
+ return;
+ anychange = 1;
+ if (mflag && firstchange == 0) {
+ if(mode) {
+ buf[0] = '-';
+ buf[1] = mode;
+ buf[2] = ' ';
+ buf[3] = '\0';
+ } else {
+ buf[0] = '\0';
+ }
+ Bprint(&stdout, "diff %s%s %s\n", buf, file1, file2);
+ firstchange = 1;
+ }
+ verb = a > b ? 'a': c > d ? 'd': 'c';
+ switch(mode) {
+ case 'e':
+ range(a, b, ",");
+ Bputc(&stdout, verb);
+ break;
+ case 0:
+ range(a, b, ",");
+ Bputc(&stdout, verb);
+ range(c, d, ",");
+ break;
+ case 'n':
+ Bprint(&stdout, "%s:", file1);
+ range(a, b, ",");
+ Bprint(&stdout, " %c ", verb);
+ Bprint(&stdout, "%s:", file2);
+ range(c, d, ",");
+ break;
+ case 'f':
+ Bputc(&stdout, verb);
+ range(a, b, " ");
+ break;
+ case 'c':
+ case 'a':
+ case 'u':
+ if(nchanges%1024 == 0)
+ changes = erealloc(changes, (nchanges+1024)*sizeof(changes[0]));
+ ch = &changes[nchanges++];
+ ch->a = a;
+ ch->b = b;
+ ch->c = c;
+ ch->d = d;
+ return;
+ }
+ Bputc(&stdout, '\n');
+ if (mode == 0 || mode == 'n') {
+ fetch(ixold, a, b, input[0], "< ");
+ if (a <= b && c <= d)
+ Bprint(&stdout, "---\n");
+ }
+ fetch(ixnew, c, d, input[1], mode == 0 || mode == 'n' ? "> ": "");
+ if (mode != 0 && mode != 'n' && c <= d)
+ Bprint(&stdout, ".\n");
+}
+
+enum
+{
+ Lines = 3, /* number of lines of context shown */
+};
+
+int
+changeset(int i)
+{
+ while(i<nchanges && changes[i].b+1+2*Lines > changes[i+1].a)
+ i++;
+ if(i<nchanges)
+ return i+1;
+ return nchanges;
+}
+
+void
+fileheader(void)
+{
+ if(mode != 'u')
+ return;
+ Bprint(&stdout, "--- %s\n", file1);
+ Bprint(&stdout, "+++ %s\n", file2);
+}
+
+void
+flushchanges(void)
+{
+ int a, b, c, d, at;
+ int i, j;
+
+ if(nchanges == 0)
+ return;
+
+ for(i=0; i<nchanges; ){
+ j = changeset(i);
+ a = changes[i].a-Lines;
+ b = changes[j-1].b+Lines;
+ c = changes[i].c-Lines;
+ d = changes[j-1].d+Lines;
+ if(a < 1)
+ a = 1;
+ if(c < 1)
+ c = 1;
+ if(b > len[0])
+ b = len[0];
+ if(d > len[1])
+ d = len[1];
+ if(mode == 'a'){
+ a = 1;
+ b = len[0];
+ c = 1;
+ d = len[1];
+ j = nchanges;
+ }
+ if(mode == 'u'){
+ Bprint(&stdout, "@@ -%d,%d +%d,%d @@\n", a, b-a+1, c, d-c+1);
+ }else{
+ Bprint(&stdout, "%s:", file1);
+ range(a, b, ",");
+ Bprint(&stdout, " - ");
+ Bprint(&stdout, "%s:", file2);
+ range(c, d, ",");
+ Bputc(&stdout, '\n');
+ }
+ at = a;
+ for(; i<j; i++){
+ fetch(ixold, at, changes[i].a-1, input[0], mode == 'u' ? " " : " ");
+ fetch(ixold, changes[i].a, changes[i].b, input[0], mode == 'u' ? "-" : "- ");
+ fetch(ixnew, changes[i].c, changes[i].d, input[1], mode == 'u' ? "+" : "+ ");
+ at = changes[i].b+1;
+ }
+ fetch(ixold, at, b, input[0], mode == 'u' ? " " : " ");
+ }
+ nchanges = 0;
+}
diff --git a/patch/diff-c-plus/diffio.c.orig b/patch/diff-c-plus/diffio.c.orig
new file mode 100644
index 0000000..d937b1f
--- /dev/null
+++ b/patch/diff-c-plus/diffio.c.orig
@@ -0,0 +1,401 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "diff.h"
+
+struct line {
+ int serial;
+ int value;
+};
+extern struct line *file[2];
+extern int len[2];
+extern long *ixold, *ixnew;
+extern int *J;
+
+static Biobuf *input[2];
+static char *file1, *file2;
+static int firstchange;
+
+#define MAXLINELEN 4096
+#define MIN(x, y) ((x) < (y) ? (x): (y))
+
+static int
+readline(Biobuf *bp, char *buf)
+{
+ int c;
+ char *p, *e;
+
+ p = buf;
+ e = p + MAXLINELEN-1;
+ do {
+ c = Bgetc(bp);
+ if (c < 0) {
+ if (p == buf)
+ return -1;
+ break;
+ }
+ if (c == '\n')
+ break;
+ *p++ = c;
+ } while (p < e);
+ *p = 0;
+ if (c != '\n' && c >= 0) {
+ do c = Bgetc(bp);
+ while (c >= 0 && c != '\n');
+ }
+ return p - buf;
+}
+
+#define HALFLONG 16
+#define low(x) (x&((1L<<HALFLONG)-1))
+#define high(x) (x>>HALFLONG)
+
+/*
+ * hashing has the effect of
+ * arranging line in 7-bit bytes and then
+ * summing 1-s complement in 16-bit hunks
+ */
+static int
+readhash(Biobuf *bp, char *buf)
+{
+ long sum;
+ unsigned shift;
+ char *p;
+ int len, space;
+
+ sum = 1;
+ shift = 0;
+ if ((len = readline(bp, buf)) == -1)
+ return 0;
+ p = buf;
+ switch(bflag) /* various types of white space handling */
+ {
+ case 0:
+ while (len--) {
+ sum += (long)*p++ << (shift &= (HALFLONG-1));
+ shift += 7;
+ }
+ break;
+ case 1:
+ /*
+ * coalesce multiple white-space
+ */
+ for (space = 0; len--; p++) {
+ if (isspace(*p)) {
+ space++;
+ continue;
+ }
+ if (space) {
+ shift += 7;
+ space = 0;
+ }
+ sum += (long)*p << (shift &= (HALFLONG-1));
+ shift += 7;
+ }
+ break;
+ default:
+ /*
+ * strip all white-space
+ */
+ while (len--) {
+ if (isspace(*p)) {
+ p++;
+ continue;
+ }
+ sum += (long)*p++ << (shift &= (HALFLONG-1));
+ shift += 7;
+ }
+ break;
+ }
+ sum = low(sum) + high(sum);
+ return ((short)low(sum) + (short)high(sum));
+}
+
+Biobuf *
+prepare(int i, char *arg)
+{
+ struct line *p;
+ int j, h;
+ Biobuf *bp;
+ char *cp, buf[MAXLINELEN];
+ int nbytes;
+ Rune r;
+
+ bp = Bopen(arg, OREAD);
+ if (!bp) {
+ panic(mflag ? 0: 2, "cannot open %s: %r\n", arg);
+ return 0;
+ }
+ if (binary)
+ return bp;
+ nbytes = Bread(bp, buf, MIN(1024, MAXLINELEN));
+ if (nbytes > 0) {
+ cp = buf;
+ while (cp < buf+nbytes-UTFmax) {
+ /*
+ * heuristic for a binary file in the
+ * brave new UNICODE world
+ */
+ cp += chartorune(&r, cp);
+ if (r == 0 || (r > 0x7f && r <= 0xa0)) {
+ binary++;
+ return bp;
+ }
+ }
+ Bseek(bp, 0, 0);
+ }
+ p = MALLOC(struct line, 3);
+ for (j = 0; h = readhash(bp, buf); p[j].value = h)
+ p = REALLOC(p, struct line, (++j+3));
+ len[i] = j;
+ file[i] = p;
+ input[i] = bp; /*fix*/
+ if (i == 0) { /*fix*/
+ file1 = arg;
+ firstchange = 0;
+ }
+ else
+ file2 = arg;
+ return bp;
+}
+
+static int
+squishspace(char *buf)
+{
+ char *p, *q;
+ int space;
+
+ for (space = 0, q = p = buf; *q; q++) {
+ if (isspace(*q)) {
+ space++;
+ continue;
+ }
+ if (space && bflag == 1) {
+ *p++ = ' ';
+ space = 0;
+ }
+ *p++ = *q;
+ }
+ *p = 0;
+ return p - buf;
+}
+
+/*
+ * need to fix up for unexpected EOF's
+ */
+void
+check(Biobuf *bf, Biobuf *bt)
+{
+ int f, t, flen, tlen;
+ char fbuf[MAXLINELEN], tbuf[MAXLINELEN];
+
+ ixold[0] = ixnew[0] = 0;
+ for (f = t = 1; f < len[0]; f++) {
+ flen = readline(bf, fbuf);
+ ixold[f] = ixold[f-1] + flen + 1; /* ftell(bf) */
+ if (J[f] == 0)
+ continue;
+ do {
+ tlen = readline(bt, tbuf);
+ ixnew[t] = ixnew[t-1] + tlen + 1; /* ftell(bt) */
+ } while (t++ < J[f]);
+ if (bflag) {
+ flen = squishspace(fbuf);
+ tlen = squishspace(tbuf);
+ }
+ if (flen != tlen || strcmp(fbuf, tbuf))
+ J[f] = 0;
+ }
+ while (t < len[1]) {
+ tlen = readline(bt, tbuf);
+ ixnew[t] = ixnew[t-1] + tlen + 1; /* fseek(bt) */
+ t++;
+ }
+}
+
+static void
+range(int a, int b, char *separator)
+{
+ Bprint(&stdout, "%d", a > b ? b: a);
+ if (a < b)
+ Bprint(&stdout, "%s%d", separator, b);
+}
+
+static void
+fetch(long *f, int a, int b, Biobuf *bp, char *s)
+{
+ char buf[MAXLINELEN];
+ int maxb;
+
+ if(a <= 1)
+ a = 1;
+ if(bp == input[0])
+ maxb = len[0];
+ else
+ maxb = len[1];
+ if(b > maxb)
+ b = maxb;
+ if(a > maxb)
+ return;
+ Bseek(bp, f[a-1], 0);
+ while (a++ <= b) {
+ readline(bp, buf);
+ Bprint(&stdout, "%s%s\n", s, buf);
+ }
+}
+
+typedef struct Change Change;
+struct Change
+{
+ int a;
+ int b;
+ int c;
+ int d;
+};
+
+Change *changes;
+int nchanges;
+
+void
+change(int a, int b, int c, int d)
+{
+ char verb;
+ char buf[4];
+ Change *ch;
+
+ if (a > b && c > d)
+ return;
+ anychange = 1;
+ if (mflag && firstchange == 0) {
+ if(mode) {
+ buf[0] = '-';
+ buf[1] = mode;
+ buf[2] = ' ';
+ buf[3] = '\0';
+ } else {
+ buf[0] = '\0';
+ }
+ Bprint(&stdout, "diff %s%s %s\n", buf, file1, file2);
+ firstchange = 1;
+ }
+ verb = a > b ? 'a': c > d ? 'd': 'c';
+ switch(mode) {
+ case 'e':
+ range(a, b, ",");
+ Bputc(&stdout, verb);
+ break;
+ case 0:
+ range(a, b, ",");
+ Bputc(&stdout, verb);
+ range(c, d, ",");
+ break;
+ case 'n':
+ Bprint(&stdout, "%s:", file1);
+ range(a, b, ",");
+ Bprint(&stdout, " %c ", verb);
+ Bprint(&stdout, "%s:", file2);
+ range(c, d, ",");
+ break;
+ case 'f':
+ Bputc(&stdout, verb);
+ range(a, b, " ");
+ break;
+ case 'c':
+ case 'a':
+ case 'u':
+ if(nchanges%1024 == 0)
+ changes = erealloc(changes, (nchanges+1024)*sizeof(changes[0]));
+ ch = &changes[nchanges++];
+ ch->a = a;
+ ch->b = b;
+ ch->c = c;
+ ch->d = d;
+ return;
+ }
+ Bputc(&stdout, '\n');
+ if (mode == 0 || mode == 'n') {
+ fetch(ixold, a, b, input[0], "< ");
+ if (a <= b && c <= d)
+ Bprint(&stdout, "---\n");
+ }
+ fetch(ixnew, c, d, input[1], mode == 0 || mode == 'n' ? "> ": "");
+ if (mode != 0 && mode != 'n' && c <= d)
+ Bprint(&stdout, ".\n");
+}
+
+enum
+{
+ Lines = 3, /* number of lines of context shown */
+};
+
+int
+changeset(int i)
+{
+ while(i<nchanges && changes[i].b+1+2*Lines > changes[i+1].a)
+ i++;
+ if(i<nchanges)
+ return i+1;
+ return nchanges;
+}
+
+void
+fileheader(void)
+{
+ if(mode != 'u')
+ return;
+ Bprint(&stdout, "--- %s\n", file1);
+ Bprint(&stdout, "+++ %s\n", file2);
+}
+
+void
+flushchanges(void)
+{
+ int a, b, c, d, at;
+ int i, j;
+
+ if(nchanges == 0)
+ return;
+
+ for(i=0; i<nchanges; ){
+ j = changeset(i);
+ a = changes[i].a-Lines;
+ b = changes[j-1].b+Lines;
+ c = changes[i].c-Lines;
+ d = changes[j-1].d+Lines;
+ if(a < 1)
+ a = 1;
+ if(c < 1)
+ c = 1;
+ if(b > len[0])
+ b = len[0];
+ if(d > len[1])
+ d = len[1];
+ if(mode == 'a'){
+ a = 1;
+ b = len[0];
+ c = 1;
+ d = len[1];
+ j = nchanges;
+ }
+ if(mode == 'u'){
+ Bprint(&stdout, "@@ -%d,%d +%d,%d @@\n", a, b-a+1, c, d-c+1);
+ }else{
+ Bprint(&stdout, "%s:", file1);
+ range(a, b, ",");
+ Bprint(&stdout, " - ");
+ Bprint(&stdout, "%s:", file2);
+ range(c, d, ",");
+ Bputc(&stdout, '\n');
+ }
+ at = a;
+ for(; i<j; i++){
+ fetch(ixold, at, changes[i].a-1, input[0], mode == 'u' ? " " : " ");
+ fetch(ixold, changes[i].a, changes[i].b, input[0], mode == 'u' ? "-" : "- ");
+ fetch(ixnew, changes[i].c, changes[i].d, input[1], mode == 'u' ? "+" : "- ");
+ at = changes[i].b+1;
+ }
+ fetch(ixold, at, b, input[0], mode == 'u' ? " " : " ");
+ }
+ nchanges = 0;
+}
diff --git a/patch/diff-c-plus/email b/patch/diff-c-plus/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/diff-c-plus/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/diff-c-plus/files b/patch/diff-c-plus/files
new file mode 100644
index 0000000..0b7672d
--- /dev/null
+++ b/patch/diff-c-plus/files
@@ -0,0 +1 @@
+/sys/src/cmd/diff/diffio.c diffio.c
diff --git a/patch/diff-c-plus/notes b/patch/diff-c-plus/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/diff-c-plus/notes
diff --git a/patch/diff-c-plus/readme b/patch/diff-c-plus/readme
new file mode 100644
index 0000000..2fdc300
--- /dev/null
+++ b/patch/diff-c-plus/readme
@@ -0,0 +1,6 @@
+Use + (plus) prefix for added lines with -c
+
+This fixes the bug where all lines, both added and removed,
+use a - (minus) prefix when diff is run with the -c option.
+
+The bug is specific to 9front. It was fixed on 16 Nov 2020.
diff --git a/patch/patch-create-i/create b/patch/patch-create-i/create
new file mode 100644
index 0000000..c9fcf24
--- /dev/null
+++ b/patch/patch-create-i/create
@@ -0,0 +1,98 @@
+#!/bin/rc
+rfork e
+
+fn xchmod {
+ chmod $* >[2]/dev/null
+}
+
+flagfmt='i'
+args='name email file... [< description]'
+
+if(! ifs=() eval `{aux/getflags $*} || ~ $#* 0 1 2){
+ aux/usage
+ exit usage
+}
+
+if(! echo $1 | grep -s '^[a-z_0-9.\-]+$'){
+ echo 'bad name: [a-z0-9._\-]+ only' >[1=2]
+ exit usage
+}
+if(! echo $2 | grep -s '^(-|[A-Za-z0-9.\-+]+@[A-Za-z0-9.\-+]+)$'){
+ echo 'bad email: [a-z0-9.-+] only; use ''-'' to not leave an email address.' >[1=2]
+ exit usage
+}
+
+if(! test -d /n/sources/patch){
+ rfork n
+ 9fs sources
+}
+
+patch=$1
+email=$2
+shift
+shift
+d=/n/sources/patch/$patch
+if(! mkdir $d){
+ echo mkdir $d failed >[1=2]
+ exit mkdir
+}
+if(! ~ $email -){
+ echo $email >$d/email
+}
+
+xchmod o-w $d
+>$d/readme
+>$d/files
+>$d/notes
+for(i in $*){
+ i=`{cleanname -d `{pwd} $i}
+ if(! test -f $i){
+ echo error: cannot find $i >[1=2]
+ rm -rf $d
+ exit oops
+ }
+ short=`{basename $i}
+ uniq=$short
+ n=0
+ while(test -f $d/$uniq){
+ uniq=$short.$n
+ n=`{echo 1+$n | hoc}
+ }
+ if(test -f /n/sources/plan9/$i){
+ cp /n/sources/plan9/$i $d/$uniq.orig
+ if(~ $flagi 1)
+ idiff $d/$uniq.orig $i > $d/$uniq
+ if not
+ cp $i $d/$uniq
+ if(~ $status ''){
+ echo $i $uniq >>$d/files
+ if(cmp -s /n/sources/plan9/$i $i)
+ echo warning: new file $i does not differ from sources >[1=2]
+ }
+ if not
+ echo warning: skipping file $i >[1=2]
+ }
+ if not{
+ echo warning: new file $i not on sources >[1=2]
+ cp $i $d/$uniq
+ echo $i $uniq >>$d/files
+ }
+}
+@{builtin cd $d && xchmod ug+rw * && xchmod a+r *}
+
+if(~ `{cat /proc/$pid/fd | awk 'NR==2{print $NF}'} */dev/cons && test -w /dev/consctl){
+ >/dev/consctl {
+ echo holdon
+ cat >$d/readme
+ }
+}
+if not
+ cat >$d/readme
+
+if(! test -s $d/readme){
+ echo 'no description given; aborting' >[1=2]
+ rm -rf $d
+ exit oops
+}
+
+echo $d
diff --git a/patch/patch-create-i/create.orig b/patch/patch-create-i/create.orig
new file mode 100755
index 0000000..55e659a
--- /dev/null
+++ b/patch/patch-create-i/create.orig
@@ -0,0 +1,85 @@
+#!/bin/rc
+rfork e
+
+fn xchmod {
+ chmod $* >[2]/dev/null
+}
+
+if(~ $#* 0 1 2){
+ echo 'usage: patch/create name email file... [< description]' >[1=2]
+ exit usage
+}
+
+if(! echo $1 | grep -s '^[a-z_0-9.\-]+$'){
+ echo 'bad name: [a-z0-9._\-]+ only' >[1=2]
+ exit usage
+}
+if(! echo $2 | grep -s '^(-|[A-Za-z0-9.\-+]+@[A-Za-z0-9.\-+]+)$'){
+ echo 'bad email: [a-z0-9.-+] only; use ''-'' to not leave an email address.' >[1=2]
+ exit usage
+}
+
+if(! test -d /n/sources/patch){
+ rfork n
+ 9fs sources
+}
+
+patch=$1
+email=$2
+shift
+shift
+d=/n/sources/patch/$patch
+if(! mkdir $d){
+ echo mkdir $d failed >[1=2]
+ exit mkdir
+}
+if(! ~ $email -){
+ echo $email >$d/email
+}
+
+xchmod o-w $d
+>$d/readme
+>$d/files
+>$d/notes
+for(i in $*){
+ i=`{cleanname -d `{pwd} $i}
+ if(! test -f $i){
+ echo error: cannot find $i >[1=2]
+ rm -rf $d
+ exit oops
+ }
+ short=`{basename $i}
+ uniq=$short
+ n=0
+ while(test -f $d/$uniq){
+ uniq=$short.$n
+ n=`{echo 1+$n | hoc}
+ }
+ cp $i $d/$uniq
+ if(test -f /n/sources/plan9/$i){
+ if(cmp -s /n/sources/plan9/$i $i)
+ echo warning: new file $i does not differ from sources >[1=2]
+ cp /n/sources/plan9/$i $d/$uniq.orig
+ }
+ if not
+ echo warning: new file $i not on sources >[1=2]
+ echo $i $uniq >>$d/files
+}
+@{builtin cd $d && xchmod ug+rw * && xchmod a+r *}
+
+if(~ `{cat /proc/$pid/fd | awk 'NR==2{print $NF}'} */dev/cons && test -w /dev/consctl){
+ >/dev/consctl {
+ echo holdon
+ cat >$d/readme
+ }
+}
+if not
+ cat >$d/readme
+
+if(! test -s $d/readme){
+ echo 'no description given; aborting' >[1=2]
+ rm -rf $d
+ exit oops
+}
+
+echo $d
diff --git a/patch/patch-create-i/email b/patch/patch-create-i/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/patch-create-i/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/patch-create-i/files b/patch/patch-create-i/files
new file mode 100644
index 0000000..5d6e99c
--- /dev/null
+++ b/patch/patch-create-i/files
@@ -0,0 +1 @@
+/rc/bin/patch/create create
diff --git a/patch/patch-create-i/notes b/patch/patch-create-i/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/patch-create-i/notes
diff --git a/patch/patch-create-i/readme b/patch/patch-create-i/readme
new file mode 100644
index 0000000..01147b0
--- /dev/null
+++ b/patch/patch-create-i/readme
@@ -0,0 +1 @@
+patch/create -i creates patch interactively with idiff
diff --git a/patch/patch-create-skip-unchanged/create b/patch/patch-create-skip-unchanged/create
new file mode 100644
index 0000000..dd078f1
--- /dev/null
+++ b/patch/patch-create-skip-unchanged/create
@@ -0,0 +1,91 @@
+#!/bin/rc
+rfork e
+
+fn xchmod {
+ chmod $* >[2]/dev/null
+}
+
+if(~ $#* 0 1 2){
+ echo 'usage: patch/create name email file... [< description]' >[1=2]
+ exit usage
+}
+
+if(! echo $1 | grep -s '^[a-z_0-9.\-]+$'){
+ echo 'bad name: [a-z0-9._\-]+ only' >[1=2]
+ exit usage
+}
+if(! echo $2 | grep -s '^(-|[A-Za-z0-9.\-+]+@[A-Za-z0-9.\-+]+)$'){
+ echo 'bad email: [a-z0-9.-+] only; use ''-'' to not leave an email address.' >[1=2]
+ exit usage
+}
+
+if(! test -d /n/sources/patch){
+ rfork n
+ 9fs sources
+}
+
+patch=$1
+email=$2
+shift
+shift
+d=/n/sources/patch/$patch
+if(! mkdir $d){
+ echo mkdir $d failed >[1=2]
+ exit mkdir
+}
+if(! ~ $email -){
+ echo $email >$d/email
+}
+
+xchmod o-w $d
+>$d/readme
+>$d/files
+>$d/notes
+for(i in $*){
+ i=`{cleanname -d `{pwd} $i}
+ if(! test -f $i){
+ echo error: cannot find $i >[1=2]
+ rm -rf $d
+ exit oops
+ }
+ short=`{basename $i}
+ uniq=$short
+ n=0
+ while(test -f $d/$uniq){
+ uniq=$short.$n
+ n=`{echo 1+$n | hoc}
+ }
+ if(test -f /n/sources/plan9/$i){
+ if(cmp -s /n/sources/plan9/$i $i){
+ echo warning: skipping new file $i that does not differ from sources >[1=2]
+ }
+ if not{
+ cp /n/sources/plan9/$i $d/$uniq.orig
+ cp $i $d/$uniq
+ echo $i $uniq >>$d/files
+ }
+ }
+ if not{
+ echo warning: new file $i not on sources >[1=2]
+ cp $i $d/$uniq
+ echo $i $uniq >>$d/files
+ }
+}
+@{builtin cd $d && xchmod ug+rw * && xchmod a+r *}
+
+if(~ `{cat /proc/$pid/fd | awk 'NR==2{print $NF}'} */dev/cons && test -w /dev/consctl){
+ >/dev/consctl {
+ echo holdon
+ cat >$d/readme
+ }
+}
+if not
+ cat >$d/readme
+
+if(! test -s $d/readme){
+ echo 'no description given; aborting' >[1=2]
+ rm -rf $d
+ exit oops
+}
+
+echo $d
diff --git a/patch/patch-create-skip-unchanged/create.orig b/patch/patch-create-skip-unchanged/create.orig
new file mode 100755
index 0000000..55e659a
--- /dev/null
+++ b/patch/patch-create-skip-unchanged/create.orig
@@ -0,0 +1,85 @@
+#!/bin/rc
+rfork e
+
+fn xchmod {
+ chmod $* >[2]/dev/null
+}
+
+if(~ $#* 0 1 2){
+ echo 'usage: patch/create name email file... [< description]' >[1=2]
+ exit usage
+}
+
+if(! echo $1 | grep -s '^[a-z_0-9.\-]+$'){
+ echo 'bad name: [a-z0-9._\-]+ only' >[1=2]
+ exit usage
+}
+if(! echo $2 | grep -s '^(-|[A-Za-z0-9.\-+]+@[A-Za-z0-9.\-+]+)$'){
+ echo 'bad email: [a-z0-9.-+] only; use ''-'' to not leave an email address.' >[1=2]
+ exit usage
+}
+
+if(! test -d /n/sources/patch){
+ rfork n
+ 9fs sources
+}
+
+patch=$1
+email=$2
+shift
+shift
+d=/n/sources/patch/$patch
+if(! mkdir $d){
+ echo mkdir $d failed >[1=2]
+ exit mkdir
+}
+if(! ~ $email -){
+ echo $email >$d/email
+}
+
+xchmod o-w $d
+>$d/readme
+>$d/files
+>$d/notes
+for(i in $*){
+ i=`{cleanname -d `{pwd} $i}
+ if(! test -f $i){
+ echo error: cannot find $i >[1=2]
+ rm -rf $d
+ exit oops
+ }
+ short=`{basename $i}
+ uniq=$short
+ n=0
+ while(test -f $d/$uniq){
+ uniq=$short.$n
+ n=`{echo 1+$n | hoc}
+ }
+ cp $i $d/$uniq
+ if(test -f /n/sources/plan9/$i){
+ if(cmp -s /n/sources/plan9/$i $i)
+ echo warning: new file $i does not differ from sources >[1=2]
+ cp /n/sources/plan9/$i $d/$uniq.orig
+ }
+ if not
+ echo warning: new file $i not on sources >[1=2]
+ echo $i $uniq >>$d/files
+}
+@{builtin cd $d && xchmod ug+rw * && xchmod a+r *}
+
+if(~ `{cat /proc/$pid/fd | awk 'NR==2{print $NF}'} */dev/cons && test -w /dev/consctl){
+ >/dev/consctl {
+ echo holdon
+ cat >$d/readme
+ }
+}
+if not
+ cat >$d/readme
+
+if(! test -s $d/readme){
+ echo 'no description given; aborting' >[1=2]
+ rm -rf $d
+ exit oops
+}
+
+echo $d
diff --git a/patch/patch-create-skip-unchanged/email b/patch/patch-create-skip-unchanged/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/patch-create-skip-unchanged/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/patch-create-skip-unchanged/files b/patch/patch-create-skip-unchanged/files
new file mode 100644
index 0000000..5d6e99c
--- /dev/null
+++ b/patch/patch-create-skip-unchanged/files
@@ -0,0 +1 @@
+/rc/bin/patch/create create
diff --git a/patch/patch-create-skip-unchanged/notes b/patch/patch-create-skip-unchanged/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/patch-create-skip-unchanged/notes
diff --git a/patch/patch-create-skip-unchanged/readme b/patch/patch-create-skip-unchanged/readme
new file mode 100644
index 0000000..a3e5990
--- /dev/null
+++ b/patch/patch-create-skip-unchanged/readme
@@ -0,0 +1,3 @@
+Skip unchanged files when creating patch
+
+I don't see any point in including unchanged files.
diff --git a/patch/patch-orig/email b/patch/patch-orig/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/patch-orig/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/patch-orig/files b/patch/patch-orig/files
new file mode 100644
index 0000000..6446d4d
--- /dev/null
+++ b/patch/patch-orig/files
@@ -0,0 +1 @@
+/rc/bin/patch/orig orig
diff --git a/patch/patch-orig/notes b/patch/patch-orig/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/patch-orig/notes
diff --git a/patch/patch-orig/orig b/patch/patch-orig/orig
new file mode 100755
index 0000000..16db50d
--- /dev/null
+++ b/patch/patch-orig/orig
@@ -0,0 +1,16 @@
+#!/bin/rc -e
+
+p=`{cleanname -d `{pwd} $1}
+n=`{echo $p | sed 's,\.orig,,'}
+dir=`{basename -d $p}
+
+if(! test -d /n/sources/plan9){
+ rfork n
+ 9fs sources
+}
+
+if(test -e /n/sources/plan9$n)
+ echo /n/sources/plan9$n: replacing existing file >[1=2]
+mkdir -p /n/sources/plan9$dir
+echo cp $p /n/sources/plan9$n
+cp $p /n/sources/plan9$n
diff --git a/patch/patch-orig/readme b/patch/patch-orig/readme
new file mode 100644
index 0000000..01e70ea
--- /dev/null
+++ b/patch/patch-orig/readme
@@ -0,0 +1,9 @@
+patch/orig copies a file to /n/sources/plan9
+
+It may not always be desirable to have an entire
+source tree in /n/sources/plan9.
+For simple patch work, it is sufficient to copy
+the original file to /n/sources/plan9.
+This is what patch/orig does.
+
+(It also ignores .orig in the given path.)
diff --git a/patch/patch-update/email b/patch/patch-update/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/patch-update/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/patch-update/files b/patch/patch-update/files
new file mode 100644
index 0000000..d37e166
--- /dev/null
+++ b/patch/patch-update/files
@@ -0,0 +1 @@
+/rc/bin/patch/update update
diff --git a/patch/patch-update/notes b/patch/patch-update/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/patch-update/notes
diff --git a/patch/patch-update/readme b/patch/patch-update/readme
new file mode 100644
index 0000000..8681f72
--- /dev/null
+++ b/patch/patch-update/readme
@@ -0,0 +1,8 @@
+patch/update updates given file(s) in given patch
+
+I often make small changes, like fixing formatting,
+to files in patches that I've already put in /n/sources/patch.
+
+If /n/sources/patch is accessible to the outside world,
+then patch/update might not be a good idea, but I have
+/n/sources/patch bound to a patch directory in $home.
diff --git a/patch/patch-update/update b/patch/patch-update/update
new file mode 100755
index 0000000..6d7f2bf
--- /dev/null
+++ b/patch/patch-update/update
@@ -0,0 +1,35 @@
+#!/bin/rc -e
+
+# patch/update -- update file(s) in patch
+
+flagfmt=a
+args='name file...'
+if(! ifs=() eval `{aux/getflags $*} || test $#* -lt 2){
+ aux/usage
+ exit usage
+}
+
+if(! test -d /n/sources/patch){
+ rfork n
+ 9fs sources
+}
+
+d=/n/sources/patch/$1
+shift
+if(! test -d $d){
+ echo 'no such patch' $d >[1=2]
+ exit nopatch
+}
+
+for(file in $*){
+ src=`{cleanname -d `{pwd} $file}
+ dst=`{awk '$1 == ENVIRON["src"] { print $2 }' $d/files}
+ if (~ $dst ''){
+ echo $src not found in $d/files -- wrong patch? >[1=2]
+ exit 'nofile'
+ }
+ echo cp $src $d/$dst
+ cp $src $d/$dst
+ chmod ug+rw $d/$dst >[2]/dev/null
+ chmod a+r $d/$dst >[2]/dev/null
+}
diff --git a/patch/troff-hyphenate-latin-1-fixed/email b/patch/troff-hyphenate-latin-1-fixed/email
new file mode 100644
index 0000000..191feb6
--- /dev/null
+++ b/patch/troff-hyphenate-latin-1-fixed/email
@@ -0,0 +1 @@
+john@ankarstrom.se
diff --git a/patch/troff-hyphenate-latin-1-fixed/files b/patch/troff-hyphenate-latin-1-fixed/files
new file mode 100644
index 0000000..48b7a8b
--- /dev/null
+++ b/patch/troff-hyphenate-latin-1-fixed/files
@@ -0,0 +1 @@
+/sys/src/cmd/troff/n8.c n8.c
diff --git a/patch/troff-hyphenate-latin-1-fixed/n8.c b/patch/troff-hyphenate-latin-1-fixed/n8.c
new file mode 100644
index 0000000..b503fdf
--- /dev/null
+++ b/patch/troff-hyphenate-latin-1-fixed/n8.c
@@ -0,0 +1,584 @@
+#include "tdef.h"
+#include "fns.h"
+#include "ext.h"
+#include <assert.h>
+
+#define HY_BIT 0200 /* stuff in here only works for 7-bit ascii */
+ /* this value is used (as a literal) in suftab.c */
+ /* to encode possible hyphenation points in suffixes. */
+ /* it could be changed, by widening the tables */
+ /* to be shorts instead of chars. */
+
+/*
+ * troff8.c
+ *
+ * hyphenation
+ */
+
+int hexsize = 0; /* hyphenation exception list size */
+char *hbufp = NULL; /* base of list */
+char *nexth = NULL; /* first free slot in list */
+Tchar *hyend;
+
+#define LATIN 256
+#define latcbits(i) ((i)+1 & 0x000FF) /* for some reason, extra chars */
+ /* from latin-1 are off by one */
+
+#define THRESH 160 /* digram goodness threshold */
+int thresh = THRESH;
+
+int texhyphen(void);
+static int alpha(Tchar);
+
+void hyphen(Tchar *wp)
+{
+ int j;
+ Tchar *i;
+
+ i = wp;
+ while (punct((*i++)))
+ ;
+ if (!alpha(*--i))
+ return;
+ wdstart = i++;
+ while (alpha(*i++))
+ ;
+ hyend = wdend = --i - 1;
+ while (punct((*i++)))
+ ;
+ if (*--i)
+ return;
+ if (wdend - wdstart < 4) /* 4 chars is too short to hyphenate */
+ return;
+ hyp = hyptr;
+ *hyp = 0;
+ hyoff = 2;
+
+ /* for now, try exceptions first, then tex (if hyphalg is non-zero),
+ then suffix and digram if tex didn't hyphenate it at all.
+ */
+
+ //if (!exword() && !texhyphen() && !suffix())
+ if (!exword() && !texhyphen())
+ digram();
+
+ /* this appears to sort hyphenation points into increasing order */
+ *hyp++ = 0;
+ if (*hyptr)
+ for (j = 1; j; ) {
+ j = 0;
+ for (hyp = hyptr + 1; *hyp != 0; hyp++) {
+ if (*(hyp - 1) > *hyp) {
+ j++;
+ i = *hyp;
+ *hyp = *(hyp - 1);
+ *(hyp - 1) = i;
+ }
+ }
+ }
+}
+
+static alpha(Tchar i) /* non-zero if really alphabetic */
+{
+ if (ismot(i))
+ return 0;
+ else if (latcbits(i) > LATIN) /* this isn't very elegant, but there's */
+ return 0; /* no good way to make sure i is in range for */
+ else if (latcbits(i) >= LATIN-64) { /* the call of isalpha */
+ return (latcbits(i) != 0xD7 && latcbits(i) != 0xF7);
+ } else
+ return isalpha(cbits(i));
+}
+
+
+punct(Tchar i)
+{
+ if (!i || alpha(i))
+ return(0);
+ else
+ return(1);
+}
+
+
+void caseha(void) /* set hyphenation algorithm */
+{
+ hyphalg = HYPHALG;
+ if (skip())
+ return;
+ noscale++;
+ hyphalg = atoi0();
+ noscale = 0;
+}
+
+
+void caseht(void) /* set hyphenation threshold; not in manual! */
+{
+ thresh = THRESH;
+ if (skip())
+ return;
+ noscale++;
+ thresh = atoi0();
+ noscale = 0;
+}
+
+
+char *growh(char *where)
+{
+ char *new;
+
+ hexsize += NHEX;
+ if ((new = grow(hbufp, hexsize, sizeof(char))) == NULL)
+ return NULL;
+ if (new == hbufp) {
+ return where;
+ } else {
+ int diff;
+ diff = where - hbufp;
+ hbufp = new;
+ return new + diff;
+ }
+}
+
+
+void casehw(void)
+{
+ int i, k;
+ char *j;
+ Tchar t;
+
+ if (nexth == NULL) {
+ if ((nexth = hbufp = grow(hbufp, NHEX, sizeof(char))) == NULL) {
+ ERROR "No space for exception word list." WARN;
+ return;
+ }
+ hexsize = NHEX;
+ }
+ k = 0;
+ while (!skip()) {
+ if ((j = nexth) >= hbufp + hexsize - 2)
+ if ((j = nexth = growh(j)) == NULL)
+ goto full;
+ for (;;) {
+ if (ismot(t = getch()))
+ continue;
+ i = cbits(t);
+ if (i == ' ' || i == '\n') {
+ *j++ = 0;
+ nexth = j;
+ *j = 0;
+ if (i == ' ')
+ break;
+ else
+ return;
+ }
+ if (i == '-') {
+ k = HY_BIT;
+ continue;
+ }
+ *j++ = maplow(i) | k;
+ k = 0;
+ if (j >= hbufp + hexsize - 2)
+ if ((j = growh(j)) == NULL)
+ goto full;
+ }
+ }
+ return;
+full:
+ ERROR "Cannot grow exception word list." WARN;
+ *nexth = 0;
+}
+
+
+int exword(void)
+{
+ Tchar *w;
+ char *e, *save;
+
+ e = hbufp;
+ while (1) {
+ save = e;
+ if (e == NULL || *e == 0)
+ return(0);
+ w = wdstart;
+ while (*e && w <= hyend && (*e & 0177) == maplow(cbits(*w))) {
+ e++;
+ w++;
+ }
+ if (!*e) {
+ if (w-1 == hyend || (w == wdend && maplow(cbits(*w)) == 's')) {
+ w = wdstart;
+ for (e = save; *e; e++) {
+ if (*e & HY_BIT)
+ *hyp++ = w;
+ if (hyp > hyptr + NHYP - 1)
+ hyp = hyptr + NHYP - 1;
+ w++;
+ }
+ return(1);
+ } else {
+ e++;
+ continue;
+ }
+ } else
+ while (*e++)
+ ;
+ }
+}
+
+
+suffix(void)
+{
+ Tchar *w;
+ char *s, *s0;
+ Tchar i;
+ extern char *suftab[];
+
+again:
+ i = cbits(*hyend);
+ if (!alpha(i))
+ return(0);
+ if (i < 'a')
+ i -= 'A' - 'a';
+ if ((s0 = suftab[i-'a']) == 0)
+ return(0);
+ for (;;) {
+ if ((i = *s0 & 017) == 0)
+ return(0);
+ s = s0 + i - 1;
+ w = hyend - 1;
+ while (s > s0 && w >= wdstart && (*s & 0177) == maplow(cbits(*w))) {
+ s--;
+ w--;
+ }
+ if (s == s0)
+ break;
+ s0 += i;
+ }
+ s = s0 + i - 1;
+ w = hyend;
+ if (*s0 & HY_BIT)
+ goto mark;
+ while (s > s0) {
+ w--;
+ if (*s-- & HY_BIT) {
+mark:
+ hyend = w - 1;
+ if (*s0 & 0100) /* 0100 used in suftab to encode something too */
+ continue;
+ if (!chkvow(w))
+ return(0);
+ *hyp++ = w;
+ }
+ }
+ if (*s0 & 040)
+ return(0);
+ if (exword())
+ return(1);
+ goto again;
+}
+
+
+maplow(int i)
+{
+ if (isupper(i))
+ i = tolower(i);
+ else if ((latcbits(i) >= 0xC0 && latcbits(i) <= 0xD6) || (latcbits(i) >= 0xD8 && latcbits(i) <= 0xDD))
+ i = tolower(latcbits(i));
+ return(i);
+}
+
+
+vowel(int i)
+{
+ int j = latcbits(i);
+ if (j >= 0xC0 && j <= 0xC6 || /* uppercase */
+ j >= 0xC8 && j <= 0xCF ||
+ j >= 0xD2 && j <= 0xD6 ||
+ j >= 0xD8 && j <= 0xDD ||
+ j >= 0xE0 && j <= 0xE6 || /* lowercase */
+ j >= 0xE8 && j <= 0xEF ||
+ j >= 0xF2 && j <= 0xF6 ||
+ j >= 0xF8 && j <= 0xFD ||
+ j == 0xFF)
+ return 1;
+
+ switch (i) {
+ case 'a': case 'A':
+ case 'e': case 'E':
+ case 'i': case 'I':
+ case 'o': case 'O':
+ case 'u': case 'U':
+ case 'y': case 'Y':
+ return(1);
+ default:
+ return(0);
+ }
+}
+
+
+Tchar *chkvow(Tchar *w)
+{
+ while (--w >= wdstart)
+ if (vowel(cbits(*w)))
+ return(w);
+ return(0);
+}
+
+
+void digram(void)
+{
+ Tchar *w;
+ int val;
+ Tchar *nhyend, *maxw;
+ int maxval;
+ extern char bxh[26][13], bxxh[26][13], xxh[26][13], xhx[26][13], hxx[26][13];
+
+again:
+ if (!(w = chkvow(hyend + 1)))
+ return;
+ hyend = w;
+ if (!(w = chkvow(hyend)))
+ return;
+ nhyend = w;
+ maxval = 0;
+ w--;
+ while (++w < hyend && w < wdend - 1) {
+ val = 1;
+ if (w == wdstart)
+ val *= dilook('a', cbits(*w), bxh);
+ else if (w == wdstart + 1)
+ val *= dilook(cbits(*(w-1)), cbits(*w), bxxh);
+ else
+ val *= dilook(cbits(*(w-1)), cbits(*w), xxh);
+ val *= dilook(cbits(*w), cbits(*(w+1)), xhx);
+ val *= dilook(cbits(*(w+1)), cbits(*(w+2)), hxx);
+ if (val > maxval) {
+ maxval = val;
+ maxw = w + 1;
+ }
+ }
+ hyend = nhyend;
+ if (maxval > thresh)
+ *hyp++ = maxw;
+ goto again;
+}
+
+
+dilook(int a, int b, char t[26][13])
+{
+ int i, j;
+
+ i = t[maplow(a)-'a'][(j = maplow(b)-'a')/2];
+ if (!(j & 01))
+ i >>= 4;
+ return(i & 017);
+}
+
+
+/* here beginneth the tex hyphenation code, as interpreted freely */
+/* the main difference is that there is no attempt to squeeze space */
+/* as tightly at tex does. */
+
+static int texit(Tchar *, Tchar *);
+static int readpats(void);
+static void install(char *);
+static void fixup(void);
+static int trieindex(int, int);
+static int trieindexpart(int);
+
+#define PARTMAX (27+31) /* latin-1-specific sizes */
+#define TRIEMAX (PARTMAX*PARTMAX+PARTMAX)
+
+static char pats[50000]; /* size ought to be computed dynamically */
+static char *nextpat = pats;
+static char *trie[TRIEMAX];
+
+int texhyphen(void)
+{
+ static int loaded = 0; /* -1: couldn't find tex file */
+
+ if (hyphalg == 0 || loaded == -1) /* non-zero => tex for now */
+ return 0;
+ if (loaded == 0) {
+ if (readpats())
+ loaded = 1;
+ else
+ loaded = -1;
+ }
+ return texit(wdstart, wdend);
+}
+
+static int texit(Tchar *start, Tchar *end) /* hyphenate as in tex, return # found */
+{
+ int nw, i, k, equal, cnt[500];
+ char w[500+1], *np, *pp, *wp, *xpp, *xwp;
+
+ w[0] = '.';
+ for (nw = 1; start <= end && nw < 500-1; nw++, start++)
+ w[nw] = maplow(cbits(*start));
+ start -= (nw - 1);
+ w[nw++] = '.';
+ w[nw] = 0;
+/*
+ * printf("try %s\n", w);
+*/
+ for (i = 0; i <= nw; i++)
+ cnt[i] = '0';
+
+ for (wp = w; wp+1 < w+nw; wp++) {
+ for (pp = trie[trieindex(*wp, *(wp+1))]; pp < nextpat; ) {
+ if (pp == 0 /* no trie entry */
+ || *pp != *wp /* no match on 1st letter */
+ || *(pp+1) != *(wp+1)) /* no match on 2nd letter */
+ break; /* so move to next letter of word */
+ equal = 1;
+ for (xpp = pp+2, xwp = wp+2; *xpp; )
+ if (*xpp++ != *xwp++) {
+ equal = 0;
+ break;
+ }
+ if (equal) {
+ np = xpp+1; /* numpat */
+ for (k = wp-w; *np; k++, np++)
+ if (*np > cnt[k])
+ cnt[k] = *np;
+/*
+ * printf("match: %s %s\n", pp, xpp+1);
+*/
+ }
+ pp += *(pp-1); /* skip over pattern and numbers to next */
+ }
+ }
+/*
+ * for (i = 0; i < nw; i++) printf("%c", w[i]);
+ * printf(" ");
+ * for (i = 0; i <= nw; i++) printf("%c", cnt[i]);
+ * printf("\n");
+*/
+/*
+ * for (i = 1; i < nw - 1; i++) {
+ * if (i > 2 && i < nw - 3 && cnt[i] % 2)
+ * printf("-");
+ * if (cbits(start[i-1]) != '.')
+ * printf("%c", cbits(start[i-1]));
+ * }
+ * printf("\n");
+*/
+ for (i = 1; i < nw -1; i++)
+ if (i > 2 && i < nw - 3 && cnt[i] % 2)
+ *hyp++ = start + i - 1;
+ return hyp - hyptr; /* non-zero if a hyphen was found */
+}
+
+/*
+ This code assumes that hyphen.tex looks like
+ % some comments
+ \patterns{ % more comments
+ pat5ter4ns, 1 per line, SORTED, nothing else
+ }
+ more goo
+ \hyphenation{ % more comments
+ ex-cep-tions, one per line; i ignore this part for now
+ }
+
+ this code is NOT robust against variations. unfortunately,
+ it looks like every local language version of this file has
+ a different format. i have also made no provision for weird
+ characters. sigh.
+*/
+
+static int readpats(void)
+{
+ FILE *fp;
+ char buf[200], buf1[200];
+
+ if ((fp = fopen(TEXHYPHENS, "r")) == NULL
+ && (fp = fopen(DWBalthyphens, "r")) == NULL) {
+ ERROR "warning: can't find hyphen.tex" WARN;
+ return 0;
+ }
+
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ sscanf(buf, "%s", buf1);
+ if (strcmp(buf1, "\\patterns{") == 0)
+ break;
+ }
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ if (buf[0] == '}')
+ break;
+ install(buf);
+ }
+ fclose(fp);
+ fixup();
+ return 1;
+}
+
+static void install(char *s) /* map ab4c5de to: 12 abcde \0 00405 \0 */
+{
+ int npat, lastpat;
+ char num[500], *onextpat = nextpat;
+
+ num[0] = '0';
+ *nextpat++ = ' '; /* fill in with count later */
+ for (npat = lastpat = 0; *s != '\n' && *s != '\0'; s++) {
+ if (isdigit(*s)) {
+ num[npat] = *s;
+ lastpat = npat;
+ } else {
+ *nextpat++ = *s;
+ npat++;
+ num[npat] = '0';
+ }
+ }
+ *nextpat++ = 0;
+ if (nextpat > pats + sizeof(pats)-20) {
+ ERROR "tex hyphenation table overflow, tail end ignored" WARN;
+ nextpat = onextpat;
+ }
+ num[lastpat+1] = 0;
+ strcat(nextpat, num);
+ nextpat += strlen(nextpat) + 1;
+}
+
+static void fixup(void) /* build indexes of where . a b c ... start */
+{
+ char *p, *lastc;
+ int n;
+
+ for (lastc = pats, p = pats+1; p < nextpat; p++)
+ if (*p == ' ') {
+ *lastc = p - lastc;
+ lastc = p;
+ }
+ *lastc = p - lastc;
+ for (p = pats+1; p < nextpat; ) {
+ n = trieindex(p[0], p[1]);
+ if (trie[n] == 0)
+ trie[n] = p;
+ p += p[-1];
+ }
+ /* printf("pats = %d\n", nextpat - pats); */
+}
+
+static int trieindex(int d1, int d2)
+{
+ int i;
+
+ i = PARTMAX*trieindexpart(d1) + trieindexpart(d2);
+ if (!(0 <= i && i < TRIEMAX)) {
+ fprintf(stderr, "i = %d\n", i);
+ fprintf(stderr, "d1 = %x = %d\n", d1, d1);
+ fprintf(stderr, "d2 = %x = %d\n", d2, d2);
+ fprintf(stderr, "part(d1) = %x = %d\n", trieindexpart(d1), trieindexpart(d1));
+ fprintf(stderr, "part(d2) = %x = %d\n", trieindexpart(d2), trieindexpart(d2));
+ assert(0);
+ }
+ return i;
+}
+
+static int trieindexpart(int d)
+{
+ if (cbits(d) == '.') return 0;
+ if (cbits(d) <= 'z') return d - 'a' + 1;
+ else return latcbits(d) - 0xE0 + 27; /* L'à' comes after 'z' */
+}
diff --git a/patch/troff-hyphenate-latin-1-fixed/n8.c.orig b/patch/troff-hyphenate-latin-1-fixed/n8.c.orig
new file mode 100644
index 0000000..8c2112c
--- /dev/null
+++ b/patch/troff-hyphenate-latin-1-fixed/n8.c.orig
@@ -0,0 +1,545 @@
+#include "tdef.h"
+#include "fns.h"
+#include "ext.h"
+#include <assert.h>
+
+#define HY_BIT 0200 /* stuff in here only works for 7-bit ascii */
+ /* this value is used (as a literal) in suftab.c */
+ /* to encode possible hyphenation points in suffixes. */
+ /* it could be changed, by widening the tables */
+ /* to be shorts instead of chars. */
+
+/*
+ * troff8.c
+ *
+ * hyphenation
+ */
+
+int hexsize = 0; /* hyphenation exception list size */
+char *hbufp = NULL; /* base of list */
+char *nexth = NULL; /* first free slot in list */
+Tchar *hyend;
+
+#define THRESH 160 /* digram goodness threshold */
+int thresh = THRESH;
+
+int texhyphen(void);
+static int alpha(Tchar);
+
+void hyphen(Tchar *wp)
+{
+ int j;
+ Tchar *i;
+
+ i = wp;
+ while (punct((*i++)))
+ ;
+ if (!alpha(*--i))
+ return;
+ wdstart = i++;
+ while (alpha(*i++))
+ ;
+ hyend = wdend = --i - 1;
+ while (punct((*i++)))
+ ;
+ if (*--i)
+ return;
+ if (wdend - wdstart < 4) /* 4 chars is too short to hyphenate */
+ return;
+ hyp = hyptr;
+ *hyp = 0;
+ hyoff = 2;
+
+ /* for now, try exceptions first, then tex (if hyphalg is non-zero),
+ then suffix and digram if tex didn't hyphenate it at all.
+ */
+
+ if (!exword() && !texhyphen() && !suffix())
+ digram();
+
+ /* this appears to sort hyphenation points into increasing order */
+ *hyp++ = 0;
+ if (*hyptr)
+ for (j = 1; j; ) {
+ j = 0;
+ for (hyp = hyptr + 1; *hyp != 0; hyp++) {
+ if (*(hyp - 1) > *hyp) {
+ j++;
+ i = *hyp;
+ *hyp = *(hyp - 1);
+ *(hyp - 1) = i;
+ }
+ }
+ }
+}
+
+static alpha(Tchar i) /* non-zero if really alphabetic */
+{
+ if (ismot(i))
+ return 0;
+ else if (cbits(i) >= ALPHABET) /* this isn't very elegant, but there's */
+ return 0; /* no good way to make sure i is in range for */
+ else /* the call of isalpha */
+ return isalpha(cbits(i));
+}
+
+
+punct(Tchar i)
+{
+ if (!i || alpha(i))
+ return(0);
+ else
+ return(1);
+}
+
+
+void caseha(void) /* set hyphenation algorithm */
+{
+ hyphalg = HYPHALG;
+ if (skip())
+ return;
+ noscale++;
+ hyphalg = atoi0();
+ noscale = 0;
+}
+
+
+void caseht(void) /* set hyphenation threshold; not in manual! */
+{
+ thresh = THRESH;
+ if (skip())
+ return;
+ noscale++;
+ thresh = atoi0();
+ noscale = 0;
+}
+
+
+char *growh(char *where)
+{
+ char *new;
+
+ hexsize += NHEX;
+ if ((new = grow(hbufp, hexsize, sizeof(char))) == NULL)
+ return NULL;
+ if (new == hbufp) {
+ return where;
+ } else {
+ int diff;
+ diff = where - hbufp;
+ hbufp = new;
+ return new + diff;
+ }
+}
+
+
+void casehw(void)
+{
+ int i, k;
+ char *j;
+ Tchar t;
+
+ if (nexth == NULL) {
+ if ((nexth = hbufp = grow(hbufp, NHEX, sizeof(char))) == NULL) {
+ ERROR "No space for exception word list." WARN;
+ return;
+ }
+ hexsize = NHEX;
+ }
+ k = 0;
+ while (!skip()) {
+ if ((j = nexth) >= hbufp + hexsize - 2)
+ if ((j = nexth = growh(j)) == NULL)
+ goto full;
+ for (;;) {
+ if (ismot(t = getch()))
+ continue;
+ i = cbits(t);
+ if (i == ' ' || i == '\n') {
+ *j++ = 0;
+ nexth = j;
+ *j = 0;
+ if (i == ' ')
+ break;
+ else
+ return;
+ }
+ if (i == '-') {
+ k = HY_BIT;
+ continue;
+ }
+ *j++ = maplow(i) | k;
+ k = 0;
+ if (j >= hbufp + hexsize - 2)
+ if ((j = growh(j)) == NULL)
+ goto full;
+ }
+ }
+ return;
+full:
+ ERROR "Cannot grow exception word list." WARN;
+ *nexth = 0;
+}
+
+
+int exword(void)
+{
+ Tchar *w;
+ char *e, *save;
+
+ e = hbufp;
+ while (1) {
+ save = e;
+ if (e == NULL || *e == 0)
+ return(0);
+ w = wdstart;
+ while (*e && w <= hyend && (*e & 0177) == maplow(cbits(*w))) {
+ e++;
+ w++;
+ }
+ if (!*e) {
+ if (w-1 == hyend || (w == wdend && maplow(cbits(*w)) == 's')) {
+ w = wdstart;
+ for (e = save; *e; e++) {
+ if (*e & HY_BIT)
+ *hyp++ = w;
+ if (hyp > hyptr + NHYP - 1)
+ hyp = hyptr + NHYP - 1;
+ w++;
+ }
+ return(1);
+ } else {
+ e++;
+ continue;
+ }
+ } else
+ while (*e++)
+ ;
+ }
+}
+
+
+suffix(void)
+{
+ Tchar *w;
+ char *s, *s0;
+ Tchar i;
+ extern char *suftab[];
+
+again:
+ i = cbits(*hyend);
+ if (!alpha(i))
+ return(0);
+ if (i < 'a')
+ i -= 'A' - 'a';
+ if ((s0 = suftab[i-'a']) == 0)
+ return(0);
+ for (;;) {
+ if ((i = *s0 & 017) == 0)
+ return(0);
+ s = s0 + i - 1;
+ w = hyend - 1;
+ while (s > s0 && w >= wdstart && (*s & 0177) == maplow(cbits(*w))) {
+ s--;
+ w--;
+ }
+ if (s == s0)
+ break;
+ s0 += i;
+ }
+ s = s0 + i - 1;
+ w = hyend;
+ if (*s0 & HY_BIT)
+ goto mark;
+ while (s > s0) {
+ w--;
+ if (*s-- & HY_BIT) {
+mark:
+ hyend = w - 1;
+ if (*s0 & 0100) /* 0100 used in suftab to encode something too */
+ continue;
+ if (!chkvow(w))
+ return(0);
+ *hyp++ = w;
+ }
+ }
+ if (*s0 & 040)
+ return(0);
+ if (exword())
+ return(1);
+ goto again;
+}
+
+
+maplow(int i)
+{
+ if (isupper(i))
+ i = tolower(i);
+ return(i);
+}
+
+
+vowel(int i)
+{
+ switch (i) {
+ case 'a': case 'A':
+ case 'e': case 'E':
+ case 'i': case 'I':
+ case 'o': case 'O':
+ case 'u': case 'U':
+ case 'y': case 'Y':
+ return(1);
+ default:
+ return(0);
+ }
+}
+
+
+Tchar *chkvow(Tchar *w)
+{
+ while (--w >= wdstart)
+ if (vowel(cbits(*w)))
+ return(w);
+ return(0);
+}
+
+
+void digram(void)
+{
+ Tchar *w;
+ int val;
+ Tchar *nhyend, *maxw;
+ int maxval;
+ extern char bxh[26][13], bxxh[26][13], xxh[26][13], xhx[26][13], hxx[26][13];
+
+again:
+ if (!(w = chkvow(hyend + 1)))
+ return;
+ hyend = w;
+ if (!(w = chkvow(hyend)))
+ return;
+ nhyend = w;
+ maxval = 0;
+ w--;
+ while (++w < hyend && w < wdend - 1) {
+ val = 1;
+ if (w == wdstart)
+ val *= dilook('a', cbits(*w), bxh);
+ else if (w == wdstart + 1)
+ val *= dilook(cbits(*(w-1)), cbits(*w), bxxh);
+ else
+ val *= dilook(cbits(*(w-1)), cbits(*w), xxh);
+ val *= dilook(cbits(*w), cbits(*(w+1)), xhx);
+ val *= dilook(cbits(*(w+1)), cbits(*(w+2)), hxx);
+ if (val > maxval) {
+ maxval = val;
+ maxw = w + 1;
+ }
+ }
+ hyend = nhyend;
+ if (maxval > thresh)
+ *hyp++ = maxw;
+ goto again;
+}
+
+
+dilook(int a, int b, char t[26][13])
+{
+ int i, j;
+
+ i = t[maplow(a)-'a'][(j = maplow(b)-'a')/2];
+ if (!(j & 01))
+ i >>= 4;
+ return(i & 017);
+}
+
+
+/* here beginneth the tex hyphenation code, as interpreted freely */
+/* the main difference is that there is no attempt to squeeze space */
+/* as tightly at tex does. */
+
+static int texit(Tchar *, Tchar *);
+static int readpats(void);
+static void install(char *);
+static void fixup(void);
+static int trieindex(int, int);
+
+static char pats[50000]; /* size ought to be computed dynamically */
+static char *nextpat = pats;
+static char *trie[27*27]; /* english-specific sizes */
+
+int texhyphen(void)
+{
+ static int loaded = 0; /* -1: couldn't find tex file */
+
+ if (hyphalg == 0 || loaded == -1) /* non-zero => tex for now */
+ return 0;
+ if (loaded == 0) {
+ if (readpats())
+ loaded = 1;
+ else
+ loaded = -1;
+ }
+ return texit(wdstart, wdend);
+}
+
+static int texit(Tchar *start, Tchar *end) /* hyphenate as in tex, return # found */
+{
+ int nw, i, k, equal, cnt[500];
+ char w[500+1], *np, *pp, *wp, *xpp, *xwp;
+
+ w[0] = '.';
+ for (nw = 1; start <= end && nw < 500-1; nw++, start++)
+ w[nw] = maplow(tolower(cbits(*start)));
+ start -= (nw - 1);
+ w[nw++] = '.';
+ w[nw] = 0;
+/*
+ * printf("try %s\n", w);
+*/
+ for (i = 0; i <= nw; i++)
+ cnt[i] = '0';
+
+ for (wp = w; wp+1 < w+nw; wp++) {
+ for (pp = trie[trieindex(*wp, *(wp+1))]; pp < nextpat; ) {
+ if (pp == 0 /* no trie entry */
+ || *pp != *wp /* no match on 1st letter */
+ || *(pp+1) != *(wp+1)) /* no match on 2nd letter */
+ break; /* so move to next letter of word */
+ equal = 1;
+ for (xpp = pp+2, xwp = wp+2; *xpp; )
+ if (*xpp++ != *xwp++) {
+ equal = 0;
+ break;
+ }
+ if (equal) {
+ np = xpp+1; /* numpat */
+ for (k = wp-w; *np; k++, np++)
+ if (*np > cnt[k])
+ cnt[k] = *np;
+/*
+ * printf("match: %s %s\n", pp, xpp+1);
+*/
+ }
+ pp += *(pp-1); /* skip over pattern and numbers to next */
+ }
+ }
+/*
+ * for (i = 0; i < nw; i++) printf("%c", w[i]);
+ * printf(" ");
+ * for (i = 0; i <= nw; i++) printf("%c", cnt[i]);
+ * printf("\n");
+*/
+/*
+ * for (i = 1; i < nw - 1; i++) {
+ * if (i > 2 && i < nw - 3 && cnt[i] % 2)
+ * printf("-");
+ * if (cbits(start[i-1]) != '.')
+ * printf("%c", cbits(start[i-1]));
+ * }
+ * printf("\n");
+*/
+ for (i = 1; i < nw -1; i++)
+ if (i > 2 && i < nw - 3 && cnt[i] % 2)
+ *hyp++ = start + i - 1;
+ return hyp - hyptr; /* non-zero if a hyphen was found */
+}
+
+/*
+ This code assumes that hyphen.tex looks like
+ % some comments
+ \patterns{ % more comments
+ pat5ter4ns, 1 per line, SORTED, nothing else
+ }
+ more goo
+ \hyphenation{ % more comments
+ ex-cep-tions, one per line; i ignore this part for now
+ }
+
+ this code is NOT robust against variations. unfortunately,
+ it looks like every local language version of this file has
+ a different format. i have also made no provision for weird
+ characters. sigh.
+*/
+
+static int readpats(void)
+{
+ FILE *fp;
+ char buf[200], buf1[200];
+
+ if ((fp = fopen(TEXHYPHENS, "r")) == NULL
+ && (fp = fopen(DWBalthyphens, "r")) == NULL) {
+ ERROR "warning: can't find hyphen.tex" WARN;
+ return 0;
+ }
+
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ sscanf(buf, "%s", buf1);
+ if (strcmp(buf1, "\\patterns{") == 0)
+ break;
+ }
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ if (buf[0] == '}')
+ break;
+ install(buf);
+ }
+ fclose(fp);
+ fixup();
+ return 1;
+}
+
+static void install(char *s) /* map ab4c5de to: 12 abcde \0 00405 \0 */
+{
+ int npat, lastpat;
+ char num[500], *onextpat = nextpat;
+
+ num[0] = '0';
+ *nextpat++ = ' '; /* fill in with count later */
+ for (npat = lastpat = 0; *s != '\n' && *s != '\0'; s++) {
+ if (isdigit(*s)) {
+ num[npat] = *s;
+ lastpat = npat;
+ } else {
+ *nextpat++ = *s;
+ npat++;
+ num[npat] = '0';
+ }
+ }
+ *nextpat++ = 0;
+ if (nextpat > pats + sizeof(pats)-20) {
+ ERROR "tex hyphenation table overflow, tail end ignored" WARN;
+ nextpat = onextpat;
+ }
+ num[lastpat+1] = 0;
+ strcat(nextpat, num);
+ nextpat += strlen(nextpat) + 1;
+}
+
+static void fixup(void) /* build indexes of where . a b c ... start */
+{
+ char *p, *lastc;
+ int n;
+
+ for (lastc = pats, p = pats+1; p < nextpat; p++)
+ if (*p == ' ') {
+ *lastc = p - lastc;
+ lastc = p;
+ }
+ *lastc = p - lastc;
+ for (p = pats+1; p < nextpat; ) {
+ n = trieindex(p[0], p[1]);
+ if (trie[n] == 0)
+ trie[n] = p;
+ p += p[-1];
+ }
+ /* printf("pats = %d\n", nextpat - pats); */
+}
+
+static int trieindex(int d1, int d2)
+{
+ int i;
+
+ i = 27*(d1 == '.'? 0: d1 - 'a' + 1) + (d2 == '.'? 0: d2 - 'a' + 1);
+ assert(0 <= i && i < 27*27);
+ return i;
+}
diff --git a/patch/troff-hyphenate-latin-1-fixed/notes b/patch/troff-hyphenate-latin-1-fixed/notes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/patch/troff-hyphenate-latin-1-fixed/notes
diff --git a/patch/troff-hyphenate-latin-1-fixed/readme b/patch/troff-hyphenate-latin-1-fixed/readme
new file mode 100644
index 0000000..0d3bd32
--- /dev/null
+++ b/patch/troff-hyphenate-latin-1-fixed/readme
@@ -0,0 +1,9 @@
+Add support for Latin-1 characters in hyphenation algorithm
+
+Ideally, it would support UTF-8, but Latin-1 is a big improvement
+over ASCII nonetheless.
+
+This patch disables the suffix function, which I haven't (yet) gotten
+to work with Latin-1. Suffix is, however, hard-coded for English,
+and, in any case, I'm not sure how important it is. In the meantime,
+it isn't a huge loss.