diff options
author | John Ankarström <john@ankarstrom.se> | 2021-01-28 21:33:30 +0000 |
---|---|---|
committer | John Ankarström <john@ankarstrom.se> | 2021-01-28 21:33:30 +0000 |
commit | 51b5b02f52cc6f314029f3f2fe97afdc26ba0f25 (patch) | |
tree | 711d1efadeb78ad9b2d9f7b629350358838bc626 /patch | |
parent | f3fd330cddade1c66d0f101d5cc6f657c4cd1bb6 (diff) | |
download | plan9-51b5b02f52cc6f314029f3f2fe97afdc26ba0f25.tar.gz |
Add various patches
Diffstat (limited to 'patch')
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 = ± + 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 = ± + 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 = ± + 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 = ± + 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 = ± + alts[MPlumb].op = CHANRCV; + alts[MWarnings].c = cwarn; + alts[MWarnings].v = nil; + alts[MWarnings].op = CHANRCV; + if(cplumb == nil) + alts[MPlumb].op = CHANNOP; + alts[NMALT].op = CHANEND; + + for(;;){ + qlock(&row); + flushwarnings(); + qunlock(&row); + flushimage(display, 1); + switch(alt(alts)){ + case MResize: + if(getwindow(display, Refnone) < 0) + error("attach to window"); + scrlresize(); + rowresize(&row, screen->clipr); + break; + case MPlumb: + if(strcmp(pm->type, "text") == 0){ + act = plumblookup(pm->attr, "action"); + if(act==nil || strcmp(act, "showfile")==0) + plumblook(pm); + else if(strcmp(act, "showdata")==0) + plumbshow(pm); + } + plumbfree(pm); + break; + case MWarnings: + break; + case MMouse: + /* + * Make a copy so decisions are consistent; mousectl changes + * underfoot. Can't just receive into m because this introduces + * another race; see /sys/src/libdraw/mouse.c. + */ + m = mousectl->Mouse; + qlock(&row); + t = rowwhich(&row, m.xy); + + if((t!=mousetext && t!=nil && t->w!=nil) && + (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) { + xfidlog(t->w, "focus"); + } + + if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){ + winlock(mousetext->w, 'M'); + mousetext->eq0 = ~0; + wincommit(mousetext->w, mousetext); + winunlock(mousetext->w); + } + mousetext = t; + if(t == nil) + goto Continue; + w = t->w; + if(t==nil || m.buttons==0) + goto Continue; + but = 0; + if(m.buttons == 1) + but = 1; + else if(m.buttons == 2) + but = 2; + else if(m.buttons == 4) + but = 3; + barttext = t; + if(t->what==Body && ptinrect(m.xy, t->scrollr)){ + if(but){ + winlock(w, 'M'); + t->eq0 = ~0; + textscroll(t, but); + winunlock(w); + } + goto Continue; + } + /* scroll buttons, wheels, etc. */ + if(t->what==Body && w != nil && (m.buttons & (8|16))){ + if(m.buttons & 8) + but = Kscrolloneup; + else + but = Kscrollonedown; + winlock(w, 'M'); + t->eq0 = ~0; + texttype(t, but); + winunlock(w); + goto Continue; + } + if(ptinrect(m.xy, t->scrollr)){ + if(but){ + if(t->what == Columntag) + rowdragcol(&row, t->col, but); + else if(t->what == Tag){ + coldragwin(t->col, t->w, but); + if(t->w) + barttext = &t->w->body; + } + if(t->col) + activecol = t->col; + } + goto Continue; + } + if(m.buttons){ + if(w) + winlock(w, 'M'); + t->eq0 = ~0; + if(w) + wincommit(w, t); + else + textcommit(t, TRUE); + if(m.buttons & 1){ + textselect(t); + if(w) + winsettag(w); + argtext = t; + seltext = t; + if(t->col) + activecol = t->col; /* button 1 only */ + if(t->w!=nil && t==&t->w->body) + activewin = t->w; + }else if(m.buttons & 2){ + if(textselect2(t, &q0, &q1, &argt)) + execute(t, q0, q1, FALSE, argt); + }else if(m.buttons & 4){ + if(textselect3(t, &q0, &q1)) + look3(t, q0, q1, FALSE); + } + if(w) + winunlock(w); + goto Continue; + } + Continue: + qunlock(&row); + break; + } + } +} + +/* + * There is a race between process exiting and our finding out it was ever created. + * This structure keeps a list of processes that have exited we haven't heard of. + */ +typedef struct Pid Pid; +struct Pid +{ + int pid; + char msg[ERRMAX]; + Pid *next; +}; + +void +waitthread(void *) +{ + Waitmsg *w; + Command *c, *lc; + uint pid; + int found, ncmd; + Rune *cmd; + char *err; + Text *t; + Pid *pids, *p, *lastp; + enum { WErr, WKill, WWait, WCmd, NWALT }; + Alt alts[NWALT+1]; + + threadsetname("waitthread"); + pids = nil; + alts[WErr].c = cerr; + alts[WErr].v = &err; + alts[WErr].op = CHANRCV; + alts[WKill].c = ckill; + alts[WKill].v = &cmd; + alts[WKill].op = CHANRCV; + alts[WWait].c = cwait; + alts[WWait].v = &w; + alts[WWait].op = CHANRCV; + alts[WCmd].c = ccommand; + alts[WCmd].v = &c; + alts[WCmd].op = CHANRCV; + alts[NWALT].op = CHANEND; + + command = nil; + for(;;){ + switch(alt(alts)){ + case WErr: + qlock(&row); + warning(nil, "%s", err); + free(err); + flushimage(display, 1); + qunlock(&row); + break; + case WKill: + found = FALSE; + ncmd = runestrlen(cmd); + for(c=command; c; c=c->next){ + /* -1 for blank */ + if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){ + if(postnote(PNGROUP, c->pid, "kill") < 0) + warning(nil, "kill %S: %r\n", cmd); + found = TRUE; + } + } + if(!found) + warning(nil, "Kill: no process %S\n", cmd); + free(cmd); + break; + case WWait: + pid = w->pid; + lc = nil; + for(c=command; c; c=c->next){ + if(c->pid == pid){ + if(lc) + lc->next = c->next; + else + command = c->next; + break; + } + lc = c; + } + qlock(&row); + t = &row.tag; + textcommit(t, TRUE); + if(c == nil){ + /* helper processes use this exit status */ + if(strncmp(w->msg, "libthread", 9) != 0){ + p = emalloc(sizeof(Pid)); + p->pid = pid; + strncpy(p->msg, w->msg, sizeof(p->msg)); + p->next = pids; + pids = p; + } + }else{ + if(search(t, c->name, c->nname)){ + textdelete(t, t->q0, t->q1, TRUE); + textsetselect(t, 0, 0); + } + if(w->msg[0]) + warning(c->md, "%s\n", w->msg); + flushimage(display, 1); + } + qunlock(&row); + free(w); + Freecmd: + if(c){ + if(c->iseditcmd) + sendul(cedit, 0); + free(c->text); + free(c->name); + fsysdelid(c->md); + free(c); + } + break; + case WCmd: + /* has this command already exited? */ + lastp = nil; + for(p=pids; p!=nil; p=p->next){ + if(p->pid == c->pid){ + if(p->msg[0]) + warning(c->md, "%s\n", p->msg); + if(lastp == nil) + pids = p->next; + else + lastp->next = p->next; + free(p); + goto Freecmd; + } + lastp = p; + } + c->next = command; + command = c; + qlock(&row); + t = &row.tag; + textcommit(t, TRUE); + textinsert(t, 0, c->name, c->nname, TRUE); + textsetselect(t, 0, 0); + flushimage(display, 1); + qunlock(&row); + break; + } + } +} + +void +xfidallocthread(void*) +{ + Xfid *xfree, *x; + enum { Alloc, Free, N }; + static Alt alts[N+1]; + + threadsetname("xfidallocthread"); + alts[Alloc].c = cxfidalloc; + alts[Alloc].v = nil; + alts[Alloc].op = CHANRCV; + alts[Free].c = cxfidfree; + alts[Free].v = &x; + alts[Free].op = CHANRCV; + alts[N].op = CHANEND; + + xfree = nil; + for(;;){ + switch(alt(alts)){ + case Alloc: + x = xfree; + if(x) + xfree = x->next; + else{ + x = emalloc(sizeof(Xfid)); + x->c = chancreate(sizeof(void(*)(Xfid*)), 0); + x->arg = x; + threadcreate(xfidctl, x->arg, STACK); + } + sendp(cxfidalloc, x); + break; + case Free: + x->next = xfree; + xfree = x; + break; + } + } +} + +/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */ +void +newwindowthread(void*) +{ + Window *w; + + threadsetname("newwindowthread"); + + for(;;){ + /* only fsysproc is talking to us, so synchronization is trivial */ + recvp(cnewwindow); + w = makenewwindow(nil); + winsettag(w); + xfidlog(w, "new"); + sendp(cnewwindow, w); + } +} + +Reffont* +rfget(int fix, int save, int setfont, char *name) +{ + Reffont *r; + Font *f; + int i; + + r = nil; + if(name == nil){ + name = fontnames[fix]; + r = reffonts[fix]; + } + if(r == nil){ + for(i=0; i<nfontcache; i++) + if(strcmp(name, fontcache[i]->f->name) == 0){ + r = fontcache[i]; + goto Found; + } + f = openfont(display, name); + if(f == nil){ + warning(nil, "can't open font file %s: %r\n", name); + return nil; + } + r = emalloc(sizeof(Reffont)); + r->f = f; + fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*)); + fontcache[nfontcache++] = r; + } + Found: + if(save){ + incref(r); + if(reffonts[fix]) + rfclose(reffonts[fix]); + reffonts[fix] = r; + if(name != fontnames[fix]){ + free(fontnames[fix]); + fontnames[fix] = estrdup(name); + } + } + if(setfont){ + reffont.f = r->f; + incref(r); + rfclose(reffonts[0]); + font = r->f; + reffonts[0] = r; + incref(r); + iconinit(); + } + incref(r); + return r; +} + +void +rfclose(Reffont *r) +{ + int i; + + if(decref(r) == 0){ + for(i=0; i<nfontcache; i++) + if(r == fontcache[i]) + break; + if(i >= nfontcache) + warning(nil, "internal error: can't find font in cache\n"); + else{ + nfontcache--; + memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*)); + } + freefont(r->f); + free(r); + } +} + +Cursor boxcursor = { + {-7, -7}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, + 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00} +}; + +void +iconinit(void) +{ + Rectangle r; + Image *tmp; + + /* Blue */ + tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite); + tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen); + tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue); + tagcols[TEXT] = display->black; + tagcols[HTEXT] = display->black; + + /* Yellow */ + textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite); + textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow); + textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen); + textcols[TEXT] = display->black; + textcols[HTEXT] = display->black; + + if(button){ + freeimage(button); + freeimage(modbutton); + freeimage(colbutton); + } + + r = Rect(0, 0, Scrollwid+2, font->height+1); + button = allocimage(display, r, screen->chan, 0, DNofill); + draw(button, r, tagcols[BACK], nil, r.min); + r.max.x -= 2; + border(button, r, 2, tagcols[BORD], ZP); + + r = button->r; + modbutton = allocimage(display, r, screen->chan, 0, DNofill); + draw(modbutton, r, tagcols[BACK], nil, r.min); + r.max.x -= 2; + border(modbutton, r, 2, tagcols[BORD], ZP); + r = insetrect(r, 2); + tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue); + draw(modbutton, r, tmp, nil, ZP); + freeimage(tmp); + + r = button->r; + colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue); + + but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF); + but3col = allocimage(display, r, screen->chan, 1, 0x006600FF); +} + +/* + * /dev/snarf updates when the file is closed, so we must open our own + * fd here rather than use snarffd + */ + +/* rio truncates larges snarf buffers, so this avoids using the + * service if the string is huge */ + +#define MAXSNARF 100*1024 + +void +putsnarf(void) +{ + int fd, i, n; + + if(snarffd<0 || snarfbuf.nc==0) + return; + if(snarfbuf.nc > MAXSNARF) + return; + fd = open("/dev/snarf", OWRITE); + if(fd < 0) + return; + for(i=0; i<snarfbuf.nc; i+=n){ + n = snarfbuf.nc-i; + if(n >= NSnarf) + n = NSnarf; + bufread(&snarfbuf, i, snarfrune, n); + if(fprint(fd, "%.*S", n, snarfrune) < 0) + break; + } + close(fd); +} + +void +getsnarf() +{ + int nulls; + + if(snarfbuf.nc > MAXSNARF) + return; + if(snarffd < 0) + return; + seek(snarffd, 0, 0); + bufreset(&snarfbuf); + bufload(&snarfbuf, 0, snarffd, &nulls); +} diff --git a/patch/acme-moveto-undo/acme.c.orig b/patch/acme-moveto-undo/acme.c.orig new file mode 100644 index 0000000..e874d35 --- /dev/null +++ b/patch/acme-moveto-undo/acme.c.orig @@ -0,0 +1,966 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <thread.h> +#include <cursor.h> +#include <mouse.h> +#include <keyboard.h> +#include <frame.h> +#include <fcall.h> +#include <plumb.h> +#include "dat.h" +#include "fns.h" + /* for generating syms in mkfile only: */ + #include <bio.h> + #include "edit.h" + +void mousethread(void*); +void keyboardthread(void*); +void waitthread(void*); +void xfidallocthread(void*); +void newwindowthread(void*); +void plumbproc(void*); + +Reffont **fontcache; +int nfontcache; +char wdir[512] = "."; +Reffont *reffonts[2]; +int snarffd = -1; +int mainpid; +int plumbsendfd; +int plumbeditfd; + +enum{ + NSnarf = 1000 /* less than 1024, I/O buffer size */ +}; +Rune snarfrune[NSnarf+1]; + +char *fontnames[2]; + +Command *command; + +void acmeerrorinit(void); +void readfile(Column*, char*); +int shutdown(void*, char*); + +void +derror(Display*, char *errorstr) +{ + error(errorstr); +} + +void +threadmain(int argc, char *argv[]) +{ + int i; + char *p, *loadfile; + char buf[256]; + Column *c; + int ncol; + Display *d; + + rfork(RFENVG|RFNAMEG); + + ncol = -1; + + loadfile = nil; + ARGBEGIN{ + case 'a': + globalindent[AUTOINDENT] = TRUE; + break; + case 'b': + bartflag = TRUE; + break; + case 'c': + p = ARGF(); + if(p == nil) + goto Usage; + ncol = atoi(p); + if(ncol <= 0) + goto Usage; + break; + case 'f': + fontnames[0] = ARGF(); + if(fontnames[0] == nil) + goto Usage; + break; + case 'F': + fontnames[1] = ARGF(); + if(fontnames[1] == nil) + goto Usage; + break; + case 'i': + globalindent[SPACESINDENT] = TRUE; + break; + case 'l': + loadfile = ARGF(); + if(loadfile == nil) + goto Usage; + break; + default: + Usage: + fprint(2, "usage: acme [-aib] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n"); + exits("usage"); + }ARGEND + + if(fontnames[0] == nil) + fontnames[0] = getenv("font"); + if(fontnames[0] == nil) + fontnames[0] = "/lib/font/bit/vga/unicode.font"; + if(access(fontnames[0], 0) < 0){ + fprint(2, "acme: can't access %s: %r\n", fontnames[0]); + exits("font open"); + } + if(fontnames[1] == nil) + fontnames[1] = fontnames[0]; + fontnames[0] = estrdup(fontnames[0]); + fontnames[1] = estrdup(fontnames[1]); + + quotefmtinstall(); + cputype = getenv("cputype"); + objtype = getenv("objtype"); + home = getenv("home"); + p = getenv("tabstop"); + if(p != nil){ + maxtab = strtoul(p, nil, 0); + free(p); + } + if(maxtab == 0) + maxtab = 4; + if(loadfile) + rowloadfonts(loadfile); + putenv("font", fontnames[0]); + snarffd = open("/dev/snarf", OREAD|OCEXEC); + if(cputype){ + sprint(buf, "/acme/bin/%s", cputype); + bind(buf, "/bin", MBEFORE); + } + bind("/acme/bin", "/bin", MBEFORE); + getwd(wdir, sizeof wdir); + + if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){ + fprint(2, "acme: can't open display: %r\n"); + exits("geninitdraw"); + } + d = display; + font = d->defaultfont; + + reffont.f = font; + reffonts[0] = &reffont; + incref(&reffont); /* one to hold up 'font' variable */ + incref(&reffont); /* one to hold up reffonts[0] */ + fontcache = emalloc(sizeof(Reffont*)); + nfontcache = 1; + fontcache[0] = &reffont; + + iconinit(); + timerinit(); + rxinit(); + + cwait = threadwaitchan(); + ccommand = chancreate(sizeof(Command**), 0); + ckill = chancreate(sizeof(Rune*), 0); + cxfidalloc = chancreate(sizeof(Xfid*), 0); + cxfidfree = chancreate(sizeof(Xfid*), 0); + cnewwindow = chancreate(sizeof(Channel*), 0); + cerr = chancreate(sizeof(char*), 0); + cedit = chancreate(sizeof(int), 0); + cexit = chancreate(sizeof(int), 0); + cwarn = chancreate(sizeof(void*), 1); + if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cnewwindow==nil || cerr==nil || cedit==nil || cexit==nil || cwarn==nil){ + fprint(2, "acme: can't create initial channels: %r\n"); + threadexitsall("channels"); + } + + mousectl = initmouse(nil, screen); + if(mousectl == nil){ + fprint(2, "acme: can't initialize mouse: %r\n"); + threadexitsall("mouse"); + } + mouse = mousectl; + keyboardctl = initkeyboard(nil); + if(keyboardctl == nil){ + fprint(2, "acme: can't initialize keyboard: %r\n"); + threadexitsall("keyboard"); + } + mainpid = getpid(); + plumbeditfd = plumbopen("edit", OREAD|OCEXEC); + if(plumbeditfd >= 0){ + cplumb = chancreate(sizeof(Plumbmsg*), 0); + proccreate(plumbproc, nil, STACK); + } + plumbsendfd = plumbopen("send", OWRITE|OCEXEC); + + fsysinit(); + + #define WPERCOL 8 + disk = diskinit(); + if(!loadfile || !rowload(&row, loadfile, TRUE)){ + rowinit(&row, screen->clipr); + if(ncol < 0){ + if(argc == 0) + ncol = 2; + else{ + ncol = (argc+(WPERCOL-1))/WPERCOL; + if(ncol < 2) + ncol = 2; + } + } + if(ncol == 0) + ncol = 2; + for(i=0; i<ncol; i++){ + c = rowadd(&row, nil, -1); + if(c==nil && i==0) + error("initializing columns"); + } + c = row.col[row.ncol-1]; + if(argc == 0) + readfile(c, wdir); + else + for(i=0; i<argc; i++){ + p = utfrrune(argv[i], '/'); + if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol) + readfile(c, argv[i]); + else + readfile(row.col[i/WPERCOL], argv[i]); + } + } + flushimage(display, 1); + + acmeerrorinit(); + threadcreate(keyboardthread, nil, STACK); + threadcreate(mousethread, nil, STACK); + threadcreate(waitthread, nil, STACK); + threadcreate(xfidallocthread, nil, STACK); + threadcreate(newwindowthread, nil, STACK); + + threadnotify(shutdown, 1); + recvul(cexit); + killprocs(); + threadexitsall(nil); +} + +void +readfile(Column *c, char *s) +{ + Window *w; + Rune rb[256]; + int nb, nr; + Runestr rs; + + w = coladd(c, nil, nil, -1); + cvttorunes(s, strlen(s), rb, &nb, &nr, nil); + rs = cleanrname((Runestr){rb, nr}); + winsetname(w, rs.r, rs.nr); + textload(&w->body, 0, s, 1); + w->body.file->mod = FALSE; + w->dirty = FALSE; + winsettag(w); + textscrdraw(&w->body); + textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc); + xfidlog(w, "new"); +} + +char *oknotes[] ={ + "delete", + "hangup", + "kill", + "exit", + nil +}; + +int dumping; + +int +shutdown(void*, char *msg) +{ + int i; + + killprocs(); + if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){ + dumping = TRUE; + rowdump(&row, nil); + } + for(i=0; oknotes[i]; i++) + if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0) + threadexitsall(msg); + print("acme: %s\n", msg); + abort(); + return 0; +} + +void +killprocs(void) +{ + Command *c; + + fsysclose(); +// if(display) +// flushimage(display, 1); + + for(c=command; c; c=c->next) + postnote(PNGROUP, c->pid, "hangup"); + remove(acmeerrorfile); +} + +static int errorfd; + +void +acmeerrorproc(void *) +{ + char *buf, *s; + int n; + + threadsetname("acmeerrorproc"); + buf = emalloc(8192+1); + while((n=read(errorfd, buf, 8192)) >= 0){ + buf[n] = '\0'; + s = estrdup(buf); + sendp(cerr, s); + } + free(buf); +} + +void +acmeerrorinit(void) +{ + int fd, pfd[2]; + char buf[64]; + + if(pipe(pfd) < 0) + error("can't create pipe"); + sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid); + fd = create(acmeerrorfile, OWRITE, 0666); + if(fd < 0){ + remove(acmeerrorfile); + fd = create(acmeerrorfile, OWRITE, 0666); + if(fd < 0) + error("can't create acmeerror file"); + } + sprint(buf, "%d", pfd[0]); + write(fd, buf, strlen(buf)); + close(fd); + /* reopen pfd[1] close on exec */ + sprint(buf, "/fd/%d", pfd[1]); + errorfd = open(buf, OREAD|OCEXEC); + if(errorfd < 0) + error("can't re-open acmeerror file"); + close(pfd[1]); + close(pfd[0]); + proccreate(acmeerrorproc, nil, STACK); +} + +void +plumbproc(void *) +{ + Plumbmsg *m; + + threadsetname("plumbproc"); + for(;;){ + m = plumbrecv(plumbeditfd); + if(m == nil) + threadexits(nil); + sendp(cplumb, m); + } +} + +void +keyboardthread(void *) +{ + Rune r; + Timer *timer; + Text *t; + enum { KTimer, KKey, NKALT }; + static Alt alts[NKALT+1]; + + alts[KTimer].c = nil; + alts[KTimer].v = nil; + alts[KTimer].op = CHANNOP; + alts[KKey].c = keyboardctl->c; + alts[KKey].v = &r; + alts[KKey].op = CHANRCV; + alts[NKALT].op = CHANEND; + + timer = nil; + typetext = nil; + threadsetname("keyboardthread"); + for(;;){ + switch(alt(alts)){ + case KTimer: + timerstop(timer); + t = typetext; + if(t!=nil && t->what==Tag){ + winlock(t->w, 'K'); + wincommit(t->w, t); + winunlock(t->w); + flushimage(display, 1); + } + alts[KTimer].c = nil; + alts[KTimer].op = CHANNOP; + break; + case KKey: + casekeyboard: + typetext = rowtype(&row, r, mouse->xy); + t = typetext; + if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */ + activecol = t->col; + if(t!=nil && t->w!=nil) + t->w->body.file->curtext = &t->w->body; + if(timer != nil) + timercancel(timer); + if(t!=nil && t->what==Tag) { + timer = timerstart(500); + alts[KTimer].c = timer->c; + alts[KTimer].op = CHANRCV; + }else{ + timer = nil; + alts[KTimer].c = nil; + alts[KTimer].op = CHANNOP; + } + if(nbrecv(keyboardctl->c, &r) > 0) + goto casekeyboard; + flushimage(display, 1); + break; + } + } +} + +void +mousethread(void *) +{ + Text *t, *argt; + int but; + uint q0, q1; + Window *w; + Plumbmsg *pm; + Mouse m; + char *act; + enum { MResize, MMouse, MPlumb, MWarnings, NMALT }; + static Alt alts[NMALT+1]; + + threadsetname("mousethread"); + alts[MResize].c = mousectl->resizec; + alts[MResize].v = nil; + alts[MResize].op = CHANRCV; + alts[MMouse].c = mousectl->c; + alts[MMouse].v = &mousectl->Mouse; + alts[MMouse].op = CHANRCV; + alts[MPlumb].c = cplumb; + alts[MPlumb].v = ± + alts[MPlumb].op = CHANRCV; + alts[MWarnings].c = cwarn; + alts[MWarnings].v = nil; + alts[MWarnings].op = CHANRCV; + if(cplumb == nil) + alts[MPlumb].op = CHANNOP; + alts[NMALT].op = CHANEND; + + for(;;){ + qlock(&row); + flushwarnings(); + qunlock(&row); + flushimage(display, 1); + switch(alt(alts)){ + case MResize: + if(getwindow(display, Refnone) < 0) + error("attach to window"); + scrlresize(); + rowresize(&row, screen->clipr); + break; + case MPlumb: + if(strcmp(pm->type, "text") == 0){ + act = plumblookup(pm->attr, "action"); + if(act==nil || strcmp(act, "showfile")==0) + plumblook(pm); + else if(strcmp(act, "showdata")==0) + plumbshow(pm); + } + plumbfree(pm); + break; + case MWarnings: + break; + case MMouse: + /* + * Make a copy so decisions are consistent; mousectl changes + * underfoot. Can't just receive into m because this introduces + * another race; see /sys/src/libdraw/mouse.c. + */ + m = mousectl->Mouse; + qlock(&row); + t = rowwhich(&row, m.xy); + + if((t!=mousetext && t!=nil && t->w!=nil) && + (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) { + xfidlog(t->w, "focus"); + } + + if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){ + winlock(mousetext->w, 'M'); + mousetext->eq0 = ~0; + wincommit(mousetext->w, mousetext); + winunlock(mousetext->w); + } + mousetext = t; + if(t == nil) + goto Continue; + w = t->w; + if(t==nil || m.buttons==0) + goto Continue; + but = 0; + if(m.buttons == 1) + but = 1; + else if(m.buttons == 2) + but = 2; + else if(m.buttons == 4) + but = 3; + barttext = t; + if(t->what==Body && ptinrect(m.xy, t->scrollr)){ + if(but){ + winlock(w, 'M'); + t->eq0 = ~0; + textscroll(t, but); + winunlock(w); + } + goto Continue; + } + /* scroll buttons, wheels, etc. */ + if(t->what==Body && w != nil && (m.buttons & (8|16))){ + if(m.buttons & 8) + but = Kscrolloneup; + else + but = Kscrollonedown; + winlock(w, 'M'); + t->eq0 = ~0; + texttype(t, but); + winunlock(w); + goto Continue; + } + if(ptinrect(m.xy, t->scrollr)){ + if(but){ + if(t->what == Columntag) + rowdragcol(&row, t->col, but); + else if(t->what == Tag){ + coldragwin(t->col, t->w, but); + if(t->w) + barttext = &t->w->body; + } + if(t->col) + activecol = t->col; + } + goto Continue; + } + if(m.buttons){ + if(w) + winlock(w, 'M'); + t->eq0 = ~0; + if(w) + wincommit(w, t); + else + textcommit(t, TRUE); + if(m.buttons & 1){ + textselect(t); + if(w) + winsettag(w); + argtext = t; + seltext = t; + if(t->col) + activecol = t->col; /* button 1 only */ + if(t->w!=nil && t==&t->w->body) + activewin = t->w; + }else if(m.buttons & 2){ + if(textselect2(t, &q0, &q1, &argt)) + execute(t, q0, q1, FALSE, argt); + }else if(m.buttons & 4){ + if(textselect3(t, &q0, &q1)) + look3(t, q0, q1, FALSE); + } + if(w) + winunlock(w); + goto Continue; + } + Continue: + qunlock(&row); + break; + } + } +} + +/* + * There is a race between process exiting and our finding out it was ever created. + * This structure keeps a list of processes that have exited we haven't heard of. + */ +typedef struct Pid Pid; +struct Pid +{ + int pid; + char msg[ERRMAX]; + Pid *next; +}; + +void +waitthread(void *) +{ + Waitmsg *w; + Command *c, *lc; + uint pid; + int found, ncmd; + Rune *cmd; + char *err; + Text *t; + Pid *pids, *p, *lastp; + enum { WErr, WKill, WWait, WCmd, NWALT }; + Alt alts[NWALT+1]; + + threadsetname("waitthread"); + pids = nil; + alts[WErr].c = cerr; + alts[WErr].v = &err; + alts[WErr].op = CHANRCV; + alts[WKill].c = ckill; + alts[WKill].v = &cmd; + alts[WKill].op = CHANRCV; + alts[WWait].c = cwait; + alts[WWait].v = &w; + alts[WWait].op = CHANRCV; + alts[WCmd].c = ccommand; + alts[WCmd].v = &c; + alts[WCmd].op = CHANRCV; + alts[NWALT].op = CHANEND; + + command = nil; + for(;;){ + switch(alt(alts)){ + case WErr: + qlock(&row); + warning(nil, "%s", err); + free(err); + flushimage(display, 1); + qunlock(&row); + break; + case WKill: + found = FALSE; + ncmd = runestrlen(cmd); + for(c=command; c; c=c->next){ + /* -1 for blank */ + if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){ + if(postnote(PNGROUP, c->pid, "kill") < 0) + warning(nil, "kill %S: %r\n", cmd); + found = TRUE; + } + } + if(!found) + warning(nil, "Kill: no process %S\n", cmd); + free(cmd); + break; + case WWait: + pid = w->pid; + lc = nil; + for(c=command; c; c=c->next){ + if(c->pid == pid){ + if(lc) + lc->next = c->next; + else + command = c->next; + break; + } + lc = c; + } + qlock(&row); + t = &row.tag; + textcommit(t, TRUE); + if(c == nil){ + /* helper processes use this exit status */ + if(strncmp(w->msg, "libthread", 9) != 0){ + p = emalloc(sizeof(Pid)); + p->pid = pid; + strncpy(p->msg, w->msg, sizeof(p->msg)); + p->next = pids; + pids = p; + } + }else{ + if(search(t, c->name, c->nname)){ + textdelete(t, t->q0, t->q1, TRUE); + textsetselect(t, 0, 0); + } + if(w->msg[0]) + warning(c->md, "%s\n", w->msg); + flushimage(display, 1); + } + qunlock(&row); + free(w); + Freecmd: + if(c){ + if(c->iseditcmd) + sendul(cedit, 0); + free(c->text); + free(c->name); + fsysdelid(c->md); + free(c); + } + break; + case WCmd: + /* has this command already exited? */ + lastp = nil; + for(p=pids; p!=nil; p=p->next){ + if(p->pid == c->pid){ + if(p->msg[0]) + warning(c->md, "%s\n", p->msg); + if(lastp == nil) + pids = p->next; + else + lastp->next = p->next; + free(p); + goto Freecmd; + } + lastp = p; + } + c->next = command; + command = c; + qlock(&row); + t = &row.tag; + textcommit(t, TRUE); + textinsert(t, 0, c->name, c->nname, TRUE); + textsetselect(t, 0, 0); + flushimage(display, 1); + qunlock(&row); + break; + } + } +} + +void +xfidallocthread(void*) +{ + Xfid *xfree, *x; + enum { Alloc, Free, N }; + static Alt alts[N+1]; + + threadsetname("xfidallocthread"); + alts[Alloc].c = cxfidalloc; + alts[Alloc].v = nil; + alts[Alloc].op = CHANRCV; + alts[Free].c = cxfidfree; + alts[Free].v = &x; + alts[Free].op = CHANRCV; + alts[N].op = CHANEND; + + xfree = nil; + for(;;){ + switch(alt(alts)){ + case Alloc: + x = xfree; + if(x) + xfree = x->next; + else{ + x = emalloc(sizeof(Xfid)); + x->c = chancreate(sizeof(void(*)(Xfid*)), 0); + x->arg = x; + threadcreate(xfidctl, x->arg, STACK); + } + sendp(cxfidalloc, x); + break; + case Free: + x->next = xfree; + xfree = x; + break; + } + } +} + +/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */ +void +newwindowthread(void*) +{ + Window *w; + + threadsetname("newwindowthread"); + + for(;;){ + /* only fsysproc is talking to us, so synchronization is trivial */ + recvp(cnewwindow); + w = makenewwindow(nil); + winsettag(w); + xfidlog(w, "new"); + sendp(cnewwindow, w); + } +} + +Reffont* +rfget(int fix, int save, int setfont, char *name) +{ + Reffont *r; + Font *f; + int i; + + r = nil; + if(name == nil){ + name = fontnames[fix]; + r = reffonts[fix]; + } + if(r == nil){ + for(i=0; i<nfontcache; i++) + if(strcmp(name, fontcache[i]->f->name) == 0){ + r = fontcache[i]; + goto Found; + } + f = openfont(display, name); + if(f == nil){ + warning(nil, "can't open font file %s: %r\n", name); + return nil; + } + r = emalloc(sizeof(Reffont)); + r->f = f; + fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*)); + fontcache[nfontcache++] = r; + } + Found: + if(save){ + incref(r); + if(reffonts[fix]) + rfclose(reffonts[fix]); + reffonts[fix] = r; + if(name != fontnames[fix]){ + free(fontnames[fix]); + fontnames[fix] = estrdup(name); + } + } + if(setfont){ + reffont.f = r->f; + incref(r); + rfclose(reffonts[0]); + font = r->f; + reffonts[0] = r; + incref(r); + iconinit(); + } + incref(r); + return r; +} + +void +rfclose(Reffont *r) +{ + int i; + + if(decref(r) == 0){ + for(i=0; i<nfontcache; i++) + if(r == fontcache[i]) + break; + if(i >= nfontcache) + warning(nil, "internal error: can't find font in cache\n"); + else{ + nfontcache--; + memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*)); + } + freefont(r->f); + free(r); + } +} + +Cursor boxcursor = { + {-7, -7}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, + 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00} +}; + +void +iconinit(void) +{ + Rectangle r; + Image *tmp; + + /* Blue */ + tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite); + tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen); + tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue); + tagcols[TEXT] = display->black; + tagcols[HTEXT] = display->black; + + /* Yellow */ + textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite); + textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow); + textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen); + textcols[TEXT] = display->black; + textcols[HTEXT] = display->black; + + if(button){ + freeimage(button); + freeimage(modbutton); + freeimage(colbutton); + } + + r = Rect(0, 0, Scrollwid+2, font->height+1); + button = allocimage(display, r, screen->chan, 0, DNofill); + draw(button, r, tagcols[BACK], nil, r.min); + r.max.x -= 2; + border(button, r, 2, tagcols[BORD], ZP); + + r = button->r; + modbutton = allocimage(display, r, screen->chan, 0, DNofill); + draw(modbutton, r, tagcols[BACK], nil, r.min); + r.max.x -= 2; + border(modbutton, r, 2, tagcols[BORD], ZP); + r = insetrect(r, 2); + tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue); + draw(modbutton, r, tmp, nil, ZP); + freeimage(tmp); + + r = button->r; + colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue); + + but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF); + but3col = allocimage(display, r, screen->chan, 1, 0x006600FF); +} + +/* + * /dev/snarf updates when the file is closed, so we must open our own + * fd here rather than use snarffd + */ + +/* rio truncates larges snarf buffers, so this avoids using the + * service if the string is huge */ + +#define MAXSNARF 100*1024 + +void +putsnarf(void) +{ + int fd, i, n; + + if(snarffd<0 || snarfbuf.nc==0) + return; + if(snarfbuf.nc > MAXSNARF) + return; + fd = open("/dev/snarf", OWRITE); + if(fd < 0) + return; + for(i=0; i<snarfbuf.nc; i+=n){ + n = snarfbuf.nc-i; + if(n >= NSnarf) + n = NSnarf; + bufread(&snarfbuf, i, snarfrune, n); + if(fprint(fd, "%.*S", n, snarfrune) < 0) + break; + } + close(fd); +} + +void +getsnarf() +{ + int nulls; + + if(snarfbuf.nc > MAXSNARF) + return; + if(snarffd < 0) + return; + seek(snarffd, 0, 0); + bufreset(&snarfbuf); + bufload(&snarfbuf, 0, snarffd, &nulls); +} diff --git a/patch/acme-moveto-undo/email b/patch/acme-moveto-undo/email new file mode 100644 index 0000000..191feb6 --- /dev/null +++ b/patch/acme-moveto-undo/email @@ -0,0 +1 @@ +john@ankarstrom.se diff --git a/patch/acme-moveto-undo/files b/patch/acme-moveto-undo/files new file mode 100644 index 0000000..610da9a --- /dev/null +++ b/patch/acme-moveto-undo/files @@ -0,0 +1,5 @@ +/sys/src/cmd/acme/acme.c acme.c +/sys/src/cmd/acme/fns.h fns.h +/sys/src/cmd/acme/moveto.c moveto.c +/sys/src/cmd/acme/text.c text.c +/sys/src/cmd/acme/mkfile mkfile diff --git a/patch/acme-moveto-undo/fns.h b/patch/acme-moveto-undo/fns.h new file mode 100644 index 0000000..4ba641b --- /dev/null +++ b/patch/acme-moveto-undo/fns.h @@ -0,0 +1,100 @@ +#pragma varargck argpos warning 2 + +void warning(Mntdir*, char*, ...); + +#define fbufalloc() emalloc(BUFSIZE) +#define fbuffree(x) free(x) + +#define moveto(mc, pt) recmoveto(mc, pt) +void recmoveto(Mousectl *mc, Point pt); +void undomoveto(Mousectl *mc); + +void plumblook(Plumbmsg*m); +void plumbshow(Plumbmsg*m); +void putsnarf(void); +void getsnarf(void); +int tempfile(void); +void scrlresize(void); +Font* getfont(int, int, char*); +char* getarg(Text*, int, int, Rune**, int*); +char* getbytearg(Text*, int, int, char**); +void new(Text*, Text*, Text*, int, int, Rune*, int); +void undo(Text*, Text*, Text*, int, int, Rune*, int); +char* getname(Text*, Text*, Rune*, int, int); +void scrsleep(uint); +void savemouse(Window*); +int restoremouse(Window*); +void clearmouse(void); +void allwindows(void(*)(Window*, void*), void*); +uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*); +void movetodel(Window*); + +Window* errorwin(Mntdir*, int); +Window* errorwinforwin(Window*); +Runestr cleanrname(Runestr); +void run(Window*, char*, Rune*, int, int, char*, char*, int); +void fsysclose(void); +void setcurtext(Text*, int); +int isfilec(Rune); +void rxinit(void); +int rxnull(void); +Runestr dirname(Text*, Rune*, int); +void error(char*); +void cvttorunes(char*, int, Rune*, int*, int*, int*); +void* tmalloc(uint); +void tfree(void); +void killprocs(void); +void killtasks(void); +int runeeq(Rune*, uint, Rune*, uint); +int ALEF_tid(void); +void iconinit(void); +Timer* timerstart(int); +void timerstop(Timer*); +void timercancel(Timer*); +void timerinit(void); +void cut(Text*, Text*, Text*, int, int, Rune*, int); +void paste(Text*, Text*, Text*, int, int, Rune*, int); +void get(Text*, Text*, Text*, int, int, Rune*, int); +void put(Text*, Text*, Text*, int, int, Rune*, int); +void putfile(File*, int, int, Rune*, int); +void fontx(Text*, Text*, Text*, int, int, Rune*, int); +int isspace(Rune); +int isalnum(Rune); +void execute(Text*, uint, uint, int, Text*); +int search(Text*, Rune*, uint); +void look3(Text*, uint, uint, int); +void editcmd(Text*, Rune*, uint); +uint min(uint, uint); +uint max(uint, uint); +Window* lookfile(Rune*, int); +Window* lookid(int, int); +char* runetobyte(Rune*, int); +Rune* bytetorune(char*, int*); +void fsysinit(void); +Mntdir* fsysmount(Rune*, int, Rune**, int); +void fsysincid(Mntdir*); +void fsysdelid(Mntdir*); +Xfid* respond(Xfid*, Fcall*, char*); +int rxcompile(Rune*); +int rgetc(void*, uint); +int tgetc(void*, uint); +int isaddrc(int); +int isregexc(int); +void *emalloc(uint); +void *erealloc(void*, uint); +char *estrdup(char*); +Range address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*); +int rxexecute(Text*, Rune*, uint, uint, Rangeset*); +int rxbexecute(Text*, uint, Rangeset*); +Window* makenewwindow(Text *t); +int expand(Text*, uint, uint, Expand*); +Rune* skipbl(Rune*, int, int*); +Rune* findbl(Rune*, int, int*); +char* edittext(Window*, int, Rune*, int); +void flushwarnings(void); +long nlcount(Text*, long, long, long*); +long nlcounttopos(Text*, long, long, long); + +#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune)) +#define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune)) +#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune)) diff --git a/patch/acme-moveto-undo/fns.h.orig b/patch/acme-moveto-undo/fns.h.orig new file mode 100644 index 0000000..02cc06d --- /dev/null +++ b/patch/acme-moveto-undo/fns.h.orig @@ -0,0 +1,96 @@ +#pragma varargck argpos warning 2 + +void warning(Mntdir*, char*, ...); + +#define fbufalloc() emalloc(BUFSIZE) +#define fbuffree(x) free(x) + +void plumblook(Plumbmsg*m); +void plumbshow(Plumbmsg*m); +void putsnarf(void); +void getsnarf(void); +int tempfile(void); +void scrlresize(void); +Font* getfont(int, int, char*); +char* getarg(Text*, int, int, Rune**, int*); +char* getbytearg(Text*, int, int, char**); +void new(Text*, Text*, Text*, int, int, Rune*, int); +void undo(Text*, Text*, Text*, int, int, Rune*, int); +char* getname(Text*, Text*, Rune*, int, int); +void scrsleep(uint); +void savemouse(Window*); +int restoremouse(Window*); +void clearmouse(void); +void allwindows(void(*)(Window*, void*), void*); +uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*); +void movetodel(Window*); + +Window* errorwin(Mntdir*, int); +Window* errorwinforwin(Window*); +Runestr cleanrname(Runestr); +void run(Window*, char*, Rune*, int, int, char*, char*, int); +void fsysclose(void); +void setcurtext(Text*, int); +int isfilec(Rune); +void rxinit(void); +int rxnull(void); +Runestr dirname(Text*, Rune*, int); +void error(char*); +void cvttorunes(char*, int, Rune*, int*, int*, int*); +void* tmalloc(uint); +void tfree(void); +void killprocs(void); +void killtasks(void); +int runeeq(Rune*, uint, Rune*, uint); +int ALEF_tid(void); +void iconinit(void); +Timer* timerstart(int); +void timerstop(Timer*); +void timercancel(Timer*); +void timerinit(void); +void cut(Text*, Text*, Text*, int, int, Rune*, int); +void paste(Text*, Text*, Text*, int, int, Rune*, int); +void get(Text*, Text*, Text*, int, int, Rune*, int); +void put(Text*, Text*, Text*, int, int, Rune*, int); +void putfile(File*, int, int, Rune*, int); +void fontx(Text*, Text*, Text*, int, int, Rune*, int); +int isspace(Rune); +int isalnum(Rune); +void execute(Text*, uint, uint, int, Text*); +int search(Text*, Rune*, uint); +void look3(Text*, uint, uint, int); +void editcmd(Text*, Rune*, uint); +uint min(uint, uint); +uint max(uint, uint); +Window* lookfile(Rune*, int); +Window* lookid(int, int); +char* runetobyte(Rune*, int); +Rune* bytetorune(char*, int*); +void fsysinit(void); +Mntdir* fsysmount(Rune*, int, Rune**, int); +void fsysincid(Mntdir*); +void fsysdelid(Mntdir*); +Xfid* respond(Xfid*, Fcall*, char*); +int rxcompile(Rune*); +int rgetc(void*, uint); +int tgetc(void*, uint); +int isaddrc(int); +int isregexc(int); +void *emalloc(uint); +void *erealloc(void*, uint); +char *estrdup(char*); +Range address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*); +int rxexecute(Text*, Rune*, uint, uint, Rangeset*); +int rxbexecute(Text*, uint, Rangeset*); +Window* makenewwindow(Text *t); +int expand(Text*, uint, uint, Expand*); +Rune* skipbl(Rune*, int, int*); +Rune* findbl(Rune*, int, int*); +char* edittext(Window*, int, Rune*, int); +void flushwarnings(void); +long nlcount(Text*, long, long, long*); +long nlcounttopos(Text*, long, long, long); + +#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune)) +#define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune)) +#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune)) diff --git a/patch/acme-moveto-undo/mkfile b/patch/acme-moveto-undo/mkfile new file mode 100644 index 0000000..f5048ca --- /dev/null +++ b/patch/acme-moveto-undo/mkfile @@ -0,0 +1,47 @@ +</$objtype/mkfile +BIN=/$objtype/bin + +TARG=acme + +OFILES=\ + acme.$O\ + addr.$O\ + buff.$O\ + cols.$O\ + disk.$O\ + ecmd.$O\ + edit.$O\ + elog.$O\ + exec.$O\ + file.$O\ + fsys.$O\ + logf.$O\ + look.$O\ + moveto.$O\ + regx.$O\ + rows.$O\ + scrl.$O\ + text.$O\ + time.$O\ + util.$O\ + wind.$O\ + xfid.$O\ + +HFILES=dat.h\ + edit.h\ + fns.h\ + +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + +</sys/src/cmd/mkone + +$O.out: /$objtype/lib/libframe.a /$objtype/lib/libdraw.a /$objtype/lib/libthread.a + +edit.$O ecmd.$O elog.$O: edit.h + +syms:V: + $CC -a acme.c > syms + for(i in ????.c) $CC -aa $i >> syms diff --git a/patch/acme-moveto-undo/mkfile.orig b/patch/acme-moveto-undo/mkfile.orig new file mode 100644 index 0000000..22ad647 --- /dev/null +++ b/patch/acme-moveto-undo/mkfile.orig @@ -0,0 +1,46 @@ +</$objtype/mkfile +BIN=/$objtype/bin + +TARG=acme + +OFILES=\ + acme.$O\ + addr.$O\ + buff.$O\ + cols.$O\ + disk.$O\ + ecmd.$O\ + edit.$O\ + elog.$O\ + exec.$O\ + file.$O\ + fsys.$O\ + logf.$O\ + look.$O\ + regx.$O\ + rows.$O\ + scrl.$O\ + text.$O\ + time.$O\ + util.$O\ + wind.$O\ + xfid.$O\ + +HFILES=dat.h\ + edit.h\ + fns.h\ + +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + +</sys/src/cmd/mkone + +$O.out: /$objtype/lib/libframe.a /$objtype/lib/libdraw.a /$objtype/lib/libthread.a + +edit.$O ecmd.$O elog.$O: edit.h + +syms:V: + $CC -a acme.c > syms + for(i in ????.c) $CC -aa $i >> syms diff --git a/patch/acme-moveto-undo/moveto.c b/patch/acme-moveto-undo/moveto.c new file mode 100644 index 0000000..dae0a6b --- /dev/null +++ b/patch/acme-moveto-undo/moveto.c @@ -0,0 +1,37 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <thread.h> +#include <cursor.h> +#include <mouse.h> +#include <keyboard.h> +#include <frame.h> +#include <fcall.h> +#include <plumb.h> +#include "dat.h" + +static Point newmouse; +static Point oldmouse; + +/* call moveto, but record old and new mouse positions */ +void +recmoveto(Mousectl *mc, Point pt){ + static vlong prevtime = 0; + vlong curtime = nsec(); + if(curtime-prevtime>500000000){ /* 500ms */ + newmouse = pt; + oldmouse = mouse->xy; + } + prevtime = curtime; + moveto(mc, pt); +} + +void +undomoveto(Mousectl *mc){ + Point tmp; + moveto(mc, oldmouse); + tmp = newmouse; + newmouse = oldmouse; + oldmouse = tmp; +} + diff --git a/patch/acme-moveto-undo/notes b/patch/acme-moveto-undo/notes new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/patch/acme-moveto-undo/notes diff --git a/patch/acme-moveto-undo/readme b/patch/acme-moveto-undo/readme new file mode 100644 index 0000000..770cb83 --- /dev/null +++ b/patch/acme-moveto-undo/readme @@ -0,0 +1,5 @@ +Press F1 to undo automatic mouse movement + +Many Acme operations call moveto, moving the mouse cursor +without the user's involvement. This patch allows the user to +undo calls to moveto by pressing F1. Another F1 undoes the undo. diff --git a/patch/acme-moveto-undo/text.c b/patch/acme-moveto-undo/text.c new file mode 100644 index 0000000..484ec3d --- /dev/null +++ b/patch/acme-moveto-undo/text.c @@ -0,0 +1,1460 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <thread.h> +#include <cursor.h> +#include <mouse.h> +#include <keyboard.h> +#include <frame.h> +#include <fcall.h> +#include <plumb.h> +#include <complete.h> +#include "dat.h" +#include "fns.h" + +Image *tagcols[NCOL]; +Image *textcols[NCOL]; + +enum{ + TABDIR = 3 /* width of tabs in directory windows */ +}; + +void +textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL]) +{ + t->file = f; + t->all = r; + t->scrollr = r; + t->scrollr.max.x = r.min.x+Scrollwid; + t->lastsr = nullrect; + r.min.x += Scrollwid+Scrollgap; + t->eq0 = ~0; + t->ncache = 0; + t->reffont = rf; + t->tabstop = maxtab; + memmove(t->Frame.cols, cols, sizeof t->Frame.cols); + textredraw(t, r, rf->f, screen, -1); +} + +void +textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx) +{ + int maxt; + Rectangle rr; + + frinit(t, r, f, b, t->Frame.cols); + rr = t->r; + rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */ + draw(t->b, rr, t->cols[BACK], nil, ZP); + /* use no wider than 3-space tabs in a directory */ + maxt = maxtab; + if(t->what == Body){ + if(t->w->isdir) + maxt = min(TABDIR, maxtab); + else + maxt = t->tabstop; + } + t->maxtab = maxt*stringwidth(f, "0"); + if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){ + if(t->maxlines > 0){ + textreset(t); + textcolumnate(t, t->w->dlp, t->w->ndl); + textshow(t, 0, 0, 1); + } + }else{ + textfill(t); + textsetselect(t, t->q0, t->q1); + } +} + +int +textresize(Text *t, Rectangle r) +{ + int odx; + + if(Dy(r) > 0) + r.max.y -= Dy(r)%t->font->height; + else + r.max.y = r.min.y; + odx = Dx(t->all); + t->all = r; + t->scrollr = r; + t->scrollr.max.x = r.min.x+Scrollwid; + t->lastsr = nullrect; + r.min.x += Scrollwid+Scrollgap; + frclear(t, 0); + textredraw(t, r, t->font, t->b, odx); + return r.max.y; +} + +void +textclose(Text *t) +{ + free(t->cache); + frclear(t, 1); + filedeltext(t->file, t); + t->file = nil; + rfclose(t->reffont); + if(argtext == t) + argtext = nil; + if(typetext == t) + typetext = nil; + if(seltext == t) + seltext = nil; + if(mousetext == t) + mousetext = nil; + if(barttext == t) + barttext = nil; +} + +int +dircmp(void *a, void *b) +{ + Dirlist *da, *db; + int i, n; + + da = *(Dirlist**)a; + db = *(Dirlist**)b; + n = min(da->nr, db->nr); + i = memcmp(da->r, db->r, n*sizeof(Rune)); + if(i) + return i; + return da->nr - db->nr; +} + +void +textcolumnate(Text *t, Dirlist **dlp, int ndl) +{ + int i, j, w, colw, mint, maxt, ncol, nrow; + Dirlist *dl; + uint q1; + + if(t->file->ntext > 1) + return; + mint = stringwidth(t->font, "0"); + /* go for narrower tabs if set more than 3 wide */ + t->maxtab = min(maxtab, TABDIR)*mint; + maxt = t->maxtab; + colw = 0; + for(i=0; i<ndl; i++){ + dl = dlp[i]; + w = dl->wid; + if(maxt-w%maxt < mint || w%maxt==0) + w += mint; + if(w % maxt) + w += maxt-(w%maxt); + if(w > colw) + colw = w; + } + if(colw == 0) + ncol = 1; + else + ncol = max(1, Dx(t->r)/colw); + nrow = (ndl+ncol-1)/ncol; + + q1 = 0; + for(i=0; i<nrow; i++){ + for(j=i; j<ndl; j+=nrow){ + dl = dlp[j]; + fileinsert(t->file, q1, dl->r, dl->nr); + q1 += dl->nr; + if(j+nrow >= ndl) + break; + w = dl->wid; + if(maxt-w%maxt < mint){ + fileinsert(t->file, q1, L"\t", 1); + q1++; + w += mint; + } + do{ + fileinsert(t->file, q1, L"\t", 1); + q1++; + w += maxt-(w%maxt); + }while(w < colw); + } + fileinsert(t->file, q1, L"\n", 1); + q1++; + } +} + +uint +textload(Text *t, uint q0, char *file, int setqid) +{ + Rune *rp; + Dirlist *dl, **dlp; + int fd, i, j, n, ndl, nulls; + uint q, q1; + Dir *d, *dbuf; + char *tmp; + Text *u; + + if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body) + error("text.load"); + if(t->w->isdir && t->file->nname==0){ + warning(nil, "empty directory name\n"); + return 0; + } + fd = open(file, OREAD); + if(fd < 0){ + warning(nil, "can't open %s: %r\n", file); + return 0; + } + d = dirfstat(fd); + if(d == nil){ + warning(nil, "can't fstat %s: %r\n", file); + goto Rescue; + } + nulls = FALSE; + if(d->qid.type & QTDIR){ + /* this is checked in get() but it's possible the file changed underfoot */ + if(t->file->ntext > 1){ + warning(nil, "%s is a directory; can't read with multiple windows on it\n", file); + goto Rescue; + } + t->w->isdir = TRUE; + t->w->filemenu = FALSE; + if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){ + rp = runemalloc(t->file->nname+1); + runemove(rp, t->file->name, t->file->nname); + rp[t->file->nname] = '/'; + winsetname(t->w, rp, t->file->nname+1); + free(rp); + } + dlp = nil; + ndl = 0; + dbuf = nil; + while((n=dirread(fd, &dbuf)) > 0){ + for(i=0; i<n; i++){ + dl = emalloc(sizeof(Dirlist)); + j = strlen(dbuf[i].name); + tmp = emalloc(j+1+1); + memmove(tmp, dbuf[i].name, j); + if(dbuf[i].qid.type & QTDIR) + tmp[j++] = '/'; + tmp[j] = '\0'; + dl->r = bytetorune(tmp, &dl->nr); + dl->wid = stringwidth(t->font, tmp); + free(tmp); + ndl++; + dlp = realloc(dlp, ndl*sizeof(Dirlist*)); + dlp[ndl-1] = dl; + } + free(dbuf); + } + qsort(dlp, ndl, sizeof(Dirlist*), dircmp); + t->w->dlp = dlp; + t->w->ndl = ndl; + textcolumnate(t, dlp, ndl); + q1 = t->file->nc; + }else{ + t->w->isdir = FALSE; + t->w->filemenu = TRUE; + q1 = q0 + fileload(t->file, q0, fd, &nulls); + } + if(setqid){ + t->file->dev = d->dev; + t->file->mtime = d->mtime; + t->file->qidpath = d->qid.path; + } + close(fd); + rp = fbufalloc(); + for(q=q0; q<q1; q+=n){ + n = q1-q; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(t->file, q, rp, n); + if(q < t->org) + t->org += n; + else if(q <= t->org+t->nchars) + frinsert(t, rp, rp+n, q-t->org); + if(t->lastlinefull) + break; + } + fbuffree(rp); + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + if(u->org > u->file->nc) /* will be 0 because of reset(), but safety first */ + u->org = 0; + textresize(u, u->all); + textbacknl(u, u->org, 0); /* go to beginning of line */ + } + textsetselect(u, q0, q0); + } + if(nulls) + warning(nil, "%s: NUL bytes elided\n", file); + free(d); + return q1-q0; + + Rescue: + close(fd); + return 0; +} + +uint +textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp) +{ + Rune *bp, *tp, *up; + int i, initial; + + if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */ + Err: + textinsert(t, q0, r, n, tofile); + *nrp = n; + return q0; + } + bp = r; + for(i=0; i<n; i++) + if(*bp++ == '\b'){ + --bp; + initial = 0; + tp = runemalloc(n); + runemove(tp, r, i); + up = tp+i; + for(; i<n; i++){ + *up = *bp++; + if(*up == '\b') + if(up == tp) + initial++; + else + --up; + else + up++; + } + if(initial){ + if(initial > q0) + initial = q0; + q0 -= initial; + textdelete(t, q0, q0+initial, tofile); + } + n = up-tp; + textinsert(t, q0, tp, n, tofile); + free(tp); + *nrp = n; + return q0; + } + goto Err; +} + +void +textinsert(Text *t, uint q0, Rune *r, uint n, int tofile) +{ + int c, i; + Text *u; + + if(tofile && t->ncache != 0) + error("text.insert"); + if(n == 0) + return; + if(tofile){ + fileinsert(t->file, q0, r, n); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + if(t->file->ntext > 1) + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + u->w->dirty = TRUE; /* always a body */ + textinsert(u, q0, r, n, FALSE); + textsetselect(u, u->q0, u->q1); + textscrdraw(u); + } + } + + } + if(q0 < t->q1) + t->q1 += n; + if(q0 < t->q0) + t->q0 += n; + if(q0 < t->org) + t->org += n; + else if(q0 <= t->org+t->nchars) + frinsert(t, r, r+n, q0-t->org); + if(t->w){ + c = 'i'; + if(t->what == Body) + c = 'I'; + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r); + else + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n); + } +} + +void +typecommit(Text *t) +{ + if(t->w != nil) + wincommit(t->w, t); + else + textcommit(t, TRUE); +} + +void +textfill(Text *t) +{ + Rune *rp; + int i, n, m, nl; + + if(t->lastlinefull || t->nofill) + return; + if(t->ncache > 0) + typecommit(t); + rp = fbufalloc(); + do{ + n = t->file->nc-(t->org+t->nchars); + if(n == 0) + break; + if(n > 2000) /* educated guess at reasonable amount */ + n = 2000; + bufread(t->file, t->org+t->nchars, rp, n); + /* + * it's expensive to frinsert more than we need, so + * count newlines. + */ + nl = t->maxlines-t->nlines; + m = 0; + for(i=0; i<n; ){ + if(rp[i++] == '\n'){ + m++; + if(m >= nl) + break; + } + } + frinsert(t, rp, rp+i, t->nchars); + }while(t->lastlinefull == FALSE); + fbuffree(rp); +} + +void +textdelete(Text *t, uint q0, uint q1, int tofile) +{ + uint n, p0, p1; + int i, c; + Text *u; + + if(tofile && t->ncache != 0) + error("text.delete"); + n = q1-q0; + if(n == 0) + return; + if(tofile){ + filedelete(t->file, q0, q1); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + if(t->file->ntext > 1) + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + u->w->dirty = TRUE; /* always a body */ + textdelete(u, q0, q1, FALSE); + textsetselect(u, u->q0, u->q1); + textscrdraw(u); + } + } + } + if(q0 < t->q0) + t->q0 -= min(n, t->q0-q0); + if(q0 < t->q1) + t->q1 -= min(n, t->q1-q0); + if(q1 <= t->org) + t->org -= n; + else if(q0 < t->org+t->nchars){ + p1 = q1 - t->org; + if(p1 > t->nchars) + p1 = t->nchars; + if(q0 < t->org){ + t->org = q0; + p0 = 0; + }else + p0 = q0 - t->org; + frdelete(t, p0, p1); + textfill(t); + } + if(t->w){ + c = 'd'; + if(t->what == Body) + c = 'D'; + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1); + } +} + +void +textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1) +{ + *p0 = min(q0, t->file->nc); + *p1 = min(q1, t->file->nc); +} + +Rune +textreadc(Text *t, uint q) +{ + Rune r; + + if(t->cq0<=q && q<t->cq0+t->ncache) + r = t->cache[q-t->cq0]; + else + bufread(t->file, q, &r, 1); + return r; +} + +static int +spacesindentbswidth(Text *t) +{ + uint q, col; + Rune r; + + col = textbswidth(t, 0x15); + q = t->q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r != ' ') + break; + q--; + if(--col % t->tabstop == 0) + break; + } + if(t->q0 == q) + return 1; + return t->q0-q; +} + +int +textbswidth(Text *t, Rune c) +{ + uint q, eq; + Rune r; + int skipping; + + /* there is known to be at least one character to erase */ + if(c == 0x08){ /* ^H: erase character */ + if(t->what == Body && t->w->indent[SPACESINDENT]) + return spacesindentbswidth(t); + return 1; + } + q = t->q0; + skipping = TRUE; + while(q > 0){ + r = textreadc(t, q-1); + if(r == '\n'){ /* eat at most one more character */ + if(q == t->q0) /* eat the newline */ + --q; + break; + } + if(c == 0x17){ + eq = isalnum(r); + if(eq && skipping) /* found one; stop skipping */ + skipping = FALSE; + else if(!eq && !skipping) + break; + } + --q; + } + return t->q0-q; +} + +int +textfilewidth(Text *t, uint q0, int oneelement) +{ + uint q; + Rune r; + + q = q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r <= ' ') + break; + if(oneelement && r=='/') + break; + --q; + } + return q0-q; +} + +Rune* +textcomplete(Text *t) +{ + int i, nstr, npath; + uint q; + Rune tmp[200]; + Rune *str, *path; + Rune *rp; + Completion *c; + char *s, *dirs; + Runestr dir; + + /* control-f: filename completion; works back to white space or / */ + if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */ + return nil; + nstr = textfilewidth(t, t->q0, TRUE); + str = runemalloc(nstr); + npath = textfilewidth(t, t->q0-nstr, FALSE); + path = runemalloc(npath); + + c = nil; + rp = nil; + dirs = nil; + + q = t->q0-nstr; + for(i=0; i<nstr; i++) + str[i] = textreadc(t, q++); + q = t->q0-nstr-npath; + for(i=0; i<npath; i++) + path[i] = textreadc(t, q++); + /* is path rooted? if not, we need to make it relative to window path */ + if(npath>0 && path[0]=='/') + dir = (Runestr){path, npath}; + else{ + dir = dirname(t, nil, 0); + if(dir.nr + 1 + npath > nelem(tmp)){ + free(dir.r); + goto Return; + } + if(dir.nr == 0){ + dir.nr = 1; + dir.r = runestrdup(L"."); + } + runemove(tmp, dir.r, dir.nr); + tmp[dir.nr] = '/'; + runemove(tmp+dir.nr+1, path, npath); + free(dir.r); + dir.r = tmp; + dir.nr += 1+npath; + dir = cleanrname(dir); + } + + s = smprint("%.*S", nstr, str); + dirs = smprint("%.*S", dir.nr, dir.r); + c = complete(dirs, s); + free(s); + if(c == nil){ + warning(nil, "error attempting completion: %r\n"); + goto Return; + } + + if(!c->advance){ + warning(nil, "%.*S%s%.*S*%s\n", + dir.nr, dir.r, + dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "", + nstr, str, + c->nmatch? "" : ": no matches in:"); + for(i=0; i<c->nfile; i++) + warning(nil, " %s\n", c->filename[i]); + } + + if(c->advance) + rp = runesmprint("%s", c->string); + else + rp = nil; + Return: + freecompletion(c); + free(dirs); + free(str); + free(path); + return rp; +} + +void +texttype(Text *t, Rune r) +{ + uint q0, q1; + int nnb, nb, n, i; + int nr; + Rune *rp; + Text *u; + + nr = 1; + rp = &r; + switch(r){ + case Kleft: + typecommit(t); + if(t->q0 > 0) + textshow(t, t->q0-1, t->q0-1, TRUE); + return; + case Kright: + typecommit(t); + if(t->q1 < t->file->nc) + textshow(t, t->q1+1, t->q1+1, TRUE); + return; + case Kdown: + n = t->maxlines/3; + goto case_Down; + case Kscrollonedown: + n = mousescrollsize(t->maxlines); + if(n <= 0) + n = 1; + goto case_Down; + case Kpgdown: + n = 2*t->maxlines/3; + case_Down: + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height)); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Kup: + n = t->maxlines/3; + goto case_Up; + case Kscrolloneup: + n = mousescrollsize(t->maxlines); + goto case_Up; + case Kpgup: + n = 2*t->maxlines/3; + case_Up: + q0 = textbacknl(t, t->org, n); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Khome: + typecommit(t); + textshow(t, 0, 0, FALSE); + return; + case Kend: + typecommit(t); + textshow(t, t->file->nc, t->file->nc, FALSE); + return; + case 0x01: /* ^A: beginning of line */ + typecommit(t); + /* go to where ^U would erase, if not already at BOL */ + nnb = 0; + if(t->q0>0 && textreadc(t, t->q0-1)!='\n') + nnb = textbswidth(t, 0x15); + textshow(t, t->q0-nnb, t->q0-nnb, TRUE); + return; + case 0x05: /* ^E: end of line */ + typecommit(t); + q0 = t->q0; + while(q0<t->file->nc && textreadc(t, q0)!='\n') + q0++; + textshow(t, q0, q0, TRUE); + return; + } + if(t->what == Body){ + seq++; + filemark(t->file); + } + if(t->q1 > t->q0){ + if(t->ncache != 0) + error("text.type"); + cut(t, t, nil, TRUE, TRUE, nil, 0); + t->eq0 = ~0; + } + textshow(t, t->q0, t->q0, 1); + switch(r){ + case 0x06: + case Kins: + rp = textcomplete(t); + if(rp == nil) + return; + nr = runestrlen(rp); + break; /* fall through to normal insertion case */ + case 0x1B: + if(t->eq0 != ~0) + textsetselect(t, t->eq0, t->q0); + if(t->ncache > 0) + typecommit(t); + return; + case 0x08: /* ^H: erase character */ + case 0x15: /* ^U: erase line */ + case 0x17: /* ^W: erase word */ + if(t->q0 == 0) /* nothing to erase */ + return; + nnb = textbswidth(t, r); + q1 = t->q0; + q0 = q1-nnb; + /* if selection is at beginning of window, avoid deleting invisible text */ + if(q0 < t->org){ + q0 = t->org; + nnb = q1-q0; + } + if(nnb <= 0) + return; + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + u->nofill = TRUE; + nb = nnb; + n = u->ncache; + if(n > 0){ + if(q1 != u->cq0+n) + error("text.type backspace"); + if(n > nb) + n = nb; + u->ncache -= n; + textdelete(u, q1-n, q1, FALSE); + nb -= n; + } + if(u->eq0==q1 || u->eq0==~0) + u->eq0 = q0; + if(nb && u==t) + textdelete(u, q0, q0+nb, TRUE); + if(u != t) + textsetselect(u, u->q0, u->q1); + else + textsetselect(t, q0, q0); + u->nofill = FALSE; + } + for(i=0; i<t->file->ntext; i++) + textfill(t->file->text[i]); + return; + case '\t': + if(t->what == Body && t->w->indent[SPACESINDENT]){ + nnb = textbswidth(t, 0x15); + if(nnb == 1 && textreadc(t, t->q0-1) == '\n') + nnb = 0; + nnb = t->tabstop - nnb % t->tabstop; + rp = runemalloc(nnb); + for(nr = 0; nr < nnb; nr++) + rp[nr] = ' '; + } + break; + case '\n': + if(t->what == Body && t->w->indent[AUTOINDENT]){ + /* find beginning of previous line using backspace code */ + nnb = textbswidth(t, 0x15); /* ^U case */ + rp = runemalloc(nnb + 1); + nr = 0; + rp[nr++] = r; + for(i=0; i<nnb; i++){ + r = textreadc(t, t->q0-nnb+i); + if(r != ' ' && r != '\t') + break; + rp[nr++] = r; + } + } + break; /* fall through to normal code */ + } + /* otherwise ordinary character; just insert, typically in caches of all texts */ + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u->eq0 == ~0) + u->eq0 = t->q0; + if(u->ncache == 0) + u->cq0 = t->q0; + else if(t->q0 != u->cq0+u->ncache) + error("text.type cq1"); + textinsert(u, t->q0, rp, nr, FALSE); + if(u != t) + textsetselect(u, u->q0, u->q1); + if(u->ncache+nr > u->ncachealloc){ + u->ncachealloc += 10 + nr; + u->cache = runerealloc(u->cache, u->ncachealloc); + } + runemove(u->cache+u->ncache, rp, nr); + u->ncache += nr; + } + if(rp != &r) + free(rp); + textsetselect(t, t->q0+nr, t->q0+nr); + if(r=='\n' && t->w!=nil) + wincommit(t->w, t); +} + +void +textcommit(Text *t, int tofile) +{ + if(t->ncache == 0) + return; + if(tofile) + fileinsert(t->file, t->cq0, t->cache, t->ncache); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + t->ncache = 0; +} + +static Text *clicktext; +static uint clickmsec; +static int clickcount; +static Point clickpt; +static Text *selecttext; +static uint selectq; + +/* + * called from frame library + */ +void +framescroll(Frame *f, int dl) +{ + if(f != &selecttext->Frame) + error("frameselect not right frame"); + textframescroll(selecttext, dl); +} + +void +textframescroll(Text *t, int dl) +{ + uint q0; + + if(dl == 0){ + scrsleep(100); + return; + } + if(dl < 0){ + q0 = textbacknl(t, t->org, -dl); + if(selectq > t->org+t->p0) + textsetselect(t, t->org+t->p0, selectq); + else + textsetselect(t, selectq, t->org+t->p0); + }else{ + if(t->org+t->nchars == t->file->nc) + return; + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height)); + if(selectq > t->org+t->p1) + textsetselect(t, t->org+t->p1, selectq); + else + textsetselect(t, selectq, t->org+t->p1); + } + textsetorigin(t, q0, TRUE); + flushimage(display, 1); +} + + +void +textselect(Text *t) +{ + uint q0, q1; + int b, x, y, dx, dy; + int state; + + selecttext = t; + /* + * To have double-clicking and chording, we double-click + * immediately if it might make sense. + */ + b = mouse->buttons; + q0 = t->q0; + q1 = t->q1; + dx = abs(clickpt.x - mouse->xy.x); + dy = abs(clickpt.y - mouse->xy.y); + clickpt = mouse->xy; + selectq = t->org+frcharofpt(t, mouse->xy); + clickcount++; + if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3) + clickcount = 0; + if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){ + textstretchsel(t, selectq, &q0, &q1, clickcount); + textsetselect(t, q0, q1); + flushimage(display, 1); + x = mouse->xy.x; + y = mouse->xy.y; + /* stay here until something interesting happens */ + while(1){ + readmouse(mousectl); + dx = abs(mouse->xy.x - x); + dy = abs(mouse->xy.y - y); + if(mouse->buttons != b || dx >= 3 || dy >= 3) + break; + clickcount++; + clickmsec = mouse->msec; + } + mouse->xy.x = x; /* in case we're calling frselect */ + mouse->xy.y = y; + q0 = t->q0; /* may have changed */ + q1 = t->q1; + selectq = t->org+frcharofpt(t, mouse->xy);; + } + if(mouse->buttons == b && clickcount == 0){ + t->Frame.scroll = framescroll; + frselect(t, mousectl); + /* horrible botch: while asleep, may have lost selection altogether */ + if(selectq > t->file->nc) + selectq = t->org + t->p0; + t->Frame.scroll = nil; + if(selectq < t->org) + q0 = selectq; + else + q0 = t->org + t->p0; + if(selectq > t->org+t->nchars) + q1 = selectq; + else + q1 = t->org+t->p1; + } + if(q0 == q1){ + if(q0==t->q0 && mouse->msec-clickmsec<500) + textstretchsel(t, selectq, &q0, &q1, clickcount); + else + clicktext = t; + clickmsec = mouse->msec; + }else + clicktext = nil; + textsetselect(t, q0, q1); + flushimage(display, 1); + state = 0; /* undo when possible; +1 for cut, -1 for paste */ + while(mouse->buttons){ + mouse->msec = 0; + b = mouse->buttons; + if((b&1) && (b&6)){ + if(state==0 && t->what==Body){ + seq++; + filemark(t->w->body.file); + } + if(b & 2){ + if(state==-1 && t->what==Body){ + winundo(t->w, TRUE); + textsetselect(t, q0, t->q0); + state = 0; + }else if(state != 1){ + cut(t, t, nil, TRUE, TRUE, nil, 0); + state = 1; + } + }else{ + if(state==1 && t->what==Body){ + winundo(t->w, TRUE); + textsetselect(t, q0, t->q1); + state = 0; + }else if(state != -1){ + paste(t, t, nil, TRUE, FALSE, nil, 0); + state = -1; + } + } + textscrdraw(t); + clearmouse(); + } + flushimage(display, 1); + while(mouse->buttons == b) + readmouse(mousectl); + if(mouse->msec-clickmsec >= 500) + clicktext = nil; + } +} + +void +textshow(Text *t, uint q0, uint q1, int doselect) +{ + int qe; + int nl; + uint q; + + if(t->what != Body){ + if(doselect) + textsetselect(t, q0, q1); + return; + } + if(t->w!=nil && t->maxlines==0) + colgrow(t->col, t->w, 1); + if(doselect) + textsetselect(t, q0, q1); + qe = t->org+t->nchars; + if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache))) + textscrdraw(t); + else{ + if(t->w->nopen[QWevent] > 0) + nl = 3*t->maxlines/4; + else + nl = t->maxlines/4; + q = textbacknl(t, q0, nl); + /* avoid going backwards if trying to go forwards - long lines! */ + if(!(q0>t->org && q<t->org)) + textsetorigin(t, q, TRUE); + while(q0 > t->org+t->nchars) + textsetorigin(t, t->org+1, FALSE); + } +} + +static +int +region(int a, int b) +{ + if(a < b) + return -1; + if(a == b) + return 0; + return 1; +} + +void +selrestore(Frame *f, Point pt0, uint p0, uint p1) +{ + if(p1<=f->p0 || p0>=f->p1){ + /* no overlap */ + frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]); + return; + } + if(p0>=f->p0 && p1<=f->p1){ + /* entirely inside */ + frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); + return; + } + + /* they now are known to overlap */ + + /* before selection */ + if(p0 < f->p0){ + frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]); + p0 = f->p0; + pt0 = frptofchar(f, p0); + } + /* after selection */ + if(p1 > f->p1){ + frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]); + p1 = f->p1; + } + /* inside selection */ + frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); +} + +void +textsetselect(Text *t, uint q0, uint q1) +{ + int p0, p1; + + /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */ + t->q0 = q0; + t->q1 = q1; + /* compute desired p0,p1 from q0,q1 */ + p0 = q0-t->org; + p1 = q1-t->org; + if(p0 < 0) + p0 = 0; + if(p1 < 0) + p1 = 0; + if(p0 > t->nchars) + p0 = t->nchars; + if(p1 > t->nchars) + p1 = t->nchars; + if(p0==t->p0 && p1==t->p1) + return; + /* screen disagrees with desired selection */ + if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){ + /* no overlap or too easy to bother trying */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0); + frdrawsel(t, frptofchar(t, p0), p0, p1, 1); + goto Return; + } + /* overlap; avoid unnecessary painting */ + if(p0 < t->p0){ + /* extend selection backwards */ + frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1); + }else if(p0 > t->p0){ + /* trim first part of selection */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0); + } + if(p1 > t->p1){ + /* extend selection forwards */ + frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1); + }else if(p1 < t->p1){ + /* trim last part of selection */ + frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0); + } + + Return: + t->p0 = p0; + t->p1 = p1; +} + +/* + * Release the button in less than DELAY ms and it's considered a null selection + * if the mouse hardly moved, regardless of whether it crossed a char boundary. + */ +enum { + DELAY = 2, + MINMOVE = 4, +}; + +uint +xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */ +{ + uint p0, p1, q, tmp; + ulong msec; + Point mp, pt0, pt1, qt; + int reg, b; + + mp = mc->xy; + b = mc->buttons; + msec = mc->msec; + + /* remove tick */ + if(f->p0 == f->p1) + frtick(f, frptofchar(f, f->p0), 0); + p0 = p1 = frcharofpt(f, mp); + pt0 = frptofchar(f, p0); + pt1 = frptofchar(f, p1); + reg = 0; + frtick(f, pt0, 1); + do{ + q = frcharofpt(f, mc->xy); + if(p1 != q){ + if(p0 == p1) + frtick(f, pt0, 0); + if(reg != region(q, p0)){ /* crossed starting point; reset */ + if(reg > 0) + selrestore(f, pt0, p0, p1); + else if(reg < 0) + selrestore(f, pt1, p1, p0); + p1 = p0; + pt1 = pt0; + reg = region(q, p0); + if(reg == 0) + frdrawsel0(f, pt0, p0, p1, col, display->white); + } + qt = frptofchar(f, q); + if(reg > 0){ + if(q > p1) + frdrawsel0(f, pt1, p1, q, col, display->white); + + else if(q < p1) + selrestore(f, qt, q, p1); + }else if(reg < 0){ + if(q > p1) + selrestore(f, pt1, p1, q); + else + frdrawsel0(f, qt, q, p1, col, display->white); + } + p1 = q; + pt1 = qt; + } + if(p0 == p1) + frtick(f, pt0, 1); + flushimage(f->display, 1); + readmouse(mc); + }while(mc->buttons == b); + if(mc->msec-msec < DELAY && p0!=p1 + && abs(mp.x-mc->xy.x)<MINMOVE + && abs(mp.y-mc->xy.y)<MINMOVE) { + if(reg > 0) + selrestore(f, pt0, p0, p1); + else if(reg < 0) + selrestore(f, pt1, p1, p0); + p1 = p0; + } + if(p1 < p0){ + tmp = p0; + p0 = p1; + p1 = tmp; + } + pt0 = frptofchar(f, p0); + if(p0 == p1) + frtick(f, pt0, 0); + selrestore(f, pt0, p0, p1); + /* restore tick */ + if(f->p0 == f->p1) + frtick(f, frptofchar(f, f->p0), 1); + flushimage(f->display, 1); + *p1p = p1; + return p0; +} + +int +textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask) +{ + uint p0, p1; + int buts; + + p0 = xselect(t, mousectl, high, &p1); + buts = mousectl->buttons; + if((buts & mask) == 0){ + *q0 = p0+t->org; + *q1 = p1+t->org; + } + + while(mousectl->buttons) + readmouse(mousectl); + return buts; +} + +int +textselect2(Text *t, uint *q0, uint *q1, Text **tp) +{ + int buts; + + *tp = nil; + buts = textselect23(t, q0, q1, but2col, 4); + if(buts & 4) + return 0; + if(buts & 1){ /* pick up argument */ + *tp = argtext; + return 1; + } + return 1; +} + +int +textselect3(Text *t, uint *q0, uint *q1) +{ + int h; + + h = (textselect23(t, q0, q1, but3col, 1|2) == 0); + return h; +} + +static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 }; +static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; +static Rune left2[] = { L'\n', 0 }; +static Rune left3[] = { L'\'', L'"', L'`', 0 }; + +static +Rune *left[] = { + left1, + left2, + left3, + nil +}; +static +Rune *right[] = { + right1, + left2, + left3, + nil +}; + +int +inmode(Rune r, int mode) +{ + return (mode == 1) ? isalnum(r) : r && !isspace(r); +} + +void +textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode) +{ + int c, i; + Rune *r, *l, *p; + uint q; + + *q0 = mp; + *q1 = mp; + for(i=0; left[i]!=nil; i++){ + q = *q0; + l = left[i]; + r = right[i]; + /* try matching character to left, looking right */ + if(q == 0) + c = '\n'; + else + c = textreadc(t, q-1); + p = runestrchr(l, c); + if(p != nil){ + if(textclickmatch(t, c, r[p-l], 1, &q)) + *q1 = q-(c!='\n'); + return; + } + /* try matching character to right, looking left */ + if(q == t->file->nc) + c = '\n'; + else + c = textreadc(t, q); + p = runestrchr(r, c); + if(p != nil){ + if(textclickmatch(t, c, l[p-r], -1, &q)){ + *q1 = *q0+(*q0<t->file->nc && c=='\n'); + *q0 = q; + if(c!='\n' || q!=0 || textreadc(t, 0)=='\n') + (*q0)++; + } + return; + } + } + /* try filling out word to right */ + while(*q1<t->file->nc && inmode(textreadc(t, *q1), mode)) + (*q1)++; + /* try filling out word to left */ + while(*q0>0 && inmode(textreadc(t, *q0-1), mode)) + (*q0)--; +} + +int +textclickmatch(Text *t, int cl, int cr, int dir, uint *q) +{ + Rune c; + int nest; + + nest = 1; + for(;;){ + if(dir > 0){ + if(*q == t->file->nc) + break; + c = textreadc(t, *q); + (*q)++; + }else{ + if(*q == 0) + break; + (*q)--; + c = textreadc(t, *q); + } + if(c == cr){ + if(--nest==0) + return 1; + }else if(c == cl) + nest++; + } + return cl=='\n' && nest==1; +} + +uint +textbacknl(Text *t, uint p, uint n) +{ + int i, j; + + /* look for start of this line if n==0 */ + if(n==0 && p>0 && textreadc(t, p-1)!='\n') + n = 1; + i = n; + while(i-->0 && p>0){ + --p; /* it's at a newline now; back over it */ + if(p == 0) + break; + /* at 128 chars, call it a line anyway */ + for(j=128; --j>0 && p>0; p--) + if(textreadc(t, p-1)=='\n') + break; + } + return p; +} + +void +textsetorigin(Text *t, uint org, int exact) +{ + int i, a, fixup; + Rune *r; + uint n; + + if(org>0 && !exact && textreadc(t, org-1) != '\n'){ + /* org is an estimate of the char posn; find a newline */ + /* don't try harder than 256 chars */ + for(i=0; i<256 && org<t->file->nc; i++){ + if(textreadc(t, org) == '\n'){ + org++; + break; + } + org++; + } + } + a = org-t->org; + fixup = 0; + if(a>=0 && a<t->nchars){ + frdelete(t, 0, a); + fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ + } + else if(a<0 && -a<t->nchars){ + n = t->org - org; + r = runemalloc(n); + bufread(t->file, org, r, n); + frinsert(t, r, r+n, 0); + free(r); + }else + frdelete(t, 0, t->nchars); + t->org = org; + textfill(t); + textscrdraw(t); + textsetselect(t, t->q0, t->q1); + if(fixup && t->p1 > t->p0) + frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1); +} + +void +textreset(Text *t) +{ + t->file->seq = 0; + t->eq0 = ~0; + /* do t->delete(0, t->nc, TRUE) without building backup stuff */ + textsetselect(t, t->org, t->org); + frdelete(t, 0, t->nchars); + t->org = 0; + t->q0 = 0; + t->q1 = 0; + filereset(t->file); + bufreset(t->file); +} diff --git a/patch/acme-moveto-undo/text.c.orig b/patch/acme-moveto-undo/text.c.orig new file mode 100644 index 0000000..484ec3d --- /dev/null +++ b/patch/acme-moveto-undo/text.c.orig @@ -0,0 +1,1460 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <thread.h> +#include <cursor.h> +#include <mouse.h> +#include <keyboard.h> +#include <frame.h> +#include <fcall.h> +#include <plumb.h> +#include <complete.h> +#include "dat.h" +#include "fns.h" + +Image *tagcols[NCOL]; +Image *textcols[NCOL]; + +enum{ + TABDIR = 3 /* width of tabs in directory windows */ +}; + +void +textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL]) +{ + t->file = f; + t->all = r; + t->scrollr = r; + t->scrollr.max.x = r.min.x+Scrollwid; + t->lastsr = nullrect; + r.min.x += Scrollwid+Scrollgap; + t->eq0 = ~0; + t->ncache = 0; + t->reffont = rf; + t->tabstop = maxtab; + memmove(t->Frame.cols, cols, sizeof t->Frame.cols); + textredraw(t, r, rf->f, screen, -1); +} + +void +textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx) +{ + int maxt; + Rectangle rr; + + frinit(t, r, f, b, t->Frame.cols); + rr = t->r; + rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */ + draw(t->b, rr, t->cols[BACK], nil, ZP); + /* use no wider than 3-space tabs in a directory */ + maxt = maxtab; + if(t->what == Body){ + if(t->w->isdir) + maxt = min(TABDIR, maxtab); + else + maxt = t->tabstop; + } + t->maxtab = maxt*stringwidth(f, "0"); + if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){ + if(t->maxlines > 0){ + textreset(t); + textcolumnate(t, t->w->dlp, t->w->ndl); + textshow(t, 0, 0, 1); + } + }else{ + textfill(t); + textsetselect(t, t->q0, t->q1); + } +} + +int +textresize(Text *t, Rectangle r) +{ + int odx; + + if(Dy(r) > 0) + r.max.y -= Dy(r)%t->font->height; + else + r.max.y = r.min.y; + odx = Dx(t->all); + t->all = r; + t->scrollr = r; + t->scrollr.max.x = r.min.x+Scrollwid; + t->lastsr = nullrect; + r.min.x += Scrollwid+Scrollgap; + frclear(t, 0); + textredraw(t, r, t->font, t->b, odx); + return r.max.y; +} + +void +textclose(Text *t) +{ + free(t->cache); + frclear(t, 1); + filedeltext(t->file, t); + t->file = nil; + rfclose(t->reffont); + if(argtext == t) + argtext = nil; + if(typetext == t) + typetext = nil; + if(seltext == t) + seltext = nil; + if(mousetext == t) + mousetext = nil; + if(barttext == t) + barttext = nil; +} + +int +dircmp(void *a, void *b) +{ + Dirlist *da, *db; + int i, n; + + da = *(Dirlist**)a; + db = *(Dirlist**)b; + n = min(da->nr, db->nr); + i = memcmp(da->r, db->r, n*sizeof(Rune)); + if(i) + return i; + return da->nr - db->nr; +} + +void +textcolumnate(Text *t, Dirlist **dlp, int ndl) +{ + int i, j, w, colw, mint, maxt, ncol, nrow; + Dirlist *dl; + uint q1; + + if(t->file->ntext > 1) + return; + mint = stringwidth(t->font, "0"); + /* go for narrower tabs if set more than 3 wide */ + t->maxtab = min(maxtab, TABDIR)*mint; + maxt = t->maxtab; + colw = 0; + for(i=0; i<ndl; i++){ + dl = dlp[i]; + w = dl->wid; + if(maxt-w%maxt < mint || w%maxt==0) + w += mint; + if(w % maxt) + w += maxt-(w%maxt); + if(w > colw) + colw = w; + } + if(colw == 0) + ncol = 1; + else + ncol = max(1, Dx(t->r)/colw); + nrow = (ndl+ncol-1)/ncol; + + q1 = 0; + for(i=0; i<nrow; i++){ + for(j=i; j<ndl; j+=nrow){ + dl = dlp[j]; + fileinsert(t->file, q1, dl->r, dl->nr); + q1 += dl->nr; + if(j+nrow >= ndl) + break; + w = dl->wid; + if(maxt-w%maxt < mint){ + fileinsert(t->file, q1, L"\t", 1); + q1++; + w += mint; + } + do{ + fileinsert(t->file, q1, L"\t", 1); + q1++; + w += maxt-(w%maxt); + }while(w < colw); + } + fileinsert(t->file, q1, L"\n", 1); + q1++; + } +} + +uint +textload(Text *t, uint q0, char *file, int setqid) +{ + Rune *rp; + Dirlist *dl, **dlp; + int fd, i, j, n, ndl, nulls; + uint q, q1; + Dir *d, *dbuf; + char *tmp; + Text *u; + + if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body) + error("text.load"); + if(t->w->isdir && t->file->nname==0){ + warning(nil, "empty directory name\n"); + return 0; + } + fd = open(file, OREAD); + if(fd < 0){ + warning(nil, "can't open %s: %r\n", file); + return 0; + } + d = dirfstat(fd); + if(d == nil){ + warning(nil, "can't fstat %s: %r\n", file); + goto Rescue; + } + nulls = FALSE; + if(d->qid.type & QTDIR){ + /* this is checked in get() but it's possible the file changed underfoot */ + if(t->file->ntext > 1){ + warning(nil, "%s is a directory; can't read with multiple windows on it\n", file); + goto Rescue; + } + t->w->isdir = TRUE; + t->w->filemenu = FALSE; + if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){ + rp = runemalloc(t->file->nname+1); + runemove(rp, t->file->name, t->file->nname); + rp[t->file->nname] = '/'; + winsetname(t->w, rp, t->file->nname+1); + free(rp); + } + dlp = nil; + ndl = 0; + dbuf = nil; + while((n=dirread(fd, &dbuf)) > 0){ + for(i=0; i<n; i++){ + dl = emalloc(sizeof(Dirlist)); + j = strlen(dbuf[i].name); + tmp = emalloc(j+1+1); + memmove(tmp, dbuf[i].name, j); + if(dbuf[i].qid.type & QTDIR) + tmp[j++] = '/'; + tmp[j] = '\0'; + dl->r = bytetorune(tmp, &dl->nr); + dl->wid = stringwidth(t->font, tmp); + free(tmp); + ndl++; + dlp = realloc(dlp, ndl*sizeof(Dirlist*)); + dlp[ndl-1] = dl; + } + free(dbuf); + } + qsort(dlp, ndl, sizeof(Dirlist*), dircmp); + t->w->dlp = dlp; + t->w->ndl = ndl; + textcolumnate(t, dlp, ndl); + q1 = t->file->nc; + }else{ + t->w->isdir = FALSE; + t->w->filemenu = TRUE; + q1 = q0 + fileload(t->file, q0, fd, &nulls); + } + if(setqid){ + t->file->dev = d->dev; + t->file->mtime = d->mtime; + t->file->qidpath = d->qid.path; + } + close(fd); + rp = fbufalloc(); + for(q=q0; q<q1; q+=n){ + n = q1-q; + if(n > RBUFSIZE) + n = RBUFSIZE; + bufread(t->file, q, rp, n); + if(q < t->org) + t->org += n; + else if(q <= t->org+t->nchars) + frinsert(t, rp, rp+n, q-t->org); + if(t->lastlinefull) + break; + } + fbuffree(rp); + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + if(u->org > u->file->nc) /* will be 0 because of reset(), but safety first */ + u->org = 0; + textresize(u, u->all); + textbacknl(u, u->org, 0); /* go to beginning of line */ + } + textsetselect(u, q0, q0); + } + if(nulls) + warning(nil, "%s: NUL bytes elided\n", file); + free(d); + return q1-q0; + + Rescue: + close(fd); + return 0; +} + +uint +textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp) +{ + Rune *bp, *tp, *up; + int i, initial; + + if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */ + Err: + textinsert(t, q0, r, n, tofile); + *nrp = n; + return q0; + } + bp = r; + for(i=0; i<n; i++) + if(*bp++ == '\b'){ + --bp; + initial = 0; + tp = runemalloc(n); + runemove(tp, r, i); + up = tp+i; + for(; i<n; i++){ + *up = *bp++; + if(*up == '\b') + if(up == tp) + initial++; + else + --up; + else + up++; + } + if(initial){ + if(initial > q0) + initial = q0; + q0 -= initial; + textdelete(t, q0, q0+initial, tofile); + } + n = up-tp; + textinsert(t, q0, tp, n, tofile); + free(tp); + *nrp = n; + return q0; + } + goto Err; +} + +void +textinsert(Text *t, uint q0, Rune *r, uint n, int tofile) +{ + int c, i; + Text *u; + + if(tofile && t->ncache != 0) + error("text.insert"); + if(n == 0) + return; + if(tofile){ + fileinsert(t->file, q0, r, n); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + if(t->file->ntext > 1) + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + u->w->dirty = TRUE; /* always a body */ + textinsert(u, q0, r, n, FALSE); + textsetselect(u, u->q0, u->q1); + textscrdraw(u); + } + } + + } + if(q0 < t->q1) + t->q1 += n; + if(q0 < t->q0) + t->q0 += n; + if(q0 < t->org) + t->org += n; + else if(q0 <= t->org+t->nchars) + frinsert(t, r, r+n, q0-t->org); + if(t->w){ + c = 'i'; + if(t->what == Body) + c = 'I'; + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r); + else + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n); + } +} + +void +typecommit(Text *t) +{ + if(t->w != nil) + wincommit(t->w, t); + else + textcommit(t, TRUE); +} + +void +textfill(Text *t) +{ + Rune *rp; + int i, n, m, nl; + + if(t->lastlinefull || t->nofill) + return; + if(t->ncache > 0) + typecommit(t); + rp = fbufalloc(); + do{ + n = t->file->nc-(t->org+t->nchars); + if(n == 0) + break; + if(n > 2000) /* educated guess at reasonable amount */ + n = 2000; + bufread(t->file, t->org+t->nchars, rp, n); + /* + * it's expensive to frinsert more than we need, so + * count newlines. + */ + nl = t->maxlines-t->nlines; + m = 0; + for(i=0; i<n; ){ + if(rp[i++] == '\n'){ + m++; + if(m >= nl) + break; + } + } + frinsert(t, rp, rp+i, t->nchars); + }while(t->lastlinefull == FALSE); + fbuffree(rp); +} + +void +textdelete(Text *t, uint q0, uint q1, int tofile) +{ + uint n, p0, p1; + int i, c; + Text *u; + + if(tofile && t->ncache != 0) + error("text.delete"); + n = q1-q0; + if(n == 0) + return; + if(tofile){ + filedelete(t->file, q0, q1); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + if(t->file->ntext > 1) + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u != t){ + u->w->dirty = TRUE; /* always a body */ + textdelete(u, q0, q1, FALSE); + textsetselect(u, u->q0, u->q1); + textscrdraw(u); + } + } + } + if(q0 < t->q0) + t->q0 -= min(n, t->q0-q0); + if(q0 < t->q1) + t->q1 -= min(n, t->q1-q0); + if(q1 <= t->org) + t->org -= n; + else if(q0 < t->org+t->nchars){ + p1 = q1 - t->org; + if(p1 > t->nchars) + p1 = t->nchars; + if(q0 < t->org){ + t->org = q0; + p0 = 0; + }else + p0 = q0 - t->org; + frdelete(t, p0, p1); + textfill(t); + } + if(t->w){ + c = 'd'; + if(t->what == Body) + c = 'D'; + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1); + } +} + +void +textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1) +{ + *p0 = min(q0, t->file->nc); + *p1 = min(q1, t->file->nc); +} + +Rune +textreadc(Text *t, uint q) +{ + Rune r; + + if(t->cq0<=q && q<t->cq0+t->ncache) + r = t->cache[q-t->cq0]; + else + bufread(t->file, q, &r, 1); + return r; +} + +static int +spacesindentbswidth(Text *t) +{ + uint q, col; + Rune r; + + col = textbswidth(t, 0x15); + q = t->q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r != ' ') + break; + q--; + if(--col % t->tabstop == 0) + break; + } + if(t->q0 == q) + return 1; + return t->q0-q; +} + +int +textbswidth(Text *t, Rune c) +{ + uint q, eq; + Rune r; + int skipping; + + /* there is known to be at least one character to erase */ + if(c == 0x08){ /* ^H: erase character */ + if(t->what == Body && t->w->indent[SPACESINDENT]) + return spacesindentbswidth(t); + return 1; + } + q = t->q0; + skipping = TRUE; + while(q > 0){ + r = textreadc(t, q-1); + if(r == '\n'){ /* eat at most one more character */ + if(q == t->q0) /* eat the newline */ + --q; + break; + } + if(c == 0x17){ + eq = isalnum(r); + if(eq && skipping) /* found one; stop skipping */ + skipping = FALSE; + else if(!eq && !skipping) + break; + } + --q; + } + return t->q0-q; +} + +int +textfilewidth(Text *t, uint q0, int oneelement) +{ + uint q; + Rune r; + + q = q0; + while(q > 0){ + r = textreadc(t, q-1); + if(r <= ' ') + break; + if(oneelement && r=='/') + break; + --q; + } + return q0-q; +} + +Rune* +textcomplete(Text *t) +{ + int i, nstr, npath; + uint q; + Rune tmp[200]; + Rune *str, *path; + Rune *rp; + Completion *c; + char *s, *dirs; + Runestr dir; + + /* control-f: filename completion; works back to white space or / */ + if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */ + return nil; + nstr = textfilewidth(t, t->q0, TRUE); + str = runemalloc(nstr); + npath = textfilewidth(t, t->q0-nstr, FALSE); + path = runemalloc(npath); + + c = nil; + rp = nil; + dirs = nil; + + q = t->q0-nstr; + for(i=0; i<nstr; i++) + str[i] = textreadc(t, q++); + q = t->q0-nstr-npath; + for(i=0; i<npath; i++) + path[i] = textreadc(t, q++); + /* is path rooted? if not, we need to make it relative to window path */ + if(npath>0 && path[0]=='/') + dir = (Runestr){path, npath}; + else{ + dir = dirname(t, nil, 0); + if(dir.nr + 1 + npath > nelem(tmp)){ + free(dir.r); + goto Return; + } + if(dir.nr == 0){ + dir.nr = 1; + dir.r = runestrdup(L"."); + } + runemove(tmp, dir.r, dir.nr); + tmp[dir.nr] = '/'; + runemove(tmp+dir.nr+1, path, npath); + free(dir.r); + dir.r = tmp; + dir.nr += 1+npath; + dir = cleanrname(dir); + } + + s = smprint("%.*S", nstr, str); + dirs = smprint("%.*S", dir.nr, dir.r); + c = complete(dirs, s); + free(s); + if(c == nil){ + warning(nil, "error attempting completion: %r\n"); + goto Return; + } + + if(!c->advance){ + warning(nil, "%.*S%s%.*S*%s\n", + dir.nr, dir.r, + dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "", + nstr, str, + c->nmatch? "" : ": no matches in:"); + for(i=0; i<c->nfile; i++) + warning(nil, " %s\n", c->filename[i]); + } + + if(c->advance) + rp = runesmprint("%s", c->string); + else + rp = nil; + Return: + freecompletion(c); + free(dirs); + free(str); + free(path); + return rp; +} + +void +texttype(Text *t, Rune r) +{ + uint q0, q1; + int nnb, nb, n, i; + int nr; + Rune *rp; + Text *u; + + nr = 1; + rp = &r; + switch(r){ + case Kleft: + typecommit(t); + if(t->q0 > 0) + textshow(t, t->q0-1, t->q0-1, TRUE); + return; + case Kright: + typecommit(t); + if(t->q1 < t->file->nc) + textshow(t, t->q1+1, t->q1+1, TRUE); + return; + case Kdown: + n = t->maxlines/3; + goto case_Down; + case Kscrollonedown: + n = mousescrollsize(t->maxlines); + if(n <= 0) + n = 1; + goto case_Down; + case Kpgdown: + n = 2*t->maxlines/3; + case_Down: + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height)); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Kup: + n = t->maxlines/3; + goto case_Up; + case Kscrolloneup: + n = mousescrollsize(t->maxlines); + goto case_Up; + case Kpgup: + n = 2*t->maxlines/3; + case_Up: + q0 = textbacknl(t, t->org, n); + if(t->what == Body) + textsetorigin(t, q0, TRUE); + return; + case Khome: + typecommit(t); + textshow(t, 0, 0, FALSE); + return; + case Kend: + typecommit(t); + textshow(t, t->file->nc, t->file->nc, FALSE); + return; + case 0x01: /* ^A: beginning of line */ + typecommit(t); + /* go to where ^U would erase, if not already at BOL */ + nnb = 0; + if(t->q0>0 && textreadc(t, t->q0-1)!='\n') + nnb = textbswidth(t, 0x15); + textshow(t, t->q0-nnb, t->q0-nnb, TRUE); + return; + case 0x05: /* ^E: end of line */ + typecommit(t); + q0 = t->q0; + while(q0<t->file->nc && textreadc(t, q0)!='\n') + q0++; + textshow(t, q0, q0, TRUE); + return; + } + if(t->what == Body){ + seq++; + filemark(t->file); + } + if(t->q1 > t->q0){ + if(t->ncache != 0) + error("text.type"); + cut(t, t, nil, TRUE, TRUE, nil, 0); + t->eq0 = ~0; + } + textshow(t, t->q0, t->q0, 1); + switch(r){ + case 0x06: + case Kins: + rp = textcomplete(t); + if(rp == nil) + return; + nr = runestrlen(rp); + break; /* fall through to normal insertion case */ + case 0x1B: + if(t->eq0 != ~0) + textsetselect(t, t->eq0, t->q0); + if(t->ncache > 0) + typecommit(t); + return; + case 0x08: /* ^H: erase character */ + case 0x15: /* ^U: erase line */ + case 0x17: /* ^W: erase word */ + if(t->q0 == 0) /* nothing to erase */ + return; + nnb = textbswidth(t, r); + q1 = t->q0; + q0 = q1-nnb; + /* if selection is at beginning of window, avoid deleting invisible text */ + if(q0 < t->org){ + q0 = t->org; + nnb = q1-q0; + } + if(nnb <= 0) + return; + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + u->nofill = TRUE; + nb = nnb; + n = u->ncache; + if(n > 0){ + if(q1 != u->cq0+n) + error("text.type backspace"); + if(n > nb) + n = nb; + u->ncache -= n; + textdelete(u, q1-n, q1, FALSE); + nb -= n; + } + if(u->eq0==q1 || u->eq0==~0) + u->eq0 = q0; + if(nb && u==t) + textdelete(u, q0, q0+nb, TRUE); + if(u != t) + textsetselect(u, u->q0, u->q1); + else + textsetselect(t, q0, q0); + u->nofill = FALSE; + } + for(i=0; i<t->file->ntext; i++) + textfill(t->file->text[i]); + return; + case '\t': + if(t->what == Body && t->w->indent[SPACESINDENT]){ + nnb = textbswidth(t, 0x15); + if(nnb == 1 && textreadc(t, t->q0-1) == '\n') + nnb = 0; + nnb = t->tabstop - nnb % t->tabstop; + rp = runemalloc(nnb); + for(nr = 0; nr < nnb; nr++) + rp[nr] = ' '; + } + break; + case '\n': + if(t->what == Body && t->w->indent[AUTOINDENT]){ + /* find beginning of previous line using backspace code */ + nnb = textbswidth(t, 0x15); /* ^U case */ + rp = runemalloc(nnb + 1); + nr = 0; + rp[nr++] = r; + for(i=0; i<nnb; i++){ + r = textreadc(t, t->q0-nnb+i); + if(r != ' ' && r != '\t') + break; + rp[nr++] = r; + } + } + break; /* fall through to normal code */ + } + /* otherwise ordinary character; just insert, typically in caches of all texts */ + for(i=0; i<t->file->ntext; i++){ + u = t->file->text[i]; + if(u->eq0 == ~0) + u->eq0 = t->q0; + if(u->ncache == 0) + u->cq0 = t->q0; + else if(t->q0 != u->cq0+u->ncache) + error("text.type cq1"); + textinsert(u, t->q0, rp, nr, FALSE); + if(u != t) + textsetselect(u, u->q0, u->q1); + if(u->ncache+nr > u->ncachealloc){ + u->ncachealloc += 10 + nr; + u->cache = runerealloc(u->cache, u->ncachealloc); + } + runemove(u->cache+u->ncache, rp, nr); + u->ncache += nr; + } + if(rp != &r) + free(rp); + textsetselect(t, t->q0+nr, t->q0+nr); + if(r=='\n' && t->w!=nil) + wincommit(t->w, t); +} + +void +textcommit(Text *t, int tofile) +{ + if(t->ncache == 0) + return; + if(tofile) + fileinsert(t->file, t->cq0, t->cache, t->ncache); + if(t->what == Body){ + t->w->dirty = TRUE; + t->w->utflastqid = -1; + } + t->ncache = 0; +} + +static Text *clicktext; +static uint clickmsec; +static int clickcount; +static Point clickpt; +static Text *selecttext; +static uint selectq; + +/* + * called from frame library + */ +void +framescroll(Frame *f, int dl) +{ + if(f != &selecttext->Frame) + error("frameselect not right frame"); + textframescroll(selecttext, dl); +} + +void +textframescroll(Text *t, int dl) +{ + uint q0; + + if(dl == 0){ + scrsleep(100); + return; + } + if(dl < 0){ + q0 = textbacknl(t, t->org, -dl); + if(selectq > t->org+t->p0) + textsetselect(t, t->org+t->p0, selectq); + else + textsetselect(t, selectq, t->org+t->p0); + }else{ + if(t->org+t->nchars == t->file->nc) + return; + q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height)); + if(selectq > t->org+t->p1) + textsetselect(t, t->org+t->p1, selectq); + else + textsetselect(t, selectq, t->org+t->p1); + } + textsetorigin(t, q0, TRUE); + flushimage(display, 1); +} + + +void +textselect(Text *t) +{ + uint q0, q1; + int b, x, y, dx, dy; + int state; + + selecttext = t; + /* + * To have double-clicking and chording, we double-click + * immediately if it might make sense. + */ + b = mouse->buttons; + q0 = t->q0; + q1 = t->q1; + dx = abs(clickpt.x - mouse->xy.x); + dy = abs(clickpt.y - mouse->xy.y); + clickpt = mouse->xy; + selectq = t->org+frcharofpt(t, mouse->xy); + clickcount++; + if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3) + clickcount = 0; + if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){ + textstretchsel(t, selectq, &q0, &q1, clickcount); + textsetselect(t, q0, q1); + flushimage(display, 1); + x = mouse->xy.x; + y = mouse->xy.y; + /* stay here until something interesting happens */ + while(1){ + readmouse(mousectl); + dx = abs(mouse->xy.x - x); + dy = abs(mouse->xy.y - y); + if(mouse->buttons != b || dx >= 3 || dy >= 3) + break; + clickcount++; + clickmsec = mouse->msec; + } + mouse->xy.x = x; /* in case we're calling frselect */ + mouse->xy.y = y; + q0 = t->q0; /* may have changed */ + q1 = t->q1; + selectq = t->org+frcharofpt(t, mouse->xy);; + } + if(mouse->buttons == b && clickcount == 0){ + t->Frame.scroll = framescroll; + frselect(t, mousectl); + /* horrible botch: while asleep, may have lost selection altogether */ + if(selectq > t->file->nc) + selectq = t->org + t->p0; + t->Frame.scroll = nil; + if(selectq < t->org) + q0 = selectq; + else + q0 = t->org + t->p0; + if(selectq > t->org+t->nchars) + q1 = selectq; + else + q1 = t->org+t->p1; + } + if(q0 == q1){ + if(q0==t->q0 && mouse->msec-clickmsec<500) + textstretchsel(t, selectq, &q0, &q1, clickcount); + else + clicktext = t; + clickmsec = mouse->msec; + }else + clicktext = nil; + textsetselect(t, q0, q1); + flushimage(display, 1); + state = 0; /* undo when possible; +1 for cut, -1 for paste */ + while(mouse->buttons){ + mouse->msec = 0; + b = mouse->buttons; + if((b&1) && (b&6)){ + if(state==0 && t->what==Body){ + seq++; + filemark(t->w->body.file); + } + if(b & 2){ + if(state==-1 && t->what==Body){ + winundo(t->w, TRUE); + textsetselect(t, q0, t->q0); + state = 0; + }else if(state != 1){ + cut(t, t, nil, TRUE, TRUE, nil, 0); + state = 1; + } + }else{ + if(state==1 && t->what==Body){ + winundo(t->w, TRUE); + textsetselect(t, q0, t->q1); + state = 0; + }else if(state != -1){ + paste(t, t, nil, TRUE, FALSE, nil, 0); + state = -1; + } + } + textscrdraw(t); + clearmouse(); + } + flushimage(display, 1); + while(mouse->buttons == b) + readmouse(mousectl); + if(mouse->msec-clickmsec >= 500) + clicktext = nil; + } +} + +void +textshow(Text *t, uint q0, uint q1, int doselect) +{ + int qe; + int nl; + uint q; + + if(t->what != Body){ + if(doselect) + textsetselect(t, q0, q1); + return; + } + if(t->w!=nil && t->maxlines==0) + colgrow(t->col, t->w, 1); + if(doselect) + textsetselect(t, q0, q1); + qe = t->org+t->nchars; + if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache))) + textscrdraw(t); + else{ + if(t->w->nopen[QWevent] > 0) + nl = 3*t->maxlines/4; + else + nl = t->maxlines/4; + q = textbacknl(t, q0, nl); + /* avoid going backwards if trying to go forwards - long lines! */ + if(!(q0>t->org && q<t->org)) + textsetorigin(t, q, TRUE); + while(q0 > t->org+t->nchars) + textsetorigin(t, t->org+1, FALSE); + } +} + +static +int +region(int a, int b) +{ + if(a < b) + return -1; + if(a == b) + return 0; + return 1; +} + +void +selrestore(Frame *f, Point pt0, uint p0, uint p1) +{ + if(p1<=f->p0 || p0>=f->p1){ + /* no overlap */ + frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]); + return; + } + if(p0>=f->p0 && p1<=f->p1){ + /* entirely inside */ + frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); + return; + } + + /* they now are known to overlap */ + + /* before selection */ + if(p0 < f->p0){ + frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]); + p0 = f->p0; + pt0 = frptofchar(f, p0); + } + /* after selection */ + if(p1 > f->p1){ + frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]); + p1 = f->p1; + } + /* inside selection */ + frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); +} + +void +textsetselect(Text *t, uint q0, uint q1) +{ + int p0, p1; + + /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */ + t->q0 = q0; + t->q1 = q1; + /* compute desired p0,p1 from q0,q1 */ + p0 = q0-t->org; + p1 = q1-t->org; + if(p0 < 0) + p0 = 0; + if(p1 < 0) + p1 = 0; + if(p0 > t->nchars) + p0 = t->nchars; + if(p1 > t->nchars) + p1 = t->nchars; + if(p0==t->p0 && p1==t->p1) + return; + /* screen disagrees with desired selection */ + if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){ + /* no overlap or too easy to bother trying */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0); + frdrawsel(t, frptofchar(t, p0), p0, p1, 1); + goto Return; + } + /* overlap; avoid unnecessary painting */ + if(p0 < t->p0){ + /* extend selection backwards */ + frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1); + }else if(p0 > t->p0){ + /* trim first part of selection */ + frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0); + } + if(p1 > t->p1){ + /* extend selection forwards */ + frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1); + }else if(p1 < t->p1){ + /* trim last part of selection */ + frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0); + } + + Return: + t->p0 = p0; + t->p1 = p1; +} + +/* + * Release the button in less than DELAY ms and it's considered a null selection + * if the mouse hardly moved, regardless of whether it crossed a char boundary. + */ +enum { + DELAY = 2, + MINMOVE = 4, +}; + +uint +xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */ +{ + uint p0, p1, q, tmp; + ulong msec; + Point mp, pt0, pt1, qt; + int reg, b; + + mp = mc->xy; + b = mc->buttons; + msec = mc->msec; + + /* remove tick */ + if(f->p0 == f->p1) + frtick(f, frptofchar(f, f->p0), 0); + p0 = p1 = frcharofpt(f, mp); + pt0 = frptofchar(f, p0); + pt1 = frptofchar(f, p1); + reg = 0; + frtick(f, pt0, 1); + do{ + q = frcharofpt(f, mc->xy); + if(p1 != q){ + if(p0 == p1) + frtick(f, pt0, 0); + if(reg != region(q, p0)){ /* crossed starting point; reset */ + if(reg > 0) + selrestore(f, pt0, p0, p1); + else if(reg < 0) + selrestore(f, pt1, p1, p0); + p1 = p0; + pt1 = pt0; + reg = region(q, p0); + if(reg == 0) + frdrawsel0(f, pt0, p0, p1, col, display->white); + } + qt = frptofchar(f, q); + if(reg > 0){ + if(q > p1) + frdrawsel0(f, pt1, p1, q, col, display->white); + + else if(q < p1) + selrestore(f, qt, q, p1); + }else if(reg < 0){ + if(q > p1) + selrestore(f, pt1, p1, q); + else + frdrawsel0(f, qt, q, p1, col, display->white); + } + p1 = q; + pt1 = qt; + } + if(p0 == p1) + frtick(f, pt0, 1); + flushimage(f->display, 1); + readmouse(mc); + }while(mc->buttons == b); + if(mc->msec-msec < DELAY && p0!=p1 + && abs(mp.x-mc->xy.x)<MINMOVE + && abs(mp.y-mc->xy.y)<MINMOVE) { + if(reg > 0) + selrestore(f, pt0, p0, p1); + else if(reg < 0) + selrestore(f, pt1, p1, p0); + p1 = p0; + } + if(p1 < p0){ + tmp = p0; + p0 = p1; + p1 = tmp; + } + pt0 = frptofchar(f, p0); + if(p0 == p1) + frtick(f, pt0, 0); + selrestore(f, pt0, p0, p1); + /* restore tick */ + if(f->p0 == f->p1) + frtick(f, frptofchar(f, f->p0), 1); + flushimage(f->display, 1); + *p1p = p1; + return p0; +} + +int +textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask) +{ + uint p0, p1; + int buts; + + p0 = xselect(t, mousectl, high, &p1); + buts = mousectl->buttons; + if((buts & mask) == 0){ + *q0 = p0+t->org; + *q1 = p1+t->org; + } + + while(mousectl->buttons) + readmouse(mousectl); + return buts; +} + +int +textselect2(Text *t, uint *q0, uint *q1, Text **tp) +{ + int buts; + + *tp = nil; + buts = textselect23(t, q0, q1, but2col, 4); + if(buts & 4) + return 0; + if(buts & 1){ /* pick up argument */ + *tp = argtext; + return 1; + } + return 1; +} + +int +textselect3(Text *t, uint *q0, uint *q1) +{ + int h; + + h = (textselect23(t, q0, q1, but3col, 1|2) == 0); + return h; +} + +static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 }; +static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; +static Rune left2[] = { L'\n', 0 }; +static Rune left3[] = { L'\'', L'"', L'`', 0 }; + +static +Rune *left[] = { + left1, + left2, + left3, + nil +}; +static +Rune *right[] = { + right1, + left2, + left3, + nil +}; + +int +inmode(Rune r, int mode) +{ + return (mode == 1) ? isalnum(r) : r && !isspace(r); +} + +void +textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode) +{ + int c, i; + Rune *r, *l, *p; + uint q; + + *q0 = mp; + *q1 = mp; + for(i=0; left[i]!=nil; i++){ + q = *q0; + l = left[i]; + r = right[i]; + /* try matching character to left, looking right */ + if(q == 0) + c = '\n'; + else + c = textreadc(t, q-1); + p = runestrchr(l, c); + if(p != nil){ + if(textclickmatch(t, c, r[p-l], 1, &q)) + *q1 = q-(c!='\n'); + return; + } + /* try matching character to right, looking left */ + if(q == t->file->nc) + c = '\n'; + else + c = textreadc(t, q); + p = runestrchr(r, c); + if(p != nil){ + if(textclickmatch(t, c, l[p-r], -1, &q)){ + *q1 = *q0+(*q0<t->file->nc && c=='\n'); + *q0 = q; + if(c!='\n' || q!=0 || textreadc(t, 0)=='\n') + (*q0)++; + } + return; + } + } + /* try filling out word to right */ + while(*q1<t->file->nc && inmode(textreadc(t, *q1), mode)) + (*q1)++; + /* try filling out word to left */ + while(*q0>0 && inmode(textreadc(t, *q0-1), mode)) + (*q0)--; +} + +int +textclickmatch(Text *t, int cl, int cr, int dir, uint *q) +{ + Rune c; + int nest; + + nest = 1; + for(;;){ + if(dir > 0){ + if(*q == t->file->nc) + break; + c = textreadc(t, *q); + (*q)++; + }else{ + if(*q == 0) + break; + (*q)--; + c = textreadc(t, *q); + } + if(c == cr){ + if(--nest==0) + return 1; + }else if(c == cl) + nest++; + } + return cl=='\n' && nest==1; +} + +uint +textbacknl(Text *t, uint p, uint n) +{ + int i, j; + + /* look for start of this line if n==0 */ + if(n==0 && p>0 && textreadc(t, p-1)!='\n') + n = 1; + i = n; + while(i-->0 && p>0){ + --p; /* it's at a newline now; back over it */ + if(p == 0) + break; + /* at 128 chars, call it a line anyway */ + for(j=128; --j>0 && p>0; p--) + if(textreadc(t, p-1)=='\n') + break; + } + return p; +} + +void +textsetorigin(Text *t, uint org, int exact) +{ + int i, a, fixup; + Rune *r; + uint n; + + if(org>0 && !exact && textreadc(t, org-1) != '\n'){ + /* org is an estimate of the char posn; find a newline */ + /* don't try harder than 256 chars */ + for(i=0; i<256 && org<t->file->nc; i++){ + if(textreadc(t, org) == '\n'){ + org++; + break; + } + org++; + } + } + a = org-t->org; + fixup = 0; + if(a>=0 && a<t->nchars){ + frdelete(t, 0, a); + fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ + } + else if(a<0 && -a<t->nchars){ + n = t->org - org; + r = runemalloc(n); + bufread(t->file, org, r, n); + frinsert(t, r, r+n, 0); + free(r); + }else + frdelete(t, 0, t->nchars); + t->org = org; + textfill(t); + textscrdraw(t); + textsetselect(t, t->q0, t->q1); + if(fixup && t->p1 > t->p0) + frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1); +} + +void +textreset(Text *t) +{ + t->file->seq = 0; + t->eq0 = ~0; + /* do t->delete(0, t->nc, TRUE) without building backup stuff */ + textsetselect(t, t->org, t->org); + frdelete(t, 0, t->nchars); + t->org = 0; + t->q0 = 0; + t->q1 = 0; + filereset(t->file); + bufreset(t->file); +} 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. |