ref: 9914a8da911d9fdfefd6b090870bbd385cacae69
author: aap <[email protected]>
date: Mon Jul 22 18:11:44 EDT 2024
first commit
--- /dev/null
+++ b/inc.h
@@ -1,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <mouse.h>
+//#include <cursor.h>
+#include <frame.h>
+//#include <fcall.h>
+//#include <9p.h>
+//#include <complete.h>
+#include <plumb.h>
+
+typedef uchar bool;
+enum {
+ FALSE = 0,
+ TRUE = 1,
+
+ BIG = 3,
+};
+
+#define ALT(c, v, t) (Alt){ c, v, t, nil, nil, 0 }
+
+#define CTRL(c) ((c)&0x1F)
+
+
+extern Rune *snarf;
+extern int nsnarf;
+extern int snarfversion;
+extern int snarffd;
+enum { MAXSNARF = 100*1024 };
+void putsnarf(void);
+void getsnarf(void);
+void setsnarf(char *s, int ns);
+
+typedef struct Text Text;
+struct Text
+{
+ Frame;
+ Rectangle scrollr, lastsr;
+ Image *i;
+ Rune *r;
+ uint nr;
+ uint maxr;
+ uint org; /* start of Frame's text */
+ uint q0, q1; /* selection */
+ uint qh; /* host point, output here */
+
+ /* not entirely happy with this in here */
+ bool rawmode;
+ Rune *raw;
+ int nraw;
+
+ int posx;
+};
+
+void xinit(Text *x, Rectangle textr, Rectangle scrollr, Font *ft, Image *b, Image **cols);
+void xsetrects(Text *x, Rectangle textr, Rectangle scrollr);
+void xclear(Text *x);
+void xredraw(Text *x);
+void xfullredraw(Text *x);
+uint xinsert(Text *x, Rune *r, int n, uint q0);
+void xfill(Text *x);
+void xdelete(Text *x, uint q0, uint q1);
+void xsetselect(Text *x, uint q0, uint q1);
+void xselect(Text *x, Mousectl *mc);
+void xscrdraw(Text *x);
+void xscroll(Text *x, Mousectl *mc, int but);
+void xscrolln(Text *x, int n);
+void xtickupdn(Text *x, int d);
+void xshow(Text *x, uint q0);
+void xplacetick(Text *x, uint q);
+void xtype(Text *x, Rune r);
+int xninput(Text *x);
+void xaddraw(Text *x, Rune *r, int nr);
+void xlook(Text *x);
+void xsnarf(Text *x);
+void xcut(Text *x);
+void xpaste(Text *x);
+void xsend(Text *x);
+int xplumb(Text *w, char *src, char *dir, int maxsize);
+void freescrtemps(void);
+
+#define runemalloc(n) malloc((n)*sizeof(Rune))
+#define runerealloc(a, n) realloc(a, (n)*sizeof(Rune))
+#define runemove(a, b, n) memmove(a, b, (n)*sizeof(Rune))
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+void panic(char *s);
+void *emalloc(ulong size);
+void *erealloc(void *p, ulong size);
+char *estrdup(char *s);
+
+
+typedef struct Timer Timer;
+struct Timer
+{
+ int dt;
+ int cancel;
+ Channel *c; /* chan(int) */
+ Timer *next;
+};
+void timerinit(void);
+Timer *timerstart(int dt);
+void timerstop(Timer *t);
+void timercancel(Timer *t);
--- /dev/null
+++ b/main.c
@@ -1,0 +1,448 @@
+#include "inc.h"
+#include <cursor.h>
+
+Keyboardctl *kbctl;
+Keyboardctl *fwdkc, kbdctl2;
+Mousectl *mctl;
+int shiftdown; // TODO: needed?
+
+Text text;
+Image *colors[NCOL];
+char filename[1024];
+char *startdir;
+
+Cursor quest = {
+ {-7,-7},
+ {0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe,
+ 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8,
+ 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0,
+ 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
+ {0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c,
+ 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0,
+ 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80,
+ 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+
+
+void
+panic(char *s)
+{
+ fprint(2, "error: %s: %r\n", s);
+ threadexitsall("error");
+}
+
+void*
+emalloc(ulong size)
+{
+ void *p;
+
+ p = malloc(size);
+ if(p == nil)
+ panic("malloc failed");
+ memset(p, 0, size);
+ return p;
+}
+
+void*
+erealloc(void *p, ulong size)
+{
+ p = realloc(p, size);
+ if(p == nil)
+ panic("realloc failed");
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *p;
+
+ p = malloc(strlen(s)+1);
+ if(p == nil)
+ panic("strdup failed");
+ strcpy(p, s);
+ return p;
+}
+
+
+
+
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+void
+putsnarf(void)
+{
+ int fd, i, n;
+
+ if(snarffd<0 || nsnarf==0)
+ return;
+ fd = open("/dev/snarf", OWRITE|OCEXEC);
+ if(fd < 0)
+ return;
+ /* snarf buffer could be huge, so fprint will truncate; do it in blocks */
+ for(i=0; i<nsnarf; i+=n){
+ n = nsnarf-i;
+ if(n >= 256)
+ n = 256;
+ if(fprint(fd, "%.*S", n, snarf+i) < 0)
+ break;
+ }
+ close(fd);
+}
+
+void
+setsnarf(char *s, int ns)
+{
+ free(snarf);
+ snarf = runesmprint("%.*s", ns, s);
+ nsnarf = runestrlen(snarf);
+ snarfversion++;
+}
+
+void
+getsnarf(void)
+{
+ int i, n;
+ char *s, *sn;
+
+ if(snarffd < 0)
+ return;
+ sn = nil;
+ i = 0;
+ seek(snarffd, 0, 0);
+ for(;;){
+ if(i > MAXSNARF)
+ break;
+ if((s = realloc(sn, i+1024+1)) == nil)
+ break;
+ sn = s;
+ if((n = read(snarffd, sn+i, 1024)) <= 0)
+ break;
+ i += n;
+ }
+ if(i == 0)
+ return;
+ sn[i] = 0;
+ setsnarf(sn, i);
+ free(sn);
+}
+
+
+
+
+void
+readfile(char *path)
+{
+ int fd, n, ns;
+ char *s, buf[1024];
+ Rune *rs;
+
+ fd = open(path, OREAD);
+ if(fd < 0)
+ return;
+ s = nil;
+ ns = 0;
+ while(n = read(fd, buf, sizeof(buf)), n > 0) {
+ s = realloc(s, ns+n);
+ memcpy(s+ns, buf, n);
+ ns += n;
+ }
+ close(fd);
+
+
+ rs = runesmprint("%.*s", ns, s);
+ free(s);
+ xdelete(&text, 0, text.nr);
+ xinsert(&text, rs, runestrlen(rs), 0);
+ free(rs);
+}
+
+int
+writefile(char *path)
+{
+ int fd;
+ char *s;
+
+ fd = create(path, OWRITE|OTRUNC, 0666);
+ if(fd < 0)
+ return 1;
+
+ s = smprint("%.*S", text.nr, text.r);
+ write(fd, s, strlen(s));
+ close(fd);
+ free(s);
+ return 0;
+}
+
+
+void
+confused(void)
+{
+ setcursor(mctl, &quest);
+ sleep(300);
+ setcursor(mctl, nil);
+}
+
+void
+editmenu(Text *x, Mousectl *mc)
+{
+ enum {
+ Cut,
+ Paste,
+ Snarf,
+ Plumb,
+ Look
+ };
+ static char *menu2str[] = {
+ "cut",
+ "paste",
+ "snarf",
+ "plumb",
+ "look",
+ nil
+ };
+ static Menu menu2 = { menu2str };
+
+ switch(menuhit(2, mc, &menu2, nil)){
+ case Cut:
+ xsnarf(x);
+ xcut(x);
+ break;
+ case Paste:
+ xpaste(x);
+ break;
+ case Snarf:
+ xsnarf(x);
+ break;
+ case Plumb:
+ if(xplumb(x, "jot", startdir, 31*1024))
+ confused();
+ break;
+ case Look:
+ xlook(x);
+ break;
+ }
+}
+
+void
+filemenu(Text *x, Mousectl *mc)
+{
+ USED(x);
+ enum {
+ Write,
+ Exit
+ };
+ static char *str[] = {
+ "write",
+ "exit",
+ nil
+ };
+ static Menu menu = { str };
+
+ switch(menuhit(3, mc, &menu, nil)){
+ case Write:
+ if(filename[0] == '\0'){
+ fwdkc = &kbdctl2;
+ enter("file", filename, sizeof(filename), mc, fwdkc, nil);
+ fwdkc = nil;
+ }
+ if(writefile(filename)){
+ memset(filename, 0, sizeof(filename));
+ confused();
+ }
+ break;
+ case Exit:
+ threadexitsall(nil);
+ }
+}
+
+void
+mousectl(Text *x, Mousectl *mc)
+{
+ int but;
+
+ for(but = 1; but < 6; but++)
+ if(mc->buttons == 1<<(but-1))
+ goto found;
+ return;
+found:
+
+/* if(shiftdown && but > 3)
+ wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
+ else*/ if(ptinrect(mc->xy, x->scrollr) || but > 3)
+ xscroll(x, mc, but);
+ else if(but == 1)
+ xselect(x, mc);
+ else if(but == 2)
+ editmenu(x, mc);
+ else if(but == 3)
+ filemenu(x, mc);
+}
+
+void
+keyctl(Text *x, Rune r)
+{
+ int nlines, n;
+
+ nlines = x->maxlines; /* need signed */
+ switch(r){
+
+ /* Scrolling */
+ case Kscrollonedown:
+ n = mousescrollsize(x->maxlines);
+ xscrolln(x, max(n, 1));
+ break;
+ case Kdown:
+ xscrolln(x, shiftdown ? 1 : nlines/3);
+ break;
+ case Kpgdown:
+ xscrolln(x, nlines*2/3);
+ break;
+ case Kscrolloneup:
+ n = mousescrollsize(x->maxlines);
+ xscrolln(x, -max(n, 1));
+ break;
+ case Kup:
+ xscrolln(x, -(shiftdown ? 1 : nlines/3));
+ break;
+ case Kpgup:
+ xscrolln(x, -nlines*2/3);
+ break;
+
+ case Khome:
+ xshow(x, 0);
+ break;
+ case Kend:
+ xshow(x, x->nr);
+ break;
+
+ /* Cursor movement */
+ case Kleft:
+ if(x->q0 > 0)
+ xplacetick(x, x->q0-1);
+ break;
+ case Kright:
+ if(x->q1 < x->nr)
+ xplacetick(x, x->q1+1);
+ break;
+ case CTRL('A'):
+ while(x->q0 > 0 && x->r[x->q0-1] != '\n' &&
+ x->q0 != x->qh)
+ x->q0--;
+ xplacetick(x, x->q0);
+ break;
+ case CTRL('E'):
+ while(x->q0 < x->nr && x->r[x->q0] != '\n')
+ x->q0++;
+ xplacetick(x, x->q0);
+ break;
+ case CTRL('B'):
+ xplacetick(x, x->qh);
+ break;
+
+ case Kesc:
+ xsnarf(x);
+ xcut(x);
+ break;
+ case Kdel:
+ xtype(x, CTRL('H'));
+ break;
+
+ default:
+ xtype(x, r);
+ break;
+ }
+}
+
+void
+setsize(Text *x)
+{
+ Rectangle scrollr, textr;
+
+ draw(screen, screen->r, colors[BACK], nil, ZP);
+ scrollr = textr = insetrect(screen->r, 1);
+ scrollr.max.x = scrollr.min.x + 12;
+ textr.min.x = scrollr.max.x + 4;
+ xinit(x, textr, scrollr, font, screen, colors);
+}
+
+void
+mthread(void*)
+{
+ while(readmouse(mctl) != -1){
+ mousectl(&text, mctl);
+ }
+}
+
+void
+kbthread(void*)
+{
+ Rune r;
+
+ for(;;){
+ r = recvul(kbctl->c);
+ if(fwdkc)
+ send(fwdkc->c, &r);
+ else
+ keyctl(&text, r);
+ flushimage(display, 1);
+ }
+}
+
+void
+resthread(void*)
+{
+ for(;;){
+ recvul(mctl->resizec);
+ if(getwindow(display, Refnone) < 0)
+ sysfatal("resize failed: %r");
+
+ setsize(&text);
+ }
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char buf[1024];
+// newwindow(nil);
+
+ if(initdraw(nil, nil, "jot") < 0)
+ sysfatal("initdraw: %r");
+
+ kbctl = initkeyboard("/dev/cons");
+ if(kbctl == nil)
+ sysfatal("initkeyboard: %r");
+ kbdctl2.c = chancreate(sizeof(Rune), 20);
+
+ mctl = initmouse("/dev/mouse", screen);
+ if(mctl == nil)
+ sysfatal("initmouse: %r");
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ if(getwd(buf, sizeof(buf)) == nil)
+ startdir = estrdup(".");
+ else
+ startdir = estrdup(buf);
+
+ colors[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ colors[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ colors[BORD] = allocimage(display, Rect(0,0,2,2), screen->chan, 1, DYellowgreen);
+ colors[TEXT] = display->black;
+ colors[HTEXT] = display->black;
+
+ setsize(&text);
+
+ timerinit();
+ threadcreate(mthread, nil, mainstacksize);
+ threadcreate(kbthread, nil, mainstacksize);
+ threadcreate(resthread, nil, mainstacksize);
+
+ if(argc > 1){
+ strncpy(filename, argv[1], sizeof(filename));
+ readfile(filename);
+ }
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,13 @@
+< /$objtype/mkfile
+
+TARG=jot
+OFILES=\
+ main.$O \
+ text.$O \
+ time.$O
+
+HFILES=inc.h
+
+BIN=$home/bin/$objtype
+
+< /sys/src/cmd/mkone
--- /dev/null
+++ b/text.c
@@ -1,0 +1,964 @@
+#include "inc.h"
+
+enum
+{
+ HiWater = 640000, /* max size of history */
+ LoWater = 400000, /* min size of history after max'ed */
+ MinWater = 20000, /* room to leave available when reallocating */
+};
+
+void
+xinit(Text *x, Rectangle textr, Rectangle scrollr, Font *ft, Image *b, Image **cols)
+{
+ frinit(x, textr, ft, b, cols);
+ x->i = b;
+ x->scrollr = scrollr;
+ x->lastsr = ZR;
+ xfill(x);
+ xsetselect(x, x->q0, x->q1);
+ xscrdraw(x);
+}
+
+void
+xsetrects(Text *x, Rectangle textr, Rectangle scrollr)
+{
+ frsetrects(x, textr, x->b);
+ x->scrollr = scrollr;
+}
+
+void
+xclear(Text *x)
+{
+ free(x->r);
+ x->r = nil;
+ x->nr = 0;
+ free(x->raw);
+ x->r = nil;
+ x->nraw = 0;
+ frclear(x, TRUE);
+};
+
+void
+xredraw(Text *x)
+{
+ frredraw(x);
+ xscrdraw(x);
+}
+
+void
+xfullredraw(Text *x)
+{
+ xfill(x);
+ x->ticked = 0;
+ if(x->p0 > 0)
+ frdrawsel(x, frptofchar(x, 0), 0, x->p0, 0);
+ if(x->p1 < x->nchars)
+ frdrawsel(x, frptofchar(x, x->p1), x->p1, x->nchars, 0);
+ frdrawsel(x, frptofchar(x, x->p0), x->p0, x->p1, 1);
+ x->lastsr = ZR;
+ xscrdraw(x);
+}
+
+uint
+xinsert(Text *w, Rune *r, int n, uint q0)
+{
+ uint m;
+
+ if(n == 0)
+ return q0;
+ if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
+ m = min(HiWater-LoWater, min(w->org, w->qh));
+ w->org -= m;
+ w->qh -= m;
+ if(w->q0 > m)
+ w->q0 -= m;
+ else
+ w->q0 = 0;
+ if(w->q1 > m)
+ w->q1 -= m;
+ else
+ w->q1 = 0;
+ w->nr -= m;
+ runemove(w->r, w->r+m, w->nr);
+ q0 -= m;
+ }
+ if(w->nr+n > w->maxr){
+ /*
+ * Minimize realloc breakage:
+ * Allocate at least MinWater
+ * Double allocation size each time
+ * But don't go much above HiWater
+ */
+ m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
+ if(m > HiWater)
+ m = max(HiWater+MinWater, w->nr+n);
+ if(m > w->maxr){
+ w->r = runerealloc(w->r, m);
+ w->maxr = m;
+ }
+ }
+ runemove(w->r+q0+n, w->r+q0, w->nr-q0);
+ runemove(w->r+q0, r, n);
+ w->nr += n;
+ /* if output touches, advance selection, not qh; works best for keyboard and output */
+ if(q0 <= w->q1)
+ w->q1 += n;
+ if(q0 <= w->q0)
+ w->q0 += n;
+ if(q0 < w->qh)
+ w->qh += n;
+ if(q0 < w->org)
+ w->org += n;
+ else if(q0 <= w->org+w->nchars)
+ frinsert(w, r, r+n, q0-w->org);
+ return q0;
+}
+
+void
+xfill(Text *w)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ while(w->lastlinefull == FALSE){
+ n = w->nr-(w->org+w->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ rp = w->r+(w->org+w->nchars);
+
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = w->maxlines-w->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(w, rp, rp+i, w->nchars);
+ }
+}
+
+void
+xdelete(Text *w, uint q0, uint q1)
+{
+ uint n, p0, p1;
+
+ n = q1-q0;
+ if(n == 0)
+ return;
+ runemove(w->r+q0, w->r+q1, w->nr-q1);
+ w->nr -= n;
+ if(q0 < w->q0)
+ w->q0 -= min(n, w->q0-q0);
+ if(q0 < w->q1)
+ w->q1 -= min(n, w->q1-q0);
+ if(q1 < w->qh)
+ w->qh -= n;
+ else if(q0 < w->qh)
+ w->qh = q0;
+ if(q1 <= w->org)
+ w->org -= n;
+ else if(q0 < w->org+w->nchars){
+ p1 = q1 - w->org;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(q0 < w->org){
+ w->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - w->org;
+ frdelete(w, p0, p1);
+ xfill(w);
+ }
+}
+
+void
+xsetselect(Text *w, uint q0, uint q1)
+{
+ int p0, p1;
+
+ w->posx = -1;
+ /* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
+ w->q0 = q0;
+ w->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-w->org;
+ p1 = q1-w->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > w->nchars)
+ p0 = w->nchars;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(p0==w->p0 && p1==w->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
+ frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < w->p0){
+ /* extend selection backwards */
+ frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
+ }else if(p0 > w->p0){
+ /* trim first part of selection */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
+ }
+ if(p1 > w->p1){
+ /* extend selection forwards */
+ frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
+ }else if(p1 < w->p1){
+ /* trim last part of selection */
+ frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
+ }
+
+ Return:
+ w->p0 = p0;
+ w->p1 = p1;
+}
+
+static void
+xsetorigin(Text *w, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<w->nr; i++){
+ if(w->r[org] == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-w->org;
+ fixup = 0;
+ if(a>=0 && a<w->nchars){
+ frdelete(w, 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<w->nchars){
+ n = w->org - org;
+ r = w->r+org;
+ frinsert(w, r, r+n, 0);
+ }else
+ frdelete(w, 0, w->nchars);
+ w->org = org;
+ xfill(w);
+ xscrdraw(w);
+ xsetselect(w, w->q0, w->q1);
+ if(fixup && w->p1 > w->p0)
+ frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
+}
+
+
+/*
+ * Scrolling
+ */
+
+static Image *scrtmp;
+
+static Image*
+scrtemps(void)
+{
+ int h;
+
+ if(scrtmp == nil){
+ h = BIG*Dy(screen->r);
+ scrtmp = allocimage(display, Rect(0, 0, 32, h), screen->chan, 0, DNofill);
+ }
+ return scrtmp;
+}
+
+void
+freescrtemps(void)
+{
+ if(scrtmp){
+ freeimage(scrtmp);
+ scrtmp = nil;
+ }
+}
+
+static Rectangle
+scrpos(Rectangle r, uint p0, uint p1, uint tot)
+{
+ Rectangle q;
+ int h;
+
+ q = r;
+ h = q.max.y-q.min.y;
+ if(tot == 0)
+ return q;
+ if(tot > 1024*1024){
+ tot>>=10;
+ p0>>=10;
+ p1>>=10;
+ }
+ if(p0 > 0)
+ q.min.y += h*p0/tot;
+ if(p1 < tot)
+ q.max.y -= h*(tot-p1)/tot;
+ if(q.max.y < q.min.y+2){
+ if(q.min.y+2 <= r.max.y)
+ q.max.y = q.min.y+2;
+ else
+ q.min.y = q.max.y-2;
+ }
+ return q;
+}
+
+void
+xscrdraw(Text *w)
+{
+ Rectangle r, r1, r2;
+ Image *b;
+
+ b = scrtemps();
+ if(b == nil || w->i == nil)
+ return;
+ r = w->scrollr;
+ r1 = r;
+ r1.min.x = 0;
+ r1.max.x = Dx(r);
+ r2 = scrpos(r1, w->org, w->org+w->nchars, w->nr);
+ if(!eqrect(r2, w->lastsr)){
+ w->lastsr = r2;
+ /* move r1, r2 to (0,0) to avoid clipping */
+ r2 = rectsubpt(r2, r1.min);
+ r1 = rectsubpt(r1, r1.min);
+ draw(b, r1, w->cols[BORD], nil, ZP);
+ draw(b, r2, w->cols[BACK], nil, ZP);
+ r2.min.x = r2.max.x-1;
+ draw(b, r2, w->cols[BORD], nil, ZP);
+ draw(w->i, r, b, nil, Pt(0, r1.min.y));
+ }
+}
+
+static uint
+xbacknl(Text *w, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && w->r[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(w->r[p-1]=='\n')
+ break;
+ }
+ return p;
+}
+
+static void
+xscrsleep(Mousectl *mc, uint dt)
+{
+ Timer *timer;
+ int y, b;
+ static Alt alts[3];
+
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ timer = timerstart(dt);
+ y = mc->xy.y;
+ b = mc->buttons;
+ alts[0] = ALT(timer->c, nil, CHANRCV);
+ alts[1] = ALT(mc->c, &mc->Mouse, CHANRCV);
+ alts[2].op = CHANEND;
+ for(;;)
+ switch(alt(alts)){
+ case 0:
+ timerstop(timer);
+ return;
+ case 1:
+ if(abs(mc->xy.y-y)>2 || mc->buttons!=b){
+ timercancel(timer);
+ return;
+ }
+ break;
+ }
+}
+
+void
+xscroll(Text *w, Mousectl *mc, int but)
+{
+ uint p0, oldp0;
+ Rectangle s;
+ int y, my, h, first;
+
+ s = insetrect(w->scrollr, 1);
+ h = s.max.y-s.min.y;
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ my = mc->xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ if(but == 2){
+ y = my;
+ if(y > s.max.y-2)
+ y = s.max.y-2;
+ if(w->nr > 1024*1024)
+ p0 = ((w->nr>>10)*(y-s.min.y)/h)<<10;
+ else
+ p0 = w->nr*(y-s.min.y)/h;
+ if(oldp0 != p0)
+ xsetorigin(w, p0, FALSE);
+ oldp0 = p0;
+ readmouse(mc);
+ continue;
+ }
+ if(but == 1 || but == 4){
+ y = max(1, (my-s.min.y)/w->font->height);
+ p0 = xbacknl(w, w->org, y);
+ }else{
+ y = max(my, s.min.y+w->font->height);
+ p0 = w->org+frcharofpt(w, Pt(s.max.x, y));
+ }
+ if(oldp0 != p0)
+ xsetorigin(w, p0, TRUE);
+ oldp0 = p0;
+ /* debounce */
+ if(first){
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ if(but > 3)
+ return;
+ sleep(200);
+ nbrecv(mc->c, &mc->Mouse);
+ first = FALSE;
+ }
+ xscrsleep(mc, 100);
+ }while(mc->buttons & (1<<(but-1)));
+ while(mc->buttons)
+ readmouse(mc);
+}
+
+void
+xscrolln(Text *x, int n)
+{
+ uint q0;
+
+ if(n < 0)
+ q0 = xbacknl(x, x->org, -n);
+ else
+ q0 = x->org+frcharofpt(x, Pt(x->Frame.r.min.x, x->Frame.r.min.y+n*x->font->height));
+ xsetorigin(x, q0, TRUE);
+}
+
+/* move tick up or down while staying at the same x position */
+void
+xtickupdn(Text *x, int d)
+{
+ Point p;
+ int py;
+ uint q0;
+
+ xshow(x, x->q0);
+ p = frptofchar(x, x->q0-x->org);
+ if(x->posx >= 0)
+ p.x = x->posx;
+ py = p.y;
+ p.y += d*x->font->height;
+ if(p.y < x->Frame.r.min.y ||
+ p.y > x->Frame.r.max.y-x->font->height){
+ xscrolln(x, d);
+ p.y = py;
+ }
+ q0 = x->org+frcharofpt(x, p);
+ xsetselect(x, q0, q0);
+ x->posx = p.x;
+}
+
+static Text *selecttext;
+static Mousectl *selectmc;
+static uint selectq;
+
+static void
+xframescroll(Text *x, int dl)
+{
+ uint endq;
+
+ if(dl == 0){
+ xscrsleep(selectmc, 100);
+ return;
+ }
+ if(dl < 0){
+ endq = x->org+x->p0;
+ }else{
+ if(x->org+x->nchars == x->nr)
+ return;
+ endq = x->org+x->p1;
+ }
+ xscrolln(x, dl);
+ xsetselect(x, min(selectq, endq), max(selectq, endq));
+}
+
+static void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selecttext->Frame)
+ panic("frameselect not right frame");
+ xframescroll(selecttext, dl);
+}
+
+/*
+ * Selection and deletion helpers
+ */
+
+int
+iswordrune(Rune r)
+{
+ return isalpharune(r) || isdigitrune(r);
+}
+
+static int
+xbswidth(Text *w, Rune c)
+{
+ uint q, stop;
+ Rune r;
+ int wd, inword;
+
+ /* there is known to be at least one character to erase */
+ if(c == Kbs) /* ^H: erase character */
+ return 1;
+ q = w->q0;
+ stop = 0;
+ if(q > w->qh)
+ stop = w->qh;
+ inword = FALSE;
+ while(q > stop){
+ r = w->r[q-1];
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == w->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ /* ^W: erase word.
+ * delete a bunch of non-word characters
+ * followed by word characters */
+ if(c == CTRL('W')){
+ wd = iswordrune(r);
+ if(wd && !inword)
+ inword = TRUE;
+ else if(!wd && inword)
+ break;
+ }
+ --q;
+ }
+ return w->q0-q;
+}
+
+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
+};
+
+static int
+xclickmatch(Text *x, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == x->nr)
+ break;
+ c = x->r[*q];
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = x->r[*q];
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+static int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? iswordrune(r) : r && !isspacerune(r);
+}
+
+static void
+xstretchsel(Text *x, uint pt, uint *q0, uint *q1, int mode)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ *q0 = pt;
+ *q1 = pt;
+ 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 = x->r[q-1];
+ p = runestrchr(l, c);
+ if(p != nil){
+ if(xclickmatch(x, c, r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == x->nr)
+ c = '\n';
+ else
+ c = x->r[q];
+ p = runestrchr(r, c);
+ if(p != nil){
+ if(xclickmatch(x, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<x->nr && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || x->r[0]=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<x->nr && inmode(x->r[*q1], mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(x->r[*q0-1], mode))
+ (*q0)--;
+}
+
+static Mouse lastclick;
+static Text *clickfrm;
+static uint clickcount;
+
+/* should be called with button 1 down */
+void
+xselect(Text *x, Mousectl *mc)
+{
+ uint q0, q1;
+ int dx, dy, dt, b;
+
+ /* reset click state if mouse is too different from last time */
+ dx = abs(mc->xy.x - lastclick.xy.x);
+ dy = abs(mc->xy.y - lastclick.xy.y);
+ dt = mc->msec - lastclick.msec;
+ if(x != clickfrm || dx > 3 || dy > 3 || dt >= 500)
+ clickcount = 0;
+
+ /* first button down can be a dragging selection or a click.
+ * subsequent buttons downs can only be clicks.
+ * both cases can be ended by chording. */
+ selectq = x->org+frcharofpt(x, mc->xy);
+ if(clickcount == 0){
+ /* what a kludge - can this be improved? */
+ selecttext = x;
+ selectmc = mc;
+ x->scroll = framescroll;
+ frselect(x, mc);
+ /* this is correct if the whole selection is visible */
+ q0 = x->org + x->p0;
+ q1 = x->org + x->p1;
+ /* otherwise replace one end with selectq */
+ if(selectq < x->org)
+ q0 = selectq;
+ if(selectq > x->org+x->nchars)
+ q1 = selectq;
+ xsetselect(x, q0, q1);
+
+ /* figure out whether it was a click */
+ if(q0 == q1 && mc->buttons == 0){
+ clickcount = 1;
+ clickfrm = x;
+ }
+ }else{
+ clickcount++;
+ xstretchsel(x, selectq, &q0, &q1, min(clickcount-1, 2));
+ xsetselect(x, q0, q1);
+ if(clickcount >= 3)
+ clickcount = 0;
+ b = mc->buttons;
+ while(mc->buttons == b)
+ readmouse(mc);
+ }
+ lastclick = mc->Mouse; /* a bit unsure if this is correct */
+
+ /* chording */
+ while(mc->buttons){
+ clickcount = 0;
+ b = mc->buttons;
+ if(b & 6){
+ if(b & 2){
+ xsnarf(x);
+ xcut(x);
+ }else{
+ xpaste(x);
+ }
+ }
+ while(mc->buttons == b)
+ readmouse(mc);
+ }
+}
+
+void
+xshow(Text *w, uint q0)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ qe = w->org+w->nchars;
+ if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+ xscrdraw(w);
+ else{
+ nl = 4*w->maxlines/5;
+ q = xbacknl(w, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>w->org && q<w->org))
+ xsetorigin(w, q, TRUE);
+ while(q0 > w->org+w->nchars)
+ xsetorigin(w, w->org+1, FALSE);
+ }
+}
+
+void
+xplacetick(Text *x, uint q)
+{
+ xsetselect(x, q, q);
+ xshow(x, q);
+}
+
+void
+xtype(Text *x, Rune r)
+{
+ uint q0, q1;
+ int nb;
+
+ xsnarf(x);
+ xcut(x);
+ switch(r){
+ case CTRL('H'): /* erase character */
+ case CTRL('W'): /* erase word */
+ case CTRL('U'): /* erase line */
+ if(x->q0==0 || x->q0==x->qh)
+ return;
+ nb = xbswidth(x, r);
+ q1 = x->q0;
+ q0 = q1-nb;
+ if(q0 < x->org){
+ q0 = x->org;
+ nb = q1-q0;
+ }
+ if(nb > 0){
+ xdelete(x, q0, q0+nb);
+ xsetselect(x, q0, q0);
+ }
+ break;
+ default:
+ xinsert(x, &r, 1, x->q0);
+ xshow(x, x->q0);
+ break;
+ }
+}
+
+int
+xninput(Text *x)
+{
+ uint q;
+ Rune r;
+
+ for(q = x->qh; q < x->nr; q++){
+ r = x->r[q];
+ if(r == '\n')
+ return q - x->qh + 1;
+ if(r == CTRL('D'))
+ return q - x->qh;
+ }
+ return -1;
+}
+
+void
+xaddraw(Text *x, Rune *r, int nr)
+{
+ x->raw = runerealloc(x->raw, x->nraw+nr);
+ runemove(x->raw+x->nraw, r, nr);
+ x->nraw += nr;
+}
+
+/* TODO: maybe pass what we're looking for in a string */
+void
+xlook(Text *x)
+{
+ int i, n, e;
+
+ i = x->q1;
+ n = i - x->q0;
+ e = x->nr - n;
+ if(n <= 0 || e < n)
+ return;
+
+ if(i > e)
+ i = 0;
+
+ while(runestrncmp(x->r+x->q0, x->r+i, n) != 0){
+ if(i < e)
+ i++;
+ else
+ i = 0;
+ }
+
+ xsetselect(x, i, i+n);
+ xshow(x, i);
+}
+
+Rune *snarf;
+int nsnarf;
+int snarfversion;
+int snarffd;
+
+void
+xsnarf(Text *x)
+{
+ if(x->q1 == x->q0)
+ return;
+ nsnarf = x->q1-x->q0;
+ snarf = runerealloc(snarf, nsnarf);
+ snarfversion++;
+ runemove(snarf, x->r+x->q0, nsnarf);
+ putsnarf();
+}
+
+void
+xcut(Text *x)
+{
+ if(x->q1 == x->q0)
+ return;
+ xdelete(x, x->q0, x->q1);
+ xsetselect(x, x->q0, x->q0);
+ xscrdraw(x);
+}
+
+void
+xpaste(Text *x)
+{
+ uint q0;
+
+ getsnarf();
+ if(nsnarf == 0)
+ return;
+ xcut(x);
+ q0 = x->q0;
+ if(x->rawmode && q0==x->nr){
+ xaddraw(x, snarf, nsnarf);
+ xsetselect(x, q0, q0);
+ }else{
+ q0 = xinsert(x, snarf, nsnarf, x->q0);
+ xsetselect(x, q0, q0+nsnarf);
+ }
+ xscrdraw(x);
+}
+
+void
+xsend(Text *x)
+{
+ getsnarf();
+ xsnarf(x);
+ if(nsnarf == 0)
+ return;
+ if(x->rawmode){
+ xaddraw(x, snarf, nsnarf);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!=CTRL('D'))
+ xaddraw(x, L"\n", 1);
+ }else{
+ xinsert(x, snarf, nsnarf, x->nr);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!=CTRL('D'))
+ xinsert(x, L"\n", 1, x->nr);
+ }
+ xplacetick(x, x->nr);
+}
+
+int
+xplumb(Text *w, char *src, char *dir, int maxsize)
+{
+ Plumbmsg *m;
+ static int fd = -2;
+ char buf[32];
+ uint p0, p1;
+
+ if(fd == -2)
+ fd = plumbopen("send", OWRITE|OCEXEC);
+ if(fd < 0)
+ return 0;
+ m = emalloc(sizeof(Plumbmsg));
+ m->src = estrdup(src);
+ m->dst = nil;
+ m->wdir = estrdup(dir);
+ m->type = estrdup("text");
+ p0 = w->q0;
+ p1 = w->q1;
+ if(w->q1 > w->q0)
+ m->attr = nil;
+ else{
+ while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
+ p0--;
+ while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
+ p1++;
+ snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
+ m->attr = plumbunpackattr(buf);
+ }
+ if(p1-p0 > maxsize){
+ plumbfree(m);
+ return 0; /* too large for 9P */
+ }
+ m->data = smprint("%.*S", p1-p0, w->r+p0);
+ m->ndata = strlen(m->data);
+ if(plumbsend(fd, m) < 0){
+ plumbfree(m);
+ return 1;
+ }
+ plumbfree(m);
+ return 0;
+}
--- /dev/null
+++ b/time.c
@@ -1,0 +1,114 @@
+#include "inc.h"
+
+/* taken from rio */
+
+static Channel* ctimer; /* chan(Timer*)[100] */
+static Timer *timer;
+
+static uint
+msec(void)
+{
+ return nsec()/1000000;
+}
+
+void
+timerstop(Timer *t)
+{
+ t->next = timer;
+ timer = t;
+}
+
+void
+timercancel(Timer *t)
+{
+ t->cancel = TRUE;
+}
+
+static void
+timerproc(void*)
+{
+ int i, nt, na, dt, del;
+ Timer **t, *x;
+ uint old, new;
+
+ rfork(RFFDG);
+ threadsetname("TIMERPROC");
+ t = nil;
+ na = 0;
+ nt = 0;
+ old = msec();
+ for(;;){
+ sleep(1); /* will sleep minimum incr */
+ new = msec();
+ dt = new-old;
+ old = new;
+ if(dt < 0) /* timer wrapped; go around, losing a tick */
+ continue;
+ for(i=0; i<nt; i++){
+ x = t[i];
+ x->dt -= dt;
+ del = 0;
+ if(x->cancel){
+ timerstop(x);
+ del = 1;
+ }else if(x->dt <= 0){
+ /*
+ * avoid possible deadlock if client is
+ * now sending on ctimer
+ */
+ if(nbsendul(x->c, 0) > 0)
+ del = 1;
+ }
+ if(del){
+ memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
+ --nt;
+ --i;
+ }
+ }
+ if(nt == 0){
+ x = recvp(ctimer);
+ gotit:
+ if(nt == na){
+ na += 10;
+ t = realloc(t, na*sizeof(Timer*));
+ if(t == nil)
+ abort();
+ }
+ t[nt++] = x;
+ old = msec();
+ }
+ if(nbrecv(ctimer, &x) > 0)
+ goto gotit;
+ }
+}
+
+void
+timerinit(void)
+{
+ ctimer = chancreate(sizeof(Timer*), 100);
+ proccreate(timerproc, nil, mainstacksize);
+}
+
+/*
+ * timeralloc() and timerfree() don't lock, so can only be
+ * called from the main proc.
+ */
+
+Timer*
+timerstart(int dt)
+{
+ Timer *t;
+
+ t = timer;
+ if(t)
+ timer = timer->next;
+ else{
+ t = emalloc(sizeof(Timer));
+ t->c = chancreate(sizeof(int), 0);
+ }
+ t->next = nil;
+ t->dt = dt;
+ t->cancel = FALSE;
+ sendp(ctimer, t);
+ return t;
+}