Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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"

enum
{
        Ctlsize = 5*12
};

char    Edel[]          = "deleted window";
char    Ebadctl[]               = "ill-formed control message";
char    Ebadaddr[]      = "bad address syntax";
char    Eaddr[]         = "address out of range";
char    Einuse[]                = "already in use";
char    Ebadevent[]     = "bad event syntax";
extern char Eperm[];

static
void
clampaddr(Window *w)
{
        if(w->addr.q0 < 0)
                w->addr.q0 = 0;
        if(w->addr.q1 < 0)
                w->addr.q1 = 0;
        if(w->addr.q0 > w->body.file->nc)
                w->addr.q0 = w->body.file->nc;
        if(w->addr.q1 > w->body.file->nc)
                w->addr.q1 = w->body.file->nc;
}

void
xfidctl(void *arg)
{
        Xfid *x;
        void (*f)(Xfid*);

        threadsetname("xfidctlthread");
        x = arg;
        for(;;){
                f = recvp(x->c);
                (*f)(x);
                flushimage(display, 1);
                sendp(cxfidfree, x);
        }
}

void
xfidflush(Xfid *x)
{
        Fcall fc;
        int i, j;
        Window *w;
        Column *c;
        Xfid *wx;

        /* search windows for matching tag */
        qlock(&row);
        for(j=0; j<row.ncol; j++){
                c = row.col[j];
                for(i=0; i<c->nw; i++){
                        w = c->w[i];
                        winlock(w, 'E');
                        wx = w->eventx;
                        if(wx!=nil && wx->tag==x->oldtag){
                                w->eventx = nil;
                                wx->flushed = TRUE;
                                sendp(wx->c, nil);
                                winunlock(w);
                                goto out;
                        }
                        winunlock(w);
                }
        }
out:
        qunlock(&row);
        respond(x, &fc, nil);
}

void
xfidopen(Xfid *x)
{
        Fcall fc;
        Window *w;
        Text *t;
        char *s;
        Rune *r;
        int m, n, q, q0, q1;

        w = x->f->w;
        t = &w->body;
        if(w){
                winlock(w, 'E');
                q = FILE(x->f->qid);
                switch(q){
                case QWaddr:
                        if(w->nopen[q]++ == 0){
                                w->addr = (Range){0,0};
                                w->limit = (Range){-1,-1};
                        }
                        break;
                case QWdata:
                case QWxdata:
                        w->nopen[q]++;
                        break;
                case QWevent:
                        if(w->nopen[q]++ == 0){
                                if(!w->isdir && w->col!=nil){
                                        w->filemenu = FALSE;
                                        winsettag(w);
                                }
                        }
                        break;
                case QWrdsel:
                        /*
                         * Use a temporary file.
                         * A pipe would be the obvious, but we can't afford the
                         * broken pipe notification.  Using the code to read QWbody
                         * is n², which should probably also be fixed.  Even then,
                         * though, we'd need to squirrel away the data in case it's
                         * modified during the operation, e.g. by |sort
                         */
                        if(w->rdselfd > 0){
                                winunlock(w);
                                respond(x, &fc, Einuse);
                                return;
                        }
                        w->rdselfd = tempfile();
                        if(w->rdselfd < 0){
                                winunlock(w);
                                respond(x, &fc, "can't create temp file");
                                return;
                        }
                        w->nopen[q]++;
                        q0 = t->q0;
                        q1 = t->q1;
                        r = fbufalloc();
                        s = fbufalloc();
                        while(q0 < q1){
                                n = q1 - q0;
                                if(n > BUFSIZE/UTFmax)
                                        n = BUFSIZE/UTFmax;
                                bufread(t->file, q0, r, n);
                                m = snprint(s, BUFSIZE+1, "%.*S", n, r);
                                if(write(w->rdselfd, s, m) != m){
                                        warning(nil, "can't write temp file for pipe command %r\n");
                                        break;
                                }
                                q0 += n;
                        }
                        fbuffree(s);
                        fbuffree(r);
                        break;
                case QWwrsel:
                        w->nopen[q]++;
                        seq++;
                        filemark(t->file);
                        cut(t, t, nil, FALSE, TRUE, nil, 0);
                        w->wrselrange = (Range){t->q1, t->q1};
                        w->nomark = TRUE;
                        break;
                case QWeditout:
                        if(editing == FALSE){
                                winunlock(w);
                                respond(x, &fc, Eperm);
                                return;
                        }
                        w->wrselrange = (Range){t->q1, t->q1};
                        break;
                }
                winunlock(w);
        }
        fc.qid = x->f->qid;
        fc.iounit = messagesize-IOHDRSZ;
        x->f->open = TRUE;
        respond(x, &fc, nil);
}

