tDhog's 9term. Updated for current libraries by Caerwyn Jones. Button-3 plumbing (like in acme) by rsc. - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit cfabc3ed1638efc186ebd26bdaa3dfb5663dff17
 (DIR) parent b8c14089d8f4be73a908f82f62fce80ed2c14a8d
 (HTM) Author: rsc <devnull@localhost>
       Date:   Sun, 23 Nov 2003 18:02:15 +0000
       
       Dhog's 9term.
       Updated for current libraries by Caerwyn Jones.
       Button-3 plumbing (like in acme) by rsc.
       
       Diffstat:
         A src/cmd/9term/9term.c               |    1380 +++++++++++++++++++++++++++++++
         A src/cmd/9term/9term.h               |     120 +++++++++++++++++++++++++++++++
         A src/cmd/9term/FreeBSD.c             |       1 +
         A src/cmd/9term/Linux.c               |      22 ++++++++++++++++++++++
         A src/cmd/9term/mkfile                |      14 ++++++++++++++
       
       5 files changed, 1537 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/9term/9term.c b/src/cmd/9term/9term.c
       t@@ -0,0 +1,1380 @@
       +#include "9term.h"
       +
       +Rectangle        scrollr;        /* scroll bar rectangle */
       +Rectangle        lastsr;                /* used for scroll bar */
       +int                holdon;                /* hold mode */
       +int                rawon;                /* raw mode */
       +int                scrolling;        /* window scrolls */
       +int                clickmsec;        /* time of last click */
       +uint                clickq0;        /* point of last click */
       +int                rcfd[2];
       +int                rcpid;
       +int                maxtab;
       +Mousectl*        mc;
       +Keyboardctl*        kc;
       +Channel*        hostc;
       +Readbuf                rcbuf[2];
       +int                mainpid;
       +int                plumbfd;
       +int                label(Rune*, int);
       +char                wdir[1024];
       +void                hangupnote(void*, char*);
       +
       +char *menu2str[] = {
       +        "cut",
       +        "paste",
       +        "snarf",
       +        "send",
       +        "scroll",
       +        "plumb",
       +        0
       +};
       +
       +Image* cols[NCOL];
       +Image* hcols[NCOL];
       +Image *plumbcolor;
       +
       +Menu menu2 =
       +{
       +        menu2str
       +};
       +
       +Text        t;
       +
       +Cursor whitearrow = {
       +        {0, 0},
       +        {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
       +         0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC, 
       +         0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
       +         0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
       +        {0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C, 
       +         0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C, 
       +         0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C, 
       +         0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
       +};
       +
       +void
       +threadmain(int argc, char *argv[])
       +{
       +        char *p;
       +
       +        rfork(RFNOTEG);
       +        mainpid = getpid();
       +        ARGBEGIN{
       +        case 'T':
       +                p = ARGF();
       +                if(p == 0)
       +                        break;
       +                maxtab = strtoul(p, 0, 0);
       +                break;
       +        case 's':
       +                scrolling++;
       +                break;
       +        }ARGEND
       +
       +        p = getenv("tabstop");
       +        if(p == 0)
       +                p = getenv("TABSTOP");
       +        if(p != 0 && maxtab <= 0)
       +                maxtab = strtoul(p, 0, 0);
       +        if(maxtab <= 0)
       +                maxtab = 8;
       +
       +        initdraw(nil, nil, "9term");
       +        notify(hangupnote);
       +
       +        mc = initmouse(nil, screen);
       +        kc = initkeyboard(nil);
       +        rcstart(rcfd);
       +        hoststart();
       +        plumbstart();
       +
       +        t.f = mallocz(sizeof(Frame), 1);
       +
       +        cols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
       +        cols[HIGH] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkyellow);
       +        cols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DYellowgreen);
       +        cols[TEXT] = display->black;
       +        cols[HTEXT] = display->black;
       +
       +        hcols[BACK] = cols[BACK];
       +        hcols[HIGH] = cols[HIGH];
       +        hcols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DMedblue);
       +        hcols[TEXT] = hcols[BORD];
       +        hcols[HTEXT] = hcols[TEXT];
       +
       +        plumbcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x006600FF);
       +
       +        draw(screen, screen->r, cols[BACK], nil, ZP);
       +        geom();
       +
       +        loop();
       +}
       +
       +void
       +hangupnote(void *a, char *msg)
       +{
       +        if(getpid() != mainpid)
       +                noted(NDFLT);
       +        if(strcmp(msg, "hangup") == 0 && rcpid != 0){
       +                postnote(PNPROC, rcpid, "hangup");
       +                noted(NDFLT);
       +        }
       +        noted(NDFLT);
       +}
       +
       +void
       +hostproc(void *arg)
       +{
       +        Channel *c;
       +        int i, n, which;
       +
       +        c = arg;
       +
       +        i = 0;
       +        for(;;){
       +                i = 1-i;        /* toggle */
       +                n = read(rcfd[0], rcbuf[i].data, sizeof rcbuf[i].data);
       +                if(n <= 0){
       +                        if(n < 0)
       +                                fprint(2, "9term: host read error: %r\n");
       +                        threadexitsall("host");
       +                }
       +                rcbuf[i].n = n;
       +                which = i;
       +                send(c, &which);
       +        }
       +}
       +
       +void
       +hoststart(void)
       +{
       +        hostc = chancreate(sizeof(int), 0);
       +        proccreate(hostproc, hostc, 1024);
       +}
       +
       +void
       +loop(void)
       +{
       +        Rune r;
       +        int i;
       +        Alt a[] = {
       +                {mc->c, &mc->m, CHANRCV},
       +                {kc->c, &r, CHANRCV},
       +                {hostc, &i, CHANRCV},
       +                {mc->resizec, nil, CHANRCV},
       +                {nil, nil, CHANEND},
       +        };
       +
       +        for(;;) {
       +                tcheck();
       +
       +                scrdraw();
       +                flushimage(display, 1);
       +                a[2].op = CHANRCV;
       +                if(!scrolling && t.qh > t.org+t.f->nchars)
       +                        a[2].op = CHANNOP;;
       +
       +                switch(alt(a)) {
       +                default:
       +                        fatal("impossible");
       +                case 0:
       +                        t.m = mc->m;
       +                        mouse();
       +                        break;
       +                case 1:
       +                        key(r);
       +                        break;
       +                case 2:
       +                        conswrite(rcbuf[i].data, rcbuf[i].n);
       +                        break;
       +                case 3:
       +                        doreshape();
       +                        break;
       +                }
       +        }
       +}
       +
       +void
       +doreshape(void)
       +{
       +        if(getwindow(display, Refnone) < 0)
       +                fatal("can't reattach to window");
       +        draw(screen, screen->r, cols[BACK], nil, ZP);
       +        geom();
       +        scrdraw();
       +}
       +
       +void
       +geom(void)
       +{
       +        Rectangle r;
       +
       +        r = screen->r;
       +        scrollr = screen->r;
       +        scrollr.max.x = r.min.x+Scrollwid;
       +        lastsr = Rect(0,0,0,0);
       +
       +        r.min.x += Scrollwid+Scrollgap;
       +
       +        frclear(t.f, 0);
       +        frinit(t.f, r, font, screen, holdon ? hcols : cols);
       +        t.f->maxtab = maxtab*stringwidth(font, "0");
       +        fill();
       +        updatesel();
       +}
       +
       +void
       +drawhold(int holdon)
       +{
       +        if(holdon)
       +                setcursor(mc, &whitearrow);
       +        else
       +                setcursor(mc, nil);
       +
       +        draw(screen, screen->r, cols[BACK], nil, ZP);
       +        geom();
       +        scrdraw();
       +}
       +
       +
       +void
       +mouse(void)
       +{
       +        int cancel, but;
       +        uint oldq0, oldq1, newq0, newq1;
       +
       +        but = t.m.buttons;
       +
       +        if(but != 1 && but != 2 && but != 4)
       +                return;
       +
       +        if (ptinrect(t.m.xy, scrollr)) {
       +                scroll(but);
       +                if(t.qh<=t.org+t.f->nchars)
       +                        consread();;
       +                return;
       +        }
       +                
       +        switch(but) {
       +        case 1:
       +                mselect();
       +                break;
       +        case 2:
       +                domenu2(2);
       +                break;
       +        case 4:
       +                /* save old selection */
       +                oldq0 = t.q0;
       +                oldq1 = t.q1;
       +
       +                /* sweep out plumb area and record it */
       +                t.f->cols[HIGH] = plumbcolor;
       +                t.f->cols[HTEXT] = display->white;
       +                mselect();
       +                newq0 = t.q0;
       +                newq1 = t.q1;
       +
       +                cancel = 0;
       +                if(t.m.buttons != 0){
       +                        while(t.m.buttons){
       +                                readmouse(mc);
       +                                t.m = mc->m;
       +                        }
       +                        cancel = 1;
       +                }
       +
       +                /* restore old selection */
       +                t.f->cols[HIGH] = cols[HIGH];
       +                t.f->cols[HTEXT] = cols[HTEXT];
       +                t.q0 = oldq0;
       +                t.q1 = oldq1;
       +                updatesel();
       +
       +                if(cancel)
       +                        break;
       +
       +                /* process plumb area */
       +                if(newq0 < newq1)
       +                        plumb(newq0, newq1);
       +                else if(oldq0 <= newq0 && newq0 < oldq1)
       +                        plumb(oldq0, oldq1);
       +                else
       +                        plumb(newq0, newq0);
       +                break;
       +        }
       +}
       +
       +void
       +mselect(void)
       +{
       +        int b, x, y;
       +        uint q0;
       +
       +        b = t.m.buttons;
       +        q0 = frcharofpt(t.f, t.m.xy) + t.org;
       +        if(t.m.msec-clickmsec<500 && clickq0 == q0 && t.q0==t.q1 && b==1){
       +                doubleclick(&t.q0, &t.q1);
       +                updatesel();
       +/*                t.t.i->flush(); */
       +                x = t.m.xy.x;
       +                y = t.m.xy.y;
       +                /* stay here until something interesting happens */
       +                do {
       +                        readmouse(mc);
       +                        t.m = mc->m;
       +                } while(t.m.buttons==b && abs(t.m.xy.x-x)<4 && abs(t.m.xy.y-y)<4);
       +                t.m.xy.x = x;        /* in case we're calling frselect */
       +                t.m.xy.y = y;
       +                clickmsec = 0;
       +        }
       +
       +        if(t.m.buttons == b) {
       +                frselect(t.f, mc);
       +                t.m = mc->m;
       +                t.q0 = t.f->p0 + t.org;
       +                t.q1 = t.f->p1 + t.org;
       +                clickmsec = t.m.msec;
       +                clickq0 = t.q0;
       +        }
       +        if((t.m.buttons != b) && (b&1)){
       +                enum {Cancut = 1, Canpaste = 2} state = Cancut | Canpaste;
       +                while(t.m.buttons){
       +                        if(t.m.buttons&2) {
       +                                if (state&Cancut) {
       +                                        snarf();
       +                                        cut();
       +                                        state = Canpaste;
       +                                }
       +                        } else if (t.m.buttons&4) {
       +                                if (state&Canpaste) {
       +                                        snarfupdate();
       +                                        if (t.nsnarf) {
       +                                                paste(t.snarf, t.nsnarf, 0);
       +                                        }
       +                                        state = Cancut|Canpaste;
       +                                }
       +                        }
       +                        readmouse(mc);
       +                        t.m = mc->m;
       +                }
       +        }
       +}
       +
       +Rune newline[] = { '\n', 0 };
       +
       +void
       +domenu2(int but)
       +{
       +        if(scrolling)
       +                menu2str[Scroll] = "noscroll";
       +        else
       +                menu2str[Scroll] = "scroll";
       +
       +        switch(menuhit(but, mc, &menu2, nil)){
       +        case -1:
       +                break;
       +        case Cut:
       +                snarf();
       +                cut();
       +                if(scrolling)
       +                        show(t.q0);
       +                break;
       +        case Paste:
       +                snarfupdate();
       +                paste(t.snarf, t.nsnarf, 0);
       +                if(scrolling)
       +                        show(t.q0);
       +                break;
       +        case Snarf:
       +                snarf();
       +                if(scrolling)
       +                        show(t.q0);
       +                break;
       +        case Send:
       +                snarf();
       +                t.q0 = t.q1 = t.nr;
       +                updatesel();
       +                snarfupdate();
       +                paste(t.snarf, t.nsnarf, 1);
       +                if(t.nsnarf == 0 || t.snarf[t.nsnarf-1] != '\n')
       +                        paste(newline, 1, 1);
       +                show(t.nr);
       +                consread();
       +                break;
       +        case Scroll:
       +                scrolling = !scrolling;
       +                if (scrolling) {
       +                        show(t.nr);
       +                        consread();
       +                }
       +                break;
       +        case Plumb:
       +                plumb(t.q0, t.q1);
       +                break;
       +        default:
       +                fatal("bad menu item");
       +        }
       +}
       +
       +void
       +key(Rune r)
       +{
       +        char buf[1];
       +
       +        if(r == 0)
       +                return;
       +        if(r==SCROLLKEY){        /* scroll key */
       +                setorigin(line2q(t.f->maxlines*2/3), 1);
       +                if(t.qh<=t.org+t.f->nchars)
       +                        consread();
       +                return;
       +        }else if(r == BACKSCROLLKEY){
       +                setorigin(backnl(t.org, t.f->maxlines*2/3), 1);
       +                return;
       +        }else if(r == CUT){
       +                snarf();
       +                cut();
       +                if(scrolling)
       +                        show(t.q0);
       +                return;
       +        }else if(r == COPY){
       +                snarf();
       +                if(scrolling)
       +                        show(t.q0);
       +                return;
       +        }else if(r == PASTE){
       +                snarfupdate();
       +                paste(t.snarf, t.nsnarf, 0);
       +                if(scrolling)
       +                        show(t.q0);
       +                return;
       +        }
       +
       +        if(rawon && t.q0==t.nr){
       +                addraw(&r, 1);
       +                return;
       +        }
       +
       +        if(r==ESC || (holdon && r==0x7F)){        /* toggle hold */
       +                holdon = !holdon;
       +                drawhold(holdon);
       +                if(!holdon)
       +                        consread();
       +                if(r == 0x1B)
       +                        return;
       +        }
       +
       +        snarf();
       +
       +        switch(r) {
       +        case 0x7F:        /* DEL: send interrupt */
       +                t.qh = t.q0 = t.q1 = t.nr;
       +                show(t.q0);
       +                buf[0] = 0x7f;
       +                if(write(rcfd[1], buf, 1) < 0)
       +                        exits(0);
       +                /* get rc to print prompt */
       +//                r = '\n';
       +//                paste(&r, 1, 1);
       +                break;
       +        case 0x08:        /* ^H: erase character */
       +        case 0x15:        /* ^U: erase line */
       +        case 0x17:        /* ^W: erase word */
       +                if (t.q0 != 0 && t.q0 != t.qh)
       +                        t.q0 -= bswidth(r);
       +                cut();
       +                break;
       +        default:
       +                paste(&r, 1, 1);
       +                break;
       +        }
       +        if(scrolling)
       +                show(t.q0);
       +}
       +
       +int
       +bswidth(Rune c)
       +{
       +        uint q, eq, stop;
       +        Rune r;
       +        int skipping;
       +
       +        /* there is known to be at least one character to erase */
       +        if(c == 0x08)        /* ^H: erase character */
       +                return 1;
       +        q = t.q0;
       +        stop = 0;
       +        if(q > t.qh)
       +                stop = t.qh;
       +        skipping = 1;
       +        while(q > stop){
       +                r = t.r[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 = 0;
       +                        else if(!eq && !skipping)
       +                                break;
       +                }
       +                --q;
       +        }
       +        return t.q0-q;
       +}
       +
       +int
       +consready(void)
       +{
       +        int i, c;
       +
       +        if(holdon)
       +                return 0;
       +
       +        if(rawon) 
       +                return t.nraw != 0;
       +
       +        /* look to see if there is a complete line */
       +        for(i=t.qh; i<t.nr; i++){
       +                c = t.r[i];
       +                if(c=='\n' || c=='\004')
       +                        return 1;
       +        }
       +        return 0;
       +}
       +
       +
       +void
       +consread(void)
       +{
       +        char buf[8000], *p;
       +        int c, width, n;
       +
       +        for(;;) {
       +                if(!consready())
       +                        return;
       +
       +                n = sizeof(buf);
       +                p = buf;
       +                while(n >= UTFmax && (t.qh<t.nr || t.nraw > 0)) {
       +                        if(t.qh == t.nr){
       +                                width = runetochar(p, &t.raw[0]);
       +                                t.nraw--;
       +                                runemove(t.raw, t.raw+1, t.nraw);
       +                        }else
       +                                width = runetochar(p, &t.r[t.qh++]);
       +                        c = *p;
       +                        p += width;
       +                        n -= width;
       +                        if(!rawon && (c == '\n' || c == '\004')) {
       +                                if(c == '\004')
       +                                        p--;
       +                                break;
       +                        }
       +                }
       +                if(n < UTFmax && t.qh<t.nr && t.r[t.qh]=='\004')
       +                        t.qh++;
       +                /* put in control-d when doing a zero length write */
       +                if(p == buf)
       +                        *p++ = '\004';
       +                if(write(rcfd[1], buf, p-buf) < 0)
       +                        exits(0);
       +/*                mallocstats(); */
       +        }
       +}
       +
       +void
       +conswrite(char *p, int n)
       +{
       +        int n2, i;
       +        Rune buf2[1000], *q;
       +
       +        /* convert to runes */
       +        i = t.npart;
       +        if(i > 0){
       +                /* handle partial runes */
       +                while(i < UTFmax && n>0) {
       +                        t.part[i] = *p;
       +                        i++;
       +                        p++;
       +                        n--;
       +                        if(fullrune(t.part, i)) {
       +                                t.npart = 0;
       +                                chartorune(buf2, t.part);
       +                                runewrite(buf2, 1);
       +                                break;
       +                        }
       +                }
       +                /* there is a little extra room in a message buf */
       +        }
       +
       +        while(n >= UTFmax || fullrune(p, n)) {
       +                n2 = nelem(buf2);
       +                q = buf2;
       +
       +                while(n2) {
       +                        if(n < UTFmax && !fullrune(p, n))
       +                                break;
       +                        i = chartorune(q, p);
       +                        p += i;
       +                        n -= i;
       +                        n2--;
       +                        q++;
       +                }
       +
       +                runewrite(buf2, q-buf2);
       +        }
       +
       +        if(n != 0) {
       +                assert(n+t.npart < UTFmax);
       +                memcpy(t.part+t.npart, p, n);
       +                t.npart += n;
       +        }
       +
       +        if(scrolling)
       +                show(t.qh);
       +}
       +
       +void
       +runewrite(Rune *r, int n)
       +{
       +        uint m;
       +        int i;
       +        uint initial;
       +        uint q0, q1;
       +        uint p0, p1;
       +        Rune *p, *q;
       +
       +        n = label(r, n);
       +        if(n == 0)
       +                return;
       +
       +        /* get ride of backspaces */
       +        initial = 0;
       +        p = q = r;
       +        for(i=0; i<n; i++) {
       +                if(*p == '\b') {
       +                        if(q == r)
       +                                initial++;
       +                        else
       +                                --q;
       +                } else if(*p)
       +                        *q++ = *p;
       +                p++;
       +        }
       +        n = q-r;
       +
       +        if(initial){
       +                /* write turned into a delete */
       +
       +                if(initial > t.qh)
       +                        initial = t.qh;
       +                q0 = t.qh-initial;
       +                q1 = t.qh;
       +
       +                runemove(t.r+q0, t.r+q1, t.nr-q1);
       +                t.nr -= initial;
       +                t.qh -= initial;
       +                if(t.q0 > q1)
       +                        t.q0 -= initial;
       +                else if(t.q0 > q0)
       +                        t.q0 = q0;
       +                if(t.q1 > q1)
       +                        t.q1 -= initial;
       +                else if(t.q1 > q0)
       +                        t.q1 = q0;
       +                if(t.org > q1)
       +                        t.org -= initial;
       +                else if(q0 < t.org+t.f->nchars){
       +                        if(t.org < q0)
       +                                p0 = q0 - t.org;
       +                        else {
       +                                t.org = q0;
       +                                p0 = 0;
       +                        }
       +                        p1 = q1 - t.org;
       +                        if(p1 > t.f->nchars)
       +                                p1 = t.f->nchars;
       +                        frdelete(t.f, p0, p1);
       +                        fill();
       +                }
       +                updatesel();
       +                return;
       +        }
       +
       +        if(t.nr>HiWater && t.qh>=t.org){
       +                m = HiWater-LoWater;
       +                if(m > t.org);
       +                        m = t.org;
       +                t.org -= m;
       +                t.qh -= m;
       +                if(t.q0 > m)
       +                        t.q0 -= m;
       +                else
       +                        t.q0 = 0;
       +                if(t.q1 > m)
       +                        t.q1 -= m;
       +                else
       +                        t.q1 = 0;
       +                t.nr -= m;
       +                runemove(t.r, t.r+m, t.nr);
       +        }
       +        t.r = runerealloc(t.r, t.nr+n);
       +        runemove(t.r+t.qh+n, t.r+t.qh, t.nr-t.qh);
       +        runemove(t.r+t.qh, r, n);
       +        t.nr += n;
       +        if(t.qh < t.org)
       +                t.org += n;
       +        else if(t.qh <= t.f->nchars+t.org)
       +                frinsert(t.f, r, r+n, t.qh-t.org);
       +        if (t.qh <= t.q0)
       +                t.q0 += n;
       +        if (t.qh <= t.q1)
       +                t.q1 += n;
       +        t.qh += n;
       +        updatesel();
       +}
       +
       +
       +void
       +cut(void)
       +{
       +        uint n, p0, p1;
       +        uint q0, q1;
       +
       +        q0 = t.q0;
       +        q1 = t.q1;
       +
       +        if (q0 < t.org && q1 >= t.org)
       +                show(q0);
       +
       +        n = q1-q0;
       +        if(n == 0)
       +                return;
       +        runemove(t.r+q0, t.r+q1, t.nr-q1);
       +        t.nr -= n;
       +        t.q0 = t.q1 = q0;
       +        if(q1 < t.qh)
       +                t.qh -= n;
       +        else if(q0 < t.qh)
       +                t.qh = q0;
       +        if(q1 < t.org)
       +                t.org -= n;
       +        else if(q0 < t.org+t.f->nchars){
       +                assert(q0 >= t.org);
       +                p0 = q0 - t.org;
       +                p1 = q1 - t.org;
       +                if(p1 > t.f->nchars)
       +                        p1 = t.f->nchars;
       +                frdelete(t.f, p0, p1);
       +                fill();
       +        }
       +        updatesel();
       +}
       +
       +void
       +snarfupdate(void)
       +{
       +
       +        char *pp;
       +        int n, i;
       +        Rune *p;
       +
       +        pp = getsnarf();
       +        n = strlen(pp);
       +        if(n <= 0) {
       +                 /*t.nsnarf = 0;*/
       +                return;
       +        }
       +        t.snarf = runerealloc(t.snarf, n);
       +        for(i=0,p=t.snarf; i<n; p++)
       +                i += chartorune(p, pp+i);
       +        t.nsnarf = p-t.snarf;
       +
       +}
       +
       +void
       +snarf(void)
       +{
       +        char buf[SnarfSize], *p;
       +        int i, n;
       +        Rune *rp;
       +
       +        if(t.q1 == t.q0)
       +                return;
       +        n = t.q1-t.q0;
       +        t.snarf =  runerealloc(t.snarf, n);
       +        for(i=0,p=buf,rp=t.snarf; i<n && p < buf + SnarfSize-UTFmax; i++){
       +                *rp++ = *(t.r+t.q0+i);
       +                p += runetochar(p, t.r+t.q0+i);
       +        }
       +        t.nsnarf = rp-t.snarf;
       +        *p = '\0';
       +        putsnarf(buf);
       +}
       +
       +void
       +paste(Rune *r, int n, int advance)
       +{
       +        uint m;
       +        uint q0;
       +
       +        if(rawon && t.q0==t.nr){
       +                addraw(r, n);
       +                return;
       +        }
       +
       +        cut();
       +        if(n == 0)
       +                return;
       +        if(t.nr>HiWater && t.q0>=t.org && t.q0>=t.qh){
       +                m = HiWater-LoWater;
       +                if(m > t.org)
       +                        m = t.org;
       +                if(m > t.qh);
       +                        m = t.qh;
       +                t.org -= m;
       +                t.qh -= m;
       +                t.q0 -= m;
       +                t.q1 -= m;
       +                t.nr -= m;
       +                runemove(t.r, t.r+m, t.nr);
       +        }
       +        t.r = runerealloc(t.r, t.nr+n);
       +        q0 = t.q0;
       +        runemove(t.r+q0+n, t.r+q0, t.nr-q0);
       +        runemove(t.r+q0, r, n);
       +        t.nr += n;
       +        if(q0 < t.qh)
       +                t.qh += n;
       +        else
       +                consread();
       +        if(q0 < t.org)
       +                t.org += n;
       +        else if(q0 <= t.f->nchars+t.org)
       +                frinsert(t.f, r, r+n, q0-t.org);
       +        if(advance)
       +                t.q0 += n;
       +        t.q1 += n;
       +        updatesel();
       +}
       +
       +void
       +fill(void)
       +{
       +        if (t.f->nlines >= t.f->maxlines)
       +                return;
       +        frinsert(t.f, t.r + t.org + t.f->nchars, t.r + t.nr, t.f->nchars);
       +}
       +
       +void
       +updatesel(void)
       +{
       +        Frame *f;
       +        uint n;
       +
       +        f = t.f;
       +        if(t.org+f->p0 == t.q0 && t.org+f->p1 == t.q1)
       +                return;
       +
       +        n = t.f->nchars;
       +
       +        frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 0);
       +        if (t.q0 >= t.org)
       +                f->p0 = t.q0-t.org;
       +        else
       +                f->p0 = 0;
       +        if(f->p0 > n)
       +                f->p0 = n;
       +        if (t.q1 >= t.org)
       +                f->p1 = t.q1-t.org;
       +        else
       +                f->p1 = 0;
       +        if(f->p1 > n)
       +                f->p1 = n;
       +        frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 1);
       +
       +/*
       +        if(t.qh<=t.org+t.f.nchars && t.cwqueue != 0)
       +                t.cwqueue->wakeup <-= 0;
       +*/
       +
       +        tcheck();
       +}
       +
       +void
       +show(uint q0)
       +{
       +        int nl;
       +        uint q, oq;
       +
       +        if(cansee(q0))
       +                return;
       +        
       +        if (q0<t.org)
       +                nl = t.f->maxlines/5;
       +        else
       +                nl = 4*t.f->maxlines/5;
       +        q = backnl(q0, nl);
       +        /* avoid going in the wrong direction */
       +        if (q0>t.org && q<t.org)
       +                q = t.org;
       +        setorigin(q, 0);
       +        /* keep trying until q0 is on the screen */
       +        while(!cansee(q0)) {
       +                assert(q0 >= t.org);
       +                oq = q;
       +                q = line2q(t.f->maxlines-nl);
       +                assert(q > oq);
       +                setorigin(q, 1);
       +        }
       +}
       +
       +int
       +cansee(uint q0)
       +{
       +        uint qe;
       +
       +        qe = t.org+t.f->nchars;
       +
       +        if(q0>=t.org && q0 < qe)
       +                return 1;
       +        if (q0 != qe)
       +                return 0;
       +        if (t.f->nlines < t.f->maxlines)
       +                return 1;
       +        if (q0 > 0 && t.r[t.nr-1] == '\n')
       +                return 0;
       +        return 1;
       +}
       +
       +
       +void
       +setorigin(uint org, int exact)
       +{
       +        int i, a;
       +        uint n;
       +        
       +        if(org>0 && !exact){
       +                /* try and start after a newline */
       +                /* don't try harder than 256 chars */
       +                for(i=0; i<256 && org<t.nr; i++){
       +                        if(t.r[org-1] == '\n')
       +                                break;
       +                        org++;
       +                }
       +        }
       +        a = org-t.org;
       +
       +        if(a>=0 && a<t.f->nchars)
       +                frdelete(t.f, 0, a);
       +        else if(a<0 && -a<100*t.f->maxlines){
       +                n = t.org - org;
       +                frinsert(t.f, t.r+org, t.r+org+n, 0);
       +        }else
       +                frdelete(t.f, 0, t.f->nchars);
       +        t.org = org;
       +        fill();
       +        updatesel();
       +}
       +
       +
       +uint
       +line2q(uint n)
       +{
       +        Frame *f;
       +
       +        f = t.f;
       +        return frcharofpt(f, Pt(f->r.min.x, f->r.min.y + n*font->height))+t.org;
       +}
       +
       +uint
       +backnl(uint p, uint n)
       +{
       +        int i, j;
       +
       +        for (i = n;; i--) {
       +                /* at 256 chars, call it a line anyway */
       +                for(j=256; --j>0 && p>0; p--)
       +                        if(t.r[p-1]=='\n')
       +                                break;
       +                if (p == 0 || i == 0)
       +                        return p;
       +                p--;
       +        }
       +        return 0; /* alef bug */
       +}
       +
       +
       +void
       +addraw(Rune *r, int nr)
       +{
       +        t.raw = runerealloc(t.raw, t.nraw+nr);
       +        runemove(t.raw+t.nraw, r, nr);
       +        t.nraw += nr;
       +/*
       +        if(t.crqueue != nil)
       +                t.crqueue->wakeup <-= 0;
       +*/        
       +}
       +
       +
       +Rune left1[] =  { '{', '[', '(', '<', 0xab, 0 };
       +Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
       +Rune left2[] =  { '\n', 0 };
       +Rune left3[] =  { '\'', '"', '`', 0 };
       +
       +Rune *left[] = {
       +        left1,
       +        left2,
       +        left3,
       +        0
       +};
       +
       +Rune *right[] = {
       +        right1,
       +        left2,
       +        left3,
       +        0
       +};
       +
       +void
       +doubleclick(uint *q0, uint *q1)
       +{
       +        int c, i;
       +        Rune *r, *l, *p;
       +        uint q;
       +
       +        for(i=0; left[i]!=0; i++){
       +                q = *q0;
       +                l = left[i];
       +                r = right[i];
       +                /* try matching character to left, looking right */
       +                if(q == 0)
       +                        c = '\n';
       +                else
       +                        c = t.r[q-1];
       +                p = strrune(l, c);
       +                if(p != 0){
       +                        if(clickmatch(c, r[p-l], 1, &q))
       +                                *q1 = q-(c!='\n');
       +                        return;
       +                }
       +                /* try matching character to right, looking left */
       +                if(q == t.nr)
       +                        c = '\n';
       +                else
       +                        c = t.r[q];
       +                p = strrune(r, c);
       +                if(p != 0){
       +                        if(clickmatch(c, l[p-r], -1, &q)){
       +                                *q1 = *q0+(*q0<t.nr && c=='\n');
       +                                *q0 = q;
       +                                if(c!='\n' || q!=0 || t.r[0]=='\n')
       +                                        (*q0)++;
       +                        }
       +                        return;
       +                }
       +        }
       +        /* try filling out word to right */
       +        while(*q1<t.nr && isalnum(t.r[*q1]))
       +                (*q1)++;
       +        /* try filling out word to left */
       +        while(*q0>0 && isalnum(t.r[*q0-1]))
       +                (*q0)--;
       +}
       +
       +void
       +plumbclick(uint *q0, uint *q1)
       +{
       +        while(*q1<t.nr && !isspace(t.r[*q1]))
       +                (*q1)++;
       +        while(*q0>0 && !isspace(t.r[*q0-1]))
       +                (*q0)--;
       +}
       +
       +int
       +clickmatch(int cl, int cr, int dir, uint *q)
       +{
       +        Rune c;
       +        int nest;
       +
       +        nest = 1;
       +        for(;;){
       +                if(dir > 0){
       +                        if(*q == t.nr)
       +                                break;
       +                        c = t.r[*q];
       +                        (*q)++;
       +                }else{
       +                        if(*q == 0)
       +                                break;
       +                        (*q)--;
       +                        c = t.r[*q];
       +                }
       +                if(c == cr){
       +                        if(--nest==0)
       +                                return 1;
       +                }else if(c == cl)
       +                        nest++;
       +        }
       +        return cl=='\n' && nest==1;
       +}
       +
       +void
       +rcstart(int fd[2])
       +{
       +        int pid;
       +        char *argv[3];
       +        char slave[256];
       +        int sfd;
       +
       +        argv[0] = "rc";
       +        argv[1] = "-i";
       +        argv[2] = 0;
       +
       +        getpts(fd, slave);
       +        switch(pid = fork()) {
       +        case 0:
       +                putenv("TERM=9term");
       +                close(fd[1]);
       +                setsid();
       +                sfd = open(slave, ORDWR);
       +        //        ioctl(sfd, I_PUSH, "ptem");
       +        //        ioctl(sfd, I_PUSH, "ldterm");
       +                dup(sfd, 0);
       +                dup(sfd, 1);
       +                dup(sfd, 2);
       +                execvp(argv[0], argv);
       +                break;
       +        case -1:
       +                fatal("proc failed: %r");
       +                break;
       +        }
       +        close(fd[0]);
       +        fd[0] = fd[1];
       +
       +        rcpid = pid;
       +}
       +
       +void
       +tcheck(void)
       +{
       +        Frame *f;
       +                
       +        f = t.f;
       +
       +        assert(t.q0 <= t.q1 && t.q1 <= t.nr);
       +        assert(t.org <= t.nr && t.qh <= t.nr);
       +        assert(f->p0 <= f->p1 && f->p1 <= f->nchars);
       +        assert(t.org + f->nchars <= t.nr);
       +        assert(t.org+f->nchars==t.nr || (f->nlines >= f->maxlines));
       +}
       +
       +Rune*
       +strrune(Rune *s, Rune c)
       +{
       +        Rune c1;
       +
       +        if(c == 0) {
       +                while(*s++)
       +                        ;
       +                return s-1;
       +        }
       +
       +        while(c1 = *s++)
       +                if(c1 == c)
       +                        return s-1;
       +        return 0;
       +}
       +
       +void
       +scrdraw(void)
       +{
       +        Rectangle r, r1, r2;
       +        static Image *scrx;
       +        
       +        r = scrollr;
       +        r.min.x += 1;        /* border between margin and bar */
       +        r1 = r;
       +        if(scrx==0 || scrx->r.max.y < r.max.y){
       +                if(scrx)
       +                        freeimage(scrx);
       +                scrx = allocimage(display, Rect(0, 0, 32, r.max.y), screen->chan, 1, DPaleyellow);
       +                if(scrx == 0)
       +                        fatal("scroll balloc");
       +        }
       +        r1.min.x = 0;
       +        r1.max.x = Dx(r);
       +        r2 = scrpos(r1, t.org, t.org+t.f->nchars, t.nr);
       +        if(!eqrect(r2, lastsr)){
       +                lastsr = r2;
       +                draw(scrx, r1, cols[BORD], nil, ZP);
       +                draw(scrx, r2, cols[BACK], nil, r2.min);
       +//                r2 = r1;
       +//                r2.min.x = r2.max.x-1;
       +//                draw(scrx, r2, cols[BORD], nil, ZP);
       +                draw(screen, r, scrx, nil, r1.min);
       +        }
       +}
       +
       +Rectangle
       +scrpos(Rectangle r, ulong p0, ulong p1, ulong tot)
       +{
       +        long h;
       +        Rectangle q;
       +
       +        q = insetrect(r, 1);
       +        h = q.max.y-q.min.y;
       +        if(tot == 0)
       +                return q;
       +        if(tot > 1024L*1024L)
       +                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
       +scroll(int but)
       +{
       +        uint p0, oldp0;
       +        Rectangle s;
       +        int x, y, my, h, first, exact;
       +
       +        s = insetrect(scrollr, 1);
       +        h = s.max.y-s.min.y;
       +        x = (s.min.x+s.max.x)/2;
       +        oldp0 = ~0;
       +        first = 1;
       +        do{
       +                if(t.m.xy.x<s.min.x || s.max.x<=t.m.xy.x){
       +                        readmouse(mc);
       +                        t.m = mc->m;
       +                }else{
       +                        my = t.m.xy.y;
       +                        if(my < s.min.y)
       +                                my = s.min.y;
       +                        if(my >= s.max.y)
       +                                my = s.max.y;
       +//                        if(!eqpt(t.m.xy, Pt(x, my)))
       +//                                cursorset(Pt(x, my));
       +                        exact = 1;
       +                        if(but == 2){
       +                                y = my;
       +                                if(y > s.max.y-2)
       +                                        y = s.max.y-2;
       +                                if(t.nr > 1024*1024)
       +                                        p0 = ((t.nr>>10)*(y-s.min.y)/h)<<10;
       +                                else
       +                                        p0 = t.nr*(y-s.min.y)/h;
       +                                exact = 0;
       +                        } else if(but == 1)
       +                                p0 = backnl(t.org, (my-s.min.y)/font->height);
       +                        else 
       +                                p0 = t.org+frcharofpt(t.f, Pt(s.max.x, my));
       +
       +                        if(oldp0 != p0)
       +                                setorigin(p0, exact);
       +                        oldp0 = p0;
       +                        scrdraw();
       +                        readmouse(mc);
       +                        t.m = mc->m;
       +                }
       +        }while(t.m.buttons & (1<<(but-1)));
       +}
       +
       +void
       +plumbstart(void)
       +{
       +        char buf[256];
       +        snprint(buf, sizeof buf,  "%s/mnt/plumb", getenv("HOME"));
       +        if((plumbfd = plumbopen(buf, OWRITE)) < 0)
       +                fatal("plumbopen");
       +}
       +
       +void
       +plumb(uint q0, uint q1)
       +{
       +        Plumbmsg *pm;
       +        char *p;
       +        int i, p0, n;
       +        char cbuf[100];
       +
       +        pm = malloc(sizeof(Plumbmsg));
       +        pm->src = strdup("9term");
       +        pm->dst = 0;
       +        pm->wdir = strdup(wdir);
       +        pm->type = strdup("text");
       +        if(q1 > q0)
       +                pm->attr = nil;
       +        else{
       +                p0 = q0;
       +                plumbclick(&q0, &q1);
       +                sprint(cbuf, "click=%d", p0-q0);
       +                pm->attr = plumbunpackattr(cbuf);
       +        }
       +        if(q0==q1){
       +                plumbfree(pm);
       +                return;
       +        }
       +        pm->data = malloc(SnarfSize);
       +        n = q1 - q0;
       +        for(i=0,p=pm->data; i<n && p < pm->data + SnarfSize-UTFmax; i++)
       +                p += runetochar(p, t.r+q0+i);
       +        *p = '\0';
       +        pm->ndata = strlen(pm->data);
       +        plumbsend(plumbfd, pm);
       +        plumbfree(pm);
       +}
       +
       +/*
       + * Process in-band messages about window title changes.
       + * The messages are of the form:
       + *
       + *        \033];xxx\007
       + *
       + * where xxx is the new directory.  This format was chosen
       + * because it changes the label on xterm windows.
       + */
       +int
       +label(Rune *sr, int n)
       +{
       +        Rune *sl, *el, *er, *r;
       +
       +        er = sr+n;
       +        for(r=er-1; r>=sr; r--)
       +                if(*r == '\007')
       +                        break;
       +        if(r < sr)
       +                return n;
       +
       +        el = r+1;
       +        if(el-sr > sizeof wdir)
       +                sr = el - sizeof wdir;
       +        for(sl=el-3; sl>=sr; sl--)
       +                if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
       +                        break;
       +        if(sl < sr)
       +                return n;
       +
       +        snprint(wdir, sizeof wdir, "%.*S", (el-1)-(sl+3), sl+3);
       +        drawsetlabel(display, wdir);
       +
       +        runemove(sl, el, er-el);
       +        n -= (el-sl);
       +        return n;
       +}
       +
 (DIR) diff --git a/src/cmd/9term/9term.h b/src/cmd/9term/9term.h
       t@@ -0,0 +1,120 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <ctype.h>
       +#include <draw.h>
       +#include <thread.h>
       +#include <mouse.h>
       +#include <cursor.h>
       +#include <keyboard.h>
       +#include <frame.h>
       +#include <plumb.h>
       +#include <termios.h>
       +
       +#define fatal        sysfatal
       +
       +typedef struct Text        Text;
       +typedef struct Readbuf        Readbuf;
       +
       +enum
       +{
       +        /* these are chosen to use malloc()'s properties well */
       +        HiWater        = 640000,        /* max size of history */
       +        LoWater        = 330000,        /* min size of history after max'ed */
       +};
       +
       +/* various geometric paramters */
       +enum
       +{
       +        Scrollwid         = 12,                /* width of scroll bar */
       +        Scrollgap         = 4,                /* gap right of scroll bar */
       +        Maxtab                = 4,
       +};
       +
       +enum
       +{
       +        Cut,
       +        Paste,
       +        Snarf,
       +        Send,
       +        Scroll,
       +        Plumb,
       +};
       +
       +
       +#define        SCROLLKEY        Kdown
       +#define        ESC                0x1B
       +#define CUT                0x18        /* ctrl-x */                
       +#define COPY                0x03        /* crtl-c */
       +#define PASTE                0x16        /* crtl-v */
       +#define BACKSCROLLKEY        Kup
       +
       +#define        READBUFSIZE 8192
       +
       +struct Text
       +{
       +        Frame                *f;                /* frame ofr terminal */
       +        Mouse                m;
       +        uint                nr;                /* num of runes in term */
       +        Rune                *r;                /* runes for term */
       +        uint                nraw;                /* num of runes in raw buffer */
       +        Rune                *raw;                /* raw buffer */
       +        uint                org;                /* first rune on the screen */
       +        uint                q0;                /* start of selection region */
       +        uint                q1;                /* end of selection region */
       +        uint                qh;                /* unix point */
       +        int                npart;                /* partial runes read from console */
       +        char                part[UTFmax];        
       +        int                nsnarf;                /* snarf buffer */
       +        Rune                *snarf;
       +};
       +
       +struct Readbuf
       +{
       +        short        n;                                /* # bytes in buf */
       +        uchar        data[READBUFSIZE];                /* data bytes */
       +};
       +
       +void        mouse(void);
       +void        domenu2(int);
       +void        loop(void);
       +void        geom(void);
       +void        fill(void);
       +void        tcheck(void);
       +void        updatesel(void);
       +void        doreshape(void);
       +void        rcstart(int fd[2]);
       +void        runewrite(Rune*, int);
       +void        consread(void);
       +void        conswrite(char*, int);
       +int        bswidth(Rune c);
       +void        cut(void);
       +void        paste(Rune*, int, int);
       +void        snarfupdate(void);
       +void        snarf(void);
       +void        show(uint);
       +void        key(Rune);
       +void        setorigin(uint org, int exact);
       +uint        line2q(uint);
       +uint        backnl(uint, uint);
       +int        cansee(uint);
       +uint        backnl(uint, uint);
       +void        addraw(Rune*, int);
       +void        mselect(void);
       +void        doubleclick(uint *q0, uint *q1);
       +int        clickmatch(int cl, int cr, int dir, uint *q);
       +Rune        *strrune(Rune *s, Rune c);
       +int        consready(void);
       +Rectangle scrpos(Rectangle r, ulong p0, ulong p1, ulong tot);
       +void        scrdraw(void);
       +void        scroll(int);
       +void        hostproc(void *arg);
       +void        hoststart(void);
       +void        pdx(int, char*, int);
       +void        plumbstart(void);
       +void        plumb(uint, uint);
       +void        plumbclick(uint*, uint*);
       +int        getpts(int fd[], char *slave);
       +
       +#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))
 (DIR) diff --git a/src/cmd/9term/FreeBSD.c b/src/cmd/9term/FreeBSD.c
       t@@ -0,0 +1 @@
       +#include "Linux.c"
 (DIR) diff --git a/src/cmd/9term/Linux.c b/src/cmd/9term/Linux.c
       t@@ -0,0 +1,22 @@
       +#include "9term.h"
       +
       +void
       +pdx(int pid, char *wdir, int bufn)
       +{
       +        char path[256];
       +        int n;
       +
       +        snprint(path, sizeof path, "/proc/%d/cwd", pid);
       +        n = readlink(path, wdir, bufn);
       +        if(n < 0)
       +                n = 0;
       +        wdir[n] = '\0';
       +}
       +
       +int
       +getpts(int fd[], char *slave)
       +{
       +
       +        openpty(&fd[1], &fd[0], slave, 0, 0);
       +        return 0;
       +}
 (DIR) diff --git a/src/cmd/9term/mkfile b/src/cmd/9term/mkfile
       t@@ -0,0 +1,14 @@
       +PLAN9=../../..
       +<$PLAN9/src/mkhdr
       +
       +TARG=9term
       +
       +OFILES=\
       +        9term.$O\
       +        $SYSNAME.$O\
       +
       +<$PLAN9/src/mkone
       +
       +LDFLAGS=-lframe -ldraw -lplumb -lthread -l9 -lfmt -lutf -L$X11/lib -lX11 -lutil
       +
       +