#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-1)/UTFmax)
                                       n = (BUFSIZE-1)/UTFmax;
                               bufread(t->file, q0, r, n);
                               m = snprint(s, BUFSIZE, "%.*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 = -1;
                       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 scrdraw, settag;
       Text *t;

       err = nil;
       e = x->data+x->count;
       scrdraw = FALSE;
       settag = 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++;
       }

       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;
       Text *t;
       int c;
       uint q0, q1;

       err = nil;
       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:
       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 = emalloc(x->count);
       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-1)/UTFmax)
                       nr = (BUFSIZE-1)/UTFmax;
               bufread(t->file, q, r, nr);
               nb = snprint(b, BUFSIZE, "%.*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);
       free(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 = emalloc(x->count);
       n = 0;
       q = q0;
       boff = 0;
       while(q<q1 && n<x->count){
               nr = q1-q;
               if(nr > (BUFSIZE-1)/UTFmax)
                       nr = (BUFSIZE-1)/UTFmax;
               bufread(t->file, q, r, nr);
               nb = snprint(b, BUFSIZE, "%.*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);
       free(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);
}