void
xfidclose(Xfid *x)
{
        Fcall fc;
        Window *w;
        int q;
        Text *t;

        w = x->f->w;
        x->f->busy = FALSE;
        if(x->f->open == FALSE){
                if(w != nil)
                        winclose(w);
                respond(x, &fc, nil);
                return;
        }

        x->f->open = FALSE;
        if(w){
                winlock(w, 'E');
                q = FILE(x->f->qid);
                switch(q){
                case QWctl:
                        if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){
                                w->ctlfid = ~0;
                                qunlock(&w->ctllock);
                        }
                        break;
                case QWdata:
                case QWxdata:
                        w->nomark = FALSE;
                        /* fall through */
                case QWaddr:
                case QWevent:   /* BUG: do we need to shut down Xfid? */
                        if(--w->nopen[q] == 0){
                                if(q == QWdata || q == QWxdata)
                                        w->nomark = FALSE;
                                if(q==QWevent && !w->isdir && w->col!=nil){
                                        w->filemenu = TRUE;
                                        winsettag(w);
                                }
                                if(q == QWevent){
                                        free(w->dumpstr);
                                        free(w->dumpdir);
                                        w->dumpstr = nil;
                                        w->dumpdir = nil;
                                }
                        }
                        break;
                case QWrdsel:
                        close(w->rdselfd);
                        w->rdselfd = 0;
                        break;
                case QWwrsel:
                        w->nomark = FALSE;
                        t = &w->body;
                        /* before: only did this if !w->noscroll, but that didn't seem right in practice */
                        textshow(t, min(w->wrselrange.q0, t->file->nc),
                                min(w->wrselrange.q1, t->file->nc), 1);
                        textscrdraw(t);
                        break;
                }
                winunlock(w);
                winclose(w);
        }
        respond(x, &fc, nil);
}

void
xfidread(Xfid *x)
{
        Fcall fc;
        int n, q;
        uint off;
        char *b;
        char buf[256];
        Window *w;

        q = FILE(x->f->qid);
        w = x->f->w;
        if(w == nil){
                fc.count = 0;
                switch(q){
                case Qcons:
                case Qlabel:
                        break;
                case Qindex:
                        xfidindexread(x);
                        return;
                default:
                        warning(nil, "unknown qid %d\n", q);
                        break;
                }
                respond(x, &fc, nil);
                return;
        }
        winlock(w, 'F');
        if(w->col == nil){
                winunlock(w);
                respond(x, &fc, Edel);
                return;
        }
        off = x->offset;
        switch(q){
        case QWaddr:
                textcommit(&w->body, TRUE);
                clampaddr(w);
                sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1);
                goto Readbuf;

        case QWbody:
                xfidutfread(x, &w->body, w->body.file->nc, QWbody);
                break;

        case QWctl:
                b = winctlprint(w, buf, 1);
                goto Readb;

        Readbuf:
                b = buf;
        Readb:
                n = strlen(b);
                if(off > n)
                        off = n;
                if(off+x->count > n)
                        x->count = n-off;
                fc.count = x->count;
                fc.data = b+off;
                respond(x, &fc, nil);
                if(b != buf)
                        free(b);
                break;

        case QWevent:
                xfideventread(x, w);
                break;

        case QWdata:
                /* BUG: what should happen if q1 > q0? */
                if(w->addr.q0 > w->body.file->nc){
                        respond(x, &fc, Eaddr);
                        break;
                }
                w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->nc);
                w->addr.q1 = w->addr.q0;
                break;

        case QWxdata:
                /* BUG: what should happen if q1 > q0? */
                if(w->addr.q0 > w->body.file->nc){
                        respond(x, &fc, Eaddr);
                        break;
                }
                w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->addr.q1);
                break;

        case QWtag:
                xfidutfread(x, &w->tag, w->tag.file->nc, QWtag);
                break;

        case QWrdsel:
                seek(w->rdselfd, off, 0);
                n = x->count;
                if(n > BUFSIZE)
                        n = BUFSIZE;
                b = fbufalloc();
                n = read(w->rdselfd, b, n);
                if(n < 0){
                        respond(x, &fc, "I/O error in temp file");
                        break;
                }
                fc.count = n;
                fc.data = b;
                respond(x, &fc, nil);
                fbuffree(b);
                break;

        default:
                sprint(buf, "unknown qid %d in read", q);
                respond(x, &fc, nil);
        }
        winunlock(w);
}

