ref: 4dbb99f478d68eaeea4c043a839454cb807c2c7d
dir: /sys/src/cmd/acme/xfid.c/
#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; xfidlogflush(x); /* 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; q = FILE(x->f->qid); if(w){ winlock(w, 'E'); 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); } else{ switch(q){ case Qlog: xfidlogopen(x); break; } } 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; case Qlog: xfidlogread(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, "font ", 5) == 0){ /* execute font command */ 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 font string"; break; } fontx(&w->body, nil, nil, FALSE, XXX, 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 if(strncmp(p, "scratch", 7) == 0){ /* mark as a scratch file */ w->isscratch = TRUE; m = 7; }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); }