static Rune*
fullrunewrite(Xfid *x, int *inr)
{
        int q, cnt, c, nb, nr;
        Rune *r;

        q = x->f->nrpart;
        cnt = x->count;
        if(q > 0){
                memmove(x->data+q, x->data, cnt);       /* there's room; see fsysproc */
                memmove(x->data, x->f->rpart, q);
                cnt += q;
                x->f->nrpart = 0;
        }
        r = runemalloc(cnt);
        cvttorunes(x->data, cnt-UTFmax, r, &nb, &nr, nil);
        /* approach end of buffer */
        while(fullrune(x->data+nb, cnt-nb)){
                c = nb;
                nb += chartorune(&r[nr], x->data+c);
                if(r[nr])
                        nr++;
        }
        if(nb < cnt){
                memmove(x->f->rpart, x->data+nb, cnt-nb);
                x->f->nrpart = cnt-nb;
        }
        *inr = nr;
        return r;
}

void
xfidwrite(Xfid *x)
{
        Fcall fc;
        int c, qid, nb, nr, eval;
        char buf[64], *err;
        Window *w;
        Rune *r;
        Range a;
        Text *t;
        uint q0, tq0, tq1;

        qid = FILE(x->f->qid);
        w = x->f->w;
        if(w){
                c = 'F';
                if(qid==QWtag || qid==QWbody)
                        c = 'E';
                winlock(w, c);
                if(w->col == nil){
                        winunlock(w);
                        respond(x, &fc, Edel);
                        return;
                }
        }
        x->data[x->count] = 0;
        switch(qid){
        case Qcons:
                w = errorwin(x->f->mntdir, 'X');
                t=&w->body;
                goto BodyTag;

        case Qlabel:
                fc.count = x->count;
                respond(x, &fc, nil);
                break;

        case QWaddr:
                x->data[x->count] = 0;
                r = bytetorune(x->data, &nr);
                t = &w->body;
                wincommit(w, t);
                eval = TRUE;
                a = address(x->f->mntdir, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb);
                free(r);
                if(nb < nr){
                        respond(x, &fc, Ebadaddr);
                        break;
                }
                if(!eval){
                        respond(x, &fc, Eaddr);
                        break;
                }
                w->addr = a;
                fc.count = x->count;
                respond(x, &fc, nil);
                break;

        case Qeditout:
        case QWeditout:
                r = fullrunewrite(x, &nr);
                if(w)
                        err = edittext(w, w->wrselrange.q1, r, nr);
                else
                        err = edittext(nil, 0, r, nr);
                free(r);
                if(err != nil){
                        respond(x, &fc, err);
                        break;
                }
                fc.count = x->count;
                respond(x, &fc, nil);
                break;

        case QWerrors:
                w = errorwinforwin(w);
                t = &w->body;
                goto BodyTag;

        case QWbody:
        case QWwrsel:
                t = &w->body;
                goto BodyTag;

        case QWctl:
                xfidctlwrite(x, w);
                break;

        case QWdata:
                a = w->addr;
                t = &w->body;
                wincommit(w, t);
                if(a.q0>t->file->nc || a.q1>t->file->nc){
                        respond(x, &fc, Eaddr);
                        break;
                }
                r = runemalloc(x->count);
                cvttorunes(x->data, x->count, r, &nb, &nr, nil);
                if(w->nomark == FALSE){
                        seq++;
                        filemark(t->file);
                }
                q0 = a.q0;
                if(a.q1 > q0){
                        textdelete(t, q0, a.q1, TRUE);
                        w->addr.q1 = q0;
                }
                tq0 = t->q0;
                tq1 = t->q1;
                textinsert(t, q0, r, nr, TRUE);
                if(tq0 >= q0)
                        tq0 += nr;
                if(tq1 >= q0)
                        tq1 += nr;
                textsetselect(t, tq0, tq1);
                if(!t->w->noscroll)
                        textshow(t, q0, q0+nr, 0);
                textscrdraw(t);
                winsettag(w);
                free(r);
                w->addr.q0 += nr;
                w->addr.q1 = w->addr.q0;
                fc.count = x->count;
                respond(x, &fc, nil);
                break;

        case QWevent:
                xfideventwrite(x, w);
                break;

        case QWtag:
                t = &w->tag;
                goto BodyTag;

        BodyTag:
                r = fullrunewrite(x, &nr);
                if(nr > 0){
                        wincommit(w, t);
                        if(qid == QWwrsel){
                                q0 = w->wrselrange.q1;
                                if(q0 > t->file->nc)
                                        q0 = t->file->nc;
                        }else
                                q0 = t->file->nc;
                        if(qid == QWtag)
                                textinsert(t, q0, r, nr, TRUE);
                        else{
                                if(w->nomark == FALSE){
                                        seq++;
                                        filemark(t->file);
                                }
                                q0 = textbsinsert(t, q0, r, nr, TRUE, &nr);
                                textsetselect(t, t->q0, t->q1); /* insert could leave it somewhere else */
                                if(qid!=QWwrsel && !t->w->noscroll)
                                        textshow(t, q0+nr, q0+nr, 1);
                                textscrdraw(t);
                        }
                        winsettag(w);
                        if(qid == QWwrsel)
                                w->wrselrange.q1 += nr;
                        free(r);
                }
                fc.count = x->count;
                respond(x, &fc, nil);
                break;

        default:
                sprint(buf, "unknown qid %d in write", qid);
                respond(x, &fc, buf);
                break;
        }
        if(w)
                winunlock(w);
}

void
xfidctlwrite(Xfid *x, Window *w)
{
        Fcall fc;
        int i, m, n, nb, nr, nulls;
        Rune *r;
        char *err, *p, *pp, *q, *e;
        int isfbuf, scrdraw, settag;
        Text *t;

        err = nil;
        e = x->data+x->count;
        scrdraw = FALSE;
        settag = FALSE;
        isfbuf = TRUE;
        if(x->count < RBUFSIZE)
                r = fbufalloc();
        else{
                isfbuf = FALSE;
                r = emalloc(x->count*UTFmax+1);
        }
        x->data[x->count] = 0;
        textcommit(&w->tag, TRUE);
        for(n=0; n<x->count; n+=m){
                p = x->data+n;
                if(strncmp(p, "lock", 4) == 0){ /* make window exclusive use */
                        qlock(&w->ctllock);
                        w->ctlfid = x->f->fid;
                        m = 4;
                }else
                if(strncmp(p, "unlock", 6) == 0){       /* release exclusive use */
                        w->ctlfid = ~0;
                        qunlock(&w->ctllock);
                        m = 6;
                }else
                if(strncmp(p, "clean", 5) == 0){        /* mark window 'clean', seq=0 */
                        t = &w->body;
                        t->eq0 = ~0;
                        filereset(t->file);
                        t->file->mod = FALSE;
                        w->dirty = FALSE;
                        settag = TRUE;
                        m = 5;
                }else
                if(strncmp(p, "dirty", 5) == 0){        /* mark window 'dirty' */
                        t = &w->body;
                        /* doesn't change sequence number, so "Put" won't appear.  it shouldn't. */
                        t->file->mod = TRUE;
                        w->dirty = TRUE;
                        settag = TRUE;
                        m = 5;
                }else
                if(strncmp(p, "show", 4) == 0){ /* show dot */
                        t = &w->body;
                        textshow(t, t->q0, t->q1, 1);
                        m = 4;
                }else
                if(strncmp(p, "name ", 5) == 0){        /* set file name */
                        pp = p+5;
                        m = 5;
                        q = memchr(pp, '\n', e-pp);
                        if(q==nil || q==pp){
                                err = Ebadctl;
                                break;
                        }
                        *q = 0;
                        nulls = FALSE;
                        cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
                        if(nulls){
                                err = "nulls in file name";
                                break;
                        }
                        for(i=0; i<nr; i++)
                                if(r[i] <= ' '){
                                        err = "bad character in file name";
                                        goto out;
                                }
out:
                        seq++;
                        filemark(w->body.file);
                        winsetname(w, r, nr);
                        m += (q+1) - pp;
                }else
                if(strncmp(p, "dump ", 5) == 0){        /* set dump string */
                        pp = p+5;
                        m = 5;
                        q = memchr(pp, '\n', e-pp);
                        if(q==nil || q==pp){
                                err = Ebadctl;
                                break;
                        }
                        *q = 0;
                        nulls = FALSE;
                        cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
                        if(nulls){
                                err = "nulls in dump string";
                                break;
                        }
                        w->dumpstr = runetobyte(r, nr);
                        m += (q+1) - pp;
                }else
                if(strncmp(p, "dumpdir ", 8) == 0){     /* set dump directory */
                        pp = p+8;
                        m = 8;
                        q = memchr(pp, '\n', e-pp);
                        if(q==nil || q==pp){
                                err = Ebadctl;
                                break;
                        }
                        *q = 0;
                        nulls = FALSE;
                        cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
                        if(nulls){
                                err = "nulls in dump directory string";
                                break;
                        }
                        w->dumpdir = runetobyte(r, nr);
                        m += (q+1) - pp;
                }else
                if(strncmp(p, "delete", 6) == 0){       /* delete for sure */
                        colclose(w->col, w, TRUE);
                        m = 6;
                }else
                if(strncmp(p, "del", 3) == 0){  /* delete, but check dirty */
                        if(!winclean(w, TRUE)){
                                err = "file dirty";
                                break;
                        }
                        colclose(w->col, w, TRUE);
                        m = 3;
                }else
                if(strncmp(p, "get", 3) == 0){  /* get file */
                        get(&w->body, nil, nil, FALSE, XXX, nil, 0);
                        m = 3;
                }else
                if(strncmp(p, "put", 3) == 0){  /* put file */
                        put(&w->body, nil, nil, XXX, XXX, nil, 0);
                        m = 3;
                }else
                if(strncmp(p, "dot=addr", 8) == 0){     /* set dot */
                        textcommit(&w->body, TRUE);
                        clampaddr(w);
                        w->body.q0 = w->addr.q0;
                        w->body.q1 = w->addr.q1;
                        textsetselect(&w->body, w->body.q0, w->body.q1);
                        settag = TRUE;
                        m = 8;
                }else
                if(strncmp(p, "addr=dot", 8) == 0){     /* set addr */
                        w->addr.q0 = w->body.q0;
                        w->addr.q1 = w->body.q1;
                        m = 8;
                }else
                if(strncmp(p, "limit=addr", 10) == 0){  /* set limit */
                        textcommit(&w->body, TRUE);
                        clampaddr(w);
                        w->limit.q0 = w->addr.q0;
                        w->limit.q1 = w->addr.q1;
                        m = 10;
                }else
                if(strncmp(p, "nomark", 6) == 0){       /* turn off automatic marking */
                        w->nomark = TRUE;
                        m = 6;
                }else
                if(strncmp(p, "mark", 4) == 0){ /* mark file */
                        seq++;
                        filemark(w->body.file);
                        settag = TRUE;
                        m = 4;
                }else
                if(strncmp(p, "nomenu", 6) == 0){       /* turn off automatic menu */
                        w->filemenu = FALSE;
                        m = 6;
                }else
                if(strncmp(p, "menu", 4) == 0){ /* enable automatic menu */
                        w->filemenu = TRUE;
                        m = 4;
                }else
                if(strncmp(p, "noscroll", 8) == 0){     /* turn off automatic scrolling */
                        w->noscroll = TRUE;
                        m = 8;
                }else
                if(strncmp(p, "cleartag", 8) == 0){     /* wipe tag right of bar */
                        wincleartag(w);
                        settag = TRUE;
                        m = 8;
                }else
                if(strncmp(p, "scroll", 6) == 0){       /* turn on automatic scrolling (writes to body only) */
                        w->noscroll = FALSE;
                        m = 6;
                }else{
                        err = Ebadctl;
                        break;
                }
                while(p[m] == '\n')
                        m++;
        }

        if(isfbuf)
                fbuffree(r);
        else
                free(r);
        if(err)
                n = 0;
        fc.count = n;
        respond(x, &fc, err);
        if(settag)
                winsettag(w);
        if(scrdraw)
                textscrdraw(&w->body);
}

void
xfideventwrite(Xfid *x, Window *w)
{
        Fcall fc;
        int m, n;
        Rune *r;
        char *err, *p, *q;
        int isfbuf;
        Text *t;
        int c;
        uint q0, q1;

        err = nil;
        isfbuf = TRUE;
        if(x->count < RBUFSIZE)
                r = fbufalloc();
        else{
                isfbuf = FALSE;
                r = emalloc(x->count*UTFmax+1);
        }
        for(n=0; n<x->count; n+=m){
                p = x->data+n;
                w->owner = *p++;        /* disgusting */
                c = *p++;
                while(*p == ' ')
                        p++;
                q0 = strtoul(p, &q, 10);
                if(q == p)
                        goto Rescue;
                p = q;
                while(*p == ' ')
                        p++;
                q1 = strtoul(p, &q, 10);
                if(q == p)
                        goto Rescue;
                p = q;
                while(*p == ' ')
                        p++;
                if(*p++ != '\n')
                        goto Rescue;
                m = p-(x->data+n);
                if('a'<=c && c<='z')
                        t = &w->tag;
                else if('A'<=c && c<='Z')
                        t = &w->body;
                else
                        goto Rescue;
                if(q0>t->file->nc || q1>t->file->nc || q0>q1)
                        goto Rescue;

                qlock(&row);    /* just like mousethread */
                switch(c){
                case 'x':
                case 'X':
                        execute(t, q0, q1, TRUE, nil);
                        break;
                case 'l':
                case 'L':
                        look3(t, q0, q1, TRUE);
                        break;
                default:
                        qunlock(&row);
                        goto Rescue;
                }
                qunlock(&row);

        }

    Out:
        if(isfbuf)
                fbuffree(r);
        else
                free(r);
        if(err)
                n = 0;
        fc.count = n;
        respond(x, &fc, err);
        return;

    Rescue:
        err = Ebadevent;
        goto Out;
}

void
xfidutfread(Xfid *x, Text *t, uint q1, int qid)
{
        Fcall fc;
        Window *w;
        Rune *r;
        char *b, *b1;
        uint q, off, boff;
        int m, n, nr, nb;

        w = t->w;
        wincommit(w, t);
        off = x->offset;
        r = fbufalloc();
        b = fbufalloc();
        b1 = fbufalloc();
        n = 0;
        if(qid==w->utflastqid && off>=w->utflastboff && w->utflastq<=q1){
                boff = w->utflastboff;
                q = w->utflastq;
        }else{
                /* BUG: stupid code: scan from beginning */
                boff = 0;
                q = 0;
        }
        w->utflastqid = qid;
        while(q<q1 && n<x->count){
                /*
                 * Updating here avoids partial rune problem: we're always on a
                 * char boundary. The cost is we will usually do one more read
                 * than we really need, but that's better than being n^2.
                 */
                w->utflastboff = boff;
                w->utflastq = q;
                nr = q1-q;
                if(nr > BUFSIZE/UTFmax)
                        nr = BUFSIZE/UTFmax;
                bufread(t->file, q, r, nr);
                nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
                if(boff >= off){
                        m = nb;
                        if(boff+m > off+x->count)
                                m = off+x->count - boff;
                        memmove(b1+n, b, m);
                        n += m;
                }else if(boff+nb > off){
                        if(n != 0)
                                error("bad count in utfrune");
                        m = nb - (off-boff);
                        if(m > x->count)
                                m = x->count;
                        memmove(b1, b+(off-boff), m);
                        n += m;
                }
                boff += nb;
                q += nr;
        }
        fbuffree(r);
        fbuffree(b);
        fc.count = n;
        fc.data = b1;
        respond(x, &fc, nil);
        fbuffree(b1);
}

int
xfidruneread(Xfid *x, Text *t, uint q0, uint q1)
{
        Fcall fc;
        Window *w;
        Rune *r, junk;
        char *b, *b1;
        uint q, boff;
        int i, rw, m, n, nr, nb;

        w = t->w;
        wincommit(w, t);
        r = fbufalloc();
        b = fbufalloc();
        b1 = fbufalloc();
        n = 0;
        q = q0;
        boff = 0;
        while(q<q1 && n<x->count){
                nr = q1-q;
                if(nr > BUFSIZE/UTFmax)
                        nr = BUFSIZE/UTFmax;
                bufread(t->file, q, r, nr);
                nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
                m = nb;
                if(boff+m > x->count){
                        i = x->count - boff;
                        /* copy whole runes only */
                        m = 0;
                        nr = 0;
                        while(m < i){
                                rw = chartorune(&junk, b+m);
                                if(m+rw > i)
                                        break;
                                m += rw;
                                nr++;
                        }
                        if(m == 0)
                                break;
                }
                memmove(b1+n, b, m);
                n += m;
                boff += nb;
                q += nr;
        }
        fbuffree(r);
        fbuffree(b);
        fc.count = n;
        fc.data = b1;
        respond(x, &fc, nil);
        fbuffree(b1);
        return q-q0;
}

void
xfideventread(Xfid *x, Window *w)
{
        Fcall fc;
        char *b;
        int i, n;

        i = 0;
        x->flushed = FALSE;
        while(w->nevents == 0){
                if(i){
                        if(!x->flushed)
                                respond(x, &fc, "window shut down");
                        return;
                }
                w->eventx = x;
                winunlock(w);
                recvp(x->c);
                winlock(w, 'F');
                i++;
        }

        n = w->nevents;
        if(n > x->count)
                n = x->count;
        fc.count = n;
        fc.data = w->events;
        respond(x, &fc, nil);
        b = w->events;
        w->events = estrdup(w->events+n);
        free(b);
        w->nevents -= n;
}

void
xfidindexread(Xfid *x)
{
        Fcall fc;
        int i, j, m, n, nmax, isbuf, cnt, off;
        Window *w;
        char *b;
        Rune *r;
        Column *c;

        qlock(&row);
        nmax = 0;
        for(j=0; j<row.ncol; j++){
                c = row.col[j];
                for(i=0; i<c->nw; i++){
                        w = c->w[i];
                        nmax += Ctlsize + w->tag.file->nc*UTFmax + 1;
                }
        }
        nmax++;
        isbuf = (nmax<=RBUFSIZE);
        if(isbuf)
                b = (char*)x->buf;
        else
                b = emalloc(nmax);
        r = fbufalloc();
        n = 0;
        for(j=0; j<row.ncol; j++){
                c = row.col[j];
                for(i=0; i<c->nw; i++){
                        w = c->w[i];
                        /* only show the currently active window of a set */
                        if(w->body.file->curtext != &w->body)
                                continue;
                        winctlprint(w, b+n, 0);
                        n += Ctlsize;
                        m = min(RBUFSIZE, w->tag.file->nc);
                        bufread(w->tag.file, 0, r, m);
                        m = n + snprint(b+n, nmax-n-1, "%.*S", m, r);
                        while(n<m && b[n]!='\n')
                                n++;
                        b[n++] = '\n';
                }
        }
        qunlock(&row);
        off = x->offset;
        cnt = x->count;
        if(off > n)
                off = n;
        if(off+cnt > n)
                cnt = n-off;
        fc.count = cnt;
        memmove(r, b+off, cnt);
        fc.data = (char*)r;
        if(!isbuf)
                free(b);
        respond(x, &fc, nil);
        fbuffree(r);
}