summaryrefslogtreecommitdiff
path: root/sys/src/cmd/fossil/view.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/fossil/view.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/fossil/view.c')
-rwxr-xr-xsys/src/cmd/fossil/view.c1127
1 files changed, 1127 insertions, 0 deletions
diff --git a/sys/src/cmd/fossil/view.c b/sys/src/cmd/fossil/view.c
new file mode 100755
index 000000000..c2ac99762
--- /dev/null
+++ b/sys/src/cmd/fossil/view.c
@@ -0,0 +1,1127 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include <draw.h>
+#include <event.h>
+
+/* --- tree.h */
+typedef struct Tree Tree;
+typedef struct Tnode Tnode;
+
+struct Tree
+{
+ Tnode *root;
+ Point offset;
+ Image *clipr;
+};
+
+struct Tnode
+{
+ Point offset;
+
+ char *str;
+// char *(*strfn)(Tnode*);
+// uint (*draw)(Tnode*, Image*, Image*, Point);
+ void (*expand)(Tnode*);
+ void (*collapse)(Tnode*);
+
+ uint expanded;
+ Tnode **kid;
+ int nkid;
+ void *aux;
+};
+
+typedef struct Atree Atree;
+struct Atree
+{
+ int resizefd;
+ Tnode *root;
+};
+
+Atree *atreeinit(char*);
+
+/* --- visfossil.c */
+Tnode *initxheader(void);
+Tnode *initxcache(char *name);
+Tnode *initxsuper(void);
+Tnode *initxlocalroot(char *name, u32int addr);
+Tnode *initxentry(Entry);
+Tnode *initxsource(Entry, int);
+Tnode *initxentryblock(Block*, Entry*);
+Tnode *initxdatablock(Block*, uint);
+Tnode *initxroot(char *name, uchar[VtScoreSize]);
+
+int fd;
+Header h;
+Super super;
+VtSession *z;
+VtRoot vac;
+int showinactive;
+
+/*
+ * dumbed down versions of fossil routines
+ */
+char*
+bsStr(int state)
+{
+ static char s[100];
+
+ if(state == BsFree)
+ return "Free";
+ if(state == BsBad)
+ return "Bad";
+
+ sprint(s, "%x", state);
+ if(!(state&BsAlloc))
+ strcat(s, ",Free"); /* should not happen */
+ if(state&BsVenti)
+ strcat(s, ",Venti");
+ if(state&BsClosed)
+ strcat(s, ",Closed");
+ return s;
+}
+
+char *bttab[] = {
+ "BtData",
+ "BtData+1",
+ "BtData+2",
+ "BtData+3",
+ "BtData+4",
+ "BtData+5",
+ "BtData+6",
+ "BtData+7",
+ "BtDir",
+ "BtDir+1",
+ "BtDir+2",
+ "BtDir+3",
+ "BtDir+4",
+ "BtDir+5",
+ "BtDir+6",
+ "BtDir+7",
+};
+
+char*
+btStr(int type)
+{
+ if(type < nelem(bttab))
+ return bttab[type];
+ return "unknown";
+}
+#pragma varargck argpos stringnode 1
+
+Block*
+allocBlock(void)
+{
+ Block *b;
+
+ b = mallocz(sizeof(Block)+h.blockSize, 1);
+ b->data = (void*)&b[1];
+ return b;
+}
+
+void
+blockPut(Block *b)
+{
+ free(b);
+}
+
+static u32int
+partStart(int part)
+{
+ switch(part){
+ default:
+ assert(0);
+ case PartSuper:
+ return h.super;
+ case PartLabel:
+ return h.label;
+ case PartData:
+ return h.data;
+ }
+}
+
+
+static u32int
+partEnd(int part)
+{
+ switch(part){
+ default:
+ assert(0);
+ case PartSuper:
+ return h.super+1;
+ case PartLabel:
+ return h.data;
+ case PartData:
+ return h.end;
+ }
+}
+
+Block*
+readBlock(int part, u32int addr)
+{
+ u32int start, end;
+ u64int offset;
+ int n, nn;
+ Block *b;
+ uchar *buf;
+
+ start = partStart(part);
+ end = partEnd(part);
+ if(addr >= end-start){
+ werrstr("bad addr 0x%.8ux; wanted 0x%.8ux - 0x%.8ux", addr, start, end);
+ return nil;
+ }
+
+ b = allocBlock();
+ b->addr = addr;
+ buf = b->data;
+ offset = ((u64int)(addr+start))*h.blockSize;
+ n = h.blockSize;
+ while(n > 0){
+ nn = pread(fd, buf, n, offset);
+ if(nn < 0){
+ blockPut(b);
+ return nil;
+ }
+ if(nn == 0){
+ werrstr("short read");
+ blockPut(b);
+ return nil;
+ }
+ n -= nn;
+ offset += nn;
+ buf += nn;
+ }
+ return b;
+}
+
+int vtType[BtMax] = {
+ VtDataType, /* BtData | 0 */
+ VtPointerType0, /* BtData | 1 */
+ VtPointerType1, /* BtData | 2 */
+ VtPointerType2, /* BtData | 3 */
+ VtPointerType3, /* BtData | 4 */
+ VtPointerType4, /* BtData | 5 */
+ VtPointerType5, /* BtData | 6 */
+ VtPointerType6, /* BtData | 7 */
+ VtDirType, /* BtDir | 0 */
+ VtPointerType0, /* BtDir | 1 */
+ VtPointerType1, /* BtDir | 2 */
+ VtPointerType2, /* BtDir | 3 */
+ VtPointerType3, /* BtDir | 4 */
+ VtPointerType4, /* BtDir | 5 */
+ VtPointerType5, /* BtDir | 6 */
+ VtPointerType6, /* BtDir | 7 */
+};
+
+Block*
+ventiBlock(uchar score[VtScoreSize], uint type)
+{
+ int n;
+ Block *b;
+
+ b = allocBlock();
+ memmove(b->score, score, VtScoreSize);
+ b->addr = NilBlock;
+
+ n = vtRead(z, b->score, vtType[type], b->data, h.blockSize);
+ if(n < 0){
+ fprint(2, "vtRead returns %d: %R\n", n);
+ blockPut(b);
+ return nil;
+ }
+ vtZeroExtend(vtType[type], b->data, n, h.blockSize);
+ b->l.type = type;
+ b->l.state = 0;
+ b->l.tag = 0;
+ b->l.epoch = 0;
+ return b;
+}
+
+Block*
+dataBlock(uchar score[VtScoreSize], uint type, uint tag)
+{
+ Block *b, *bl;
+ int lpb;
+ Label l;
+ u32int addr;
+
+ addr = globalToLocal(score);
+ if(addr == NilBlock)
+ return ventiBlock(score, type);
+
+ lpb = h.blockSize/LabelSize;
+ bl = readBlock(PartLabel, addr/lpb);
+ if(bl == nil)
+ return nil;
+ if(!labelUnpack(&l, bl->data, addr%lpb)){
+ werrstr("%R");
+ blockPut(bl);
+ return nil;
+ }
+ blockPut(bl);
+ if(l.type != type){
+ werrstr("type mismatch; got %d (%s) wanted %d (%s)",
+ l.type, btStr(l.type), type, btStr(type));
+ return nil;
+ }
+ if(tag && l.tag != tag){
+ werrstr("tag mismatch; got 0x%.8ux wanted 0x%.8ux",
+ l.tag, tag);
+ return nil;
+ }
+ b = readBlock(PartData, addr);
+ if(b == nil)
+ return nil;
+ b->l = l;
+ return b;
+}
+
+Entry*
+copyEntry(Entry e)
+{
+ Entry *p;
+
+ p = mallocz(sizeof *p, 1);
+ *p = e;
+ return p;
+}
+
+MetaBlock*
+copyMetaBlock(MetaBlock mb)
+{
+ MetaBlock *p;
+
+ p = mallocz(sizeof mb, 1);
+ *p = mb;
+ return p;
+}
+
+/*
+ * visualizer
+ */
+
+#pragma varargck argpos stringnode 1
+
+Tnode*
+stringnode(char *fmt, ...)
+{
+ va_list arg;
+ Tnode *t;
+
+ t = mallocz(sizeof(Tnode), 1);
+ va_start(arg, fmt);
+ t->str = vsmprint(fmt, arg);
+ va_end(arg);
+ t->nkid = -1;
+ return t;
+}
+
+void
+xcacheexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxheader();
+}
+
+Tnode*
+initxcache(char *name)
+{
+ Tnode *t;
+
+ if((fd = open(name, OREAD)) < 0)
+ sysfatal("cannot open %s: %r", name);
+
+ t = stringnode("%s", name);
+ t->expand = xcacheexpand;
+ return t;
+}
+
+void
+xheaderexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxsuper();
+ //t->kid[1] = initxlabel(h.label);
+ //t->kid[2] = initxdata(h.data);
+}
+
+Tnode*
+initxheader(void)
+{
+ u8int buf[HeaderSize];
+ Tnode *t;
+
+ if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize)
+ return stringnode("error reading header: %r");
+ if(!headerUnpack(&h, buf))
+ return stringnode("error unpacking header: %R");
+
+ t = stringnode("header "
+ "version=%#ux (%d) "
+ "blockSize=%#ux (%d) "
+ "super=%#lux (%ld) "
+ "label=%#lux (%ld) "
+ "data=%#lux (%ld) "
+ "end=%#lux (%ld)",
+ h.version, h.version, h.blockSize, h.blockSize,
+ h.super, h.super,
+ h.label, h.label, h.data, h.data, h.end, h.end);
+ t->expand = xheaderexpand;
+ return t;
+}
+
+void
+xsuperexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxlocalroot("active", super.active);
+// t->kid[1] = initxlocalroot("next", super.next);
+// t->kid[2] = initxlocalroot("current", super.current);
+}
+
+Tnode*
+initxsuper(void)
+{
+ Block *b;
+ Tnode *t;
+
+ b = readBlock(PartSuper, 0);
+ if(b == nil)
+ return stringnode("reading super: %r");
+ if(!superUnpack(&super, b->data)){
+ blockPut(b);
+ return stringnode("unpacking super: %R");
+ }
+ blockPut(b);
+ t = stringnode("super "
+ "version=%#ux "
+ "epoch=[%#ux,%#ux) "
+ "qid=%#llux "
+ "active=%#x "
+ "next=%#x "
+ "current=%#x "
+ "last=%V "
+ "name=%s",
+ super.version, super.epochLow, super.epochHigh,
+ super.qid, super.active, super.next, super.current,
+ super.last, super.name);
+ t->expand = xsuperexpand;
+ return t;
+}
+
+void
+xvacrootexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxroot("root", vac.score);
+}
+
+Tnode*
+initxvacroot(uchar score[VtScoreSize])
+{
+ Tnode *t;
+ uchar buf[VtRootSize];
+ int n;
+
+ if((n = vtRead(z, score, VtRootType, buf, VtRootSize)) < 0)
+ return stringnode("reading root %V: %R", score);
+
+ if(!vtRootUnpack(&vac, buf))
+ return stringnode("unpack %d-byte root: %R", n);
+
+ h.blockSize = vac.blockSize;
+ t = stringnode("vac version=%#ux name=%s type=%s blockSize=%ud score=%V prev=%V",
+ vac.version, vac.name, vac.type, vac.blockSize, vac.score, vac.prev);
+ t->expand = xvacrootexpand;
+ return t;
+}
+
+Tnode*
+initxlabel(Label l)
+{
+ return stringnode("label type=%s state=%s epoch=%#ux tag=%#ux",
+ btStr(l.type), bsStr(l.state), l.epoch, l.tag);
+}
+
+typedef struct Xblock Xblock;
+struct Xblock
+{
+ Tnode;
+ Block *b;
+ int (*gen)(void*, Block*, int, Tnode**);
+ void *arg;
+ int printlabel;
+};
+
+void
+xblockexpand(Tnode *tt)
+{
+ int i, j;
+ enum { Q = 32 };
+ Xblock *t = (Xblock*)tt;
+ Tnode *nn;
+
+ if(t->nkid >= 0)
+ return;
+
+ j = 0;
+ if(t->printlabel){
+ t->kid = mallocz(Q*sizeof(t->kid[0]), 1);
+ t->kid[0] = initxlabel(t->b->l);
+ j = 1;
+ }
+
+ for(i=0;; i++){
+ switch((*t->gen)(t->arg, t->b, i, &nn)){
+ case -1:
+ t->nkid = j;
+ return;
+ case 0:
+ break;
+ case 1:
+ if(j%Q == 0)
+ t->kid = realloc(t->kid, (j+Q)*sizeof(t->kid[0]));
+ t->kid[j++] = nn;
+ break;
+ }
+ }
+}
+
+int
+nilgen(void*, Block*, int, Tnode**)
+{
+ return -1;
+}
+
+Tnode*
+initxblock(Block *b, char *s, int (*gen)(void*, Block*, int, Tnode**), void *arg)
+{
+ Xblock *t;
+
+ if(gen == nil)
+ gen = nilgen;
+ t = mallocz(sizeof(Xblock), 1);
+ t->b = b;
+ t->gen = gen;
+ t->arg = arg;
+ if(b->addr == NilBlock)
+ t->str = smprint("Block %V: %s", b->score, s);
+ else
+ t->str = smprint("Block %#ux: %s", b->addr, s);
+ t->printlabel = 1;
+ t->nkid = -1;
+ t->expand = xblockexpand;
+ return t;
+}
+
+int
+xentrygen(void *v, Block *b, int o, Tnode **tp)
+{
+ Entry e;
+ Entry *ed;
+
+ ed = v;
+ if(o >= ed->dsize/VtEntrySize)
+ return -1;
+
+ entryUnpack(&e, b->data, o);
+ if(!showinactive && !(e.flags & VtEntryActive))
+ return 0;
+ *tp = initxentry(e);
+ return 1;
+}
+
+Tnode*
+initxentryblock(Block *b, Entry *ed)
+{
+ return initxblock(b, "entry", xentrygen, ed);
+}
+
+typedef struct Xentry Xentry;
+struct Xentry
+{
+ Tnode;
+ Entry e;
+};
+
+void
+xentryexpand(Tnode *tt)
+{
+ Xentry *t = (Xentry*)tt;
+
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxsource(t->e, 1);
+}
+
+Tnode*
+initxentry(Entry e)
+{
+ Xentry *t;
+
+ t = mallocz(sizeof *t, 1);
+ t->nkid = -1;
+ t->str = smprint("Entry gen=%#ux psize=%d dsize=%d depth=%d flags=%#ux size=%lld score=%V",
+ e.gen, e.psize, e.dsize, e.depth, e.flags, e.size, e.score);
+ if(e.flags & VtEntryLocal)
+ t->str = smprint("%s archive=%d snap=%d tag=%#ux", t->str, e.archive, e.snap, e.tag);
+ t->expand = xentryexpand;
+ t->e = e;
+ return t;
+}
+
+int
+ptrgen(void *v, Block *b, int o, Tnode **tp)
+{
+ Entry *ed;
+ Entry e;
+
+ ed = v;
+ if(o >= ed->psize/VtScoreSize)
+ return -1;
+
+ e = *ed;
+ e.depth--;
+ memmove(e.score, b->data+o*VtScoreSize, VtScoreSize);
+ if(memcmp(e.score, vtZeroScore, VtScoreSize) == 0)
+ return 0;
+ *tp = initxsource(e, 0);
+ return 1;
+}
+
+static int
+etype(int flags, int depth)
+{
+ uint t;
+
+ if(flags&VtEntryDir)
+ t = BtDir;
+ else
+ t = BtData;
+ return t+depth;
+}
+
+Tnode*
+initxsource(Entry e, int dowrap)
+{
+ Block *b;
+ Tnode *t, *tt;
+
+ b = dataBlock(e.score, etype(e.flags, e.depth), e.tag);
+ if(b == nil)
+ return stringnode("dataBlock: %r");
+
+ if((e.flags & VtEntryActive) == 0)
+ return stringnode("inactive Entry");
+
+ if(e.depth == 0){
+ if(e.flags & VtEntryDir)
+ tt = initxentryblock(b, copyEntry(e));
+ else
+ tt = initxdatablock(b, e.dsize);
+ }else{
+ tt = initxblock(b, smprint("%s+%d pointer", (e.flags & VtEntryDir) ? "BtDir" : "BtData", e.depth),
+ ptrgen, copyEntry(e));
+ }
+
+ /*
+ * wrap the contents of the Source in a Source node,
+ * just so it's closer to what you see in the code.
+ */
+ if(dowrap){
+ t = stringnode("Source");
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(Tnode*)*1, 1);
+ t->kid[0] = tt;
+ tt = t;
+ }
+ return tt;
+}
+
+int
+xlocalrootgen(void*, Block *b, int o, Tnode **tp)
+{
+ Entry e;
+
+ if(o >= 1)
+ return -1;
+ entryUnpack(&e, b->data, o);
+ *tp = initxentry(e);
+ return 1;
+}
+
+Tnode*
+initxlocalroot(char *name, u32int addr)
+{
+ uchar score[VtScoreSize];
+ Block *b;
+
+ localToGlobal(addr, score);
+ b = dataBlock(score, BtDir, RootTag);
+ if(b == nil)
+ return stringnode("read data block %#ux: %R", addr);
+ return initxblock(b, smprint("'%s' fs root", name), xlocalrootgen, nil);
+}
+
+int
+xvacrootgen(void*, Block *b, int o, Tnode **tp)
+{
+ Entry e;
+
+ if(o >= 3)
+ return -1;
+ entryUnpack(&e, b->data, o);
+ *tp = initxentry(e);
+ return 1;
+}
+
+Tnode*
+initxroot(char *name, uchar score[VtScoreSize])
+{
+ Block *b;
+
+ b = dataBlock(score, BtDir, RootTag);
+ if(b == nil)
+ return stringnode("read data block %V: %R", score);
+ return initxblock(b, smprint("'%s' fs root", name), xvacrootgen, nil);
+}
+Tnode*
+initxdirentry(MetaEntry *me)
+{
+ DirEntry dir;
+ Tnode *t;
+
+ if(!deUnpack(&dir, me))
+ return stringnode("deUnpack: %R");
+
+ t = stringnode("dirEntry elem=%s size=%llud data=%#lux/%#lux meta=%#lux/%#lux", dir.elem, dir.size, dir.entry, dir.gen, dir.mentry, dir.mgen);
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*1, 1);
+ t->kid[0] = stringnode(
+ "qid=%#llux\n"
+ "uid=%s gid=%s mid=%s\n"
+ "mtime=%lud mcount=%lud ctime=%lud atime=%lud\n"
+ "mode=%luo\n"
+ "plan9 %d p9path %#llux p9version %lud\n"
+ "qidSpace %d offset %#llux max %#llux",
+ dir.qid,
+ dir.uid, dir.gid, dir.mid,
+ dir.mtime, dir.mcount, dir.ctime, dir.atime,
+ dir.mode,
+ dir.plan9, dir.p9path, dir.p9version,
+ dir.qidSpace, dir.qidOffset, dir.qidMax);
+ return t;
+}
+
+int
+metaentrygen(void *v, Block*, int o, Tnode **tp)
+{
+ Tnode *t;
+ MetaBlock *mb;
+ MetaEntry me;
+
+ mb = v;
+ if(o >= mb->nindex)
+ return -1;
+ meUnpack(&me, mb, o);
+
+ t = stringnode("MetaEntry %d bytes", mb->size);
+ t->kid = mallocz(sizeof(t->kid[0])*1, 1);
+ t->kid[0] = initxdirentry(&me);
+ t->nkid = 1;
+ *tp = t;
+ return 1;
+}
+
+int
+metablockgen(void *v, Block *b, int o, Tnode **tp)
+{
+ Xblock *t;
+ MetaBlock *mb;
+
+ if(o >= 1)
+ return -1;
+
+ /* hack: reuse initxblock as a generic iterator */
+ mb = v;
+ t = (Xblock*)initxblock(b, "", metaentrygen, mb);
+ t->str = smprint("MetaBlock %d/%d space used, %d add'l free %d/%d table used%s",
+ mb->size, mb->maxsize, mb->free, mb->nindex, mb->maxindex,
+ mb->botch ? " [BOTCH]" : "");
+ t->printlabel = 0;
+ *tp = t;
+ return 1;
+}
+
+/*
+ * attempt to guess at the type of data in the block.
+ * it could just be data from a file, but we're hoping it's MetaBlocks.
+ */
+Tnode*
+initxdatablock(Block *b, uint n)
+{
+ MetaBlock mb;
+
+ if(n > h.blockSize)
+ n = h.blockSize;
+
+ if(mbUnpack(&mb, b->data, n))
+ return initxblock(b, "metadata", metablockgen, copyMetaBlock(mb));
+
+ return initxblock(b, "data", nil, nil);
+}
+
+int
+parseScore(uchar *score, char *buf, int n)
+{
+ int i, c;
+
+ memset(score, 0, VtScoreSize);
+
+ if(n < VtScoreSize*2)
+ return 0;
+ for(i=0; i<VtScoreSize*2; i++){
+ if(buf[i] >= '0' && buf[i] <= '9')
+ c = buf[i] - '0';
+ else if(buf[i] >= 'a' && buf[i] <= 'f')
+ c = buf[i] - 'a' + 10;
+ else if(buf[i] >= 'A' && buf[i] <= 'F')
+ c = buf[i] - 'A' + 10;
+ else{
+ return 0;
+ }
+
+ if((i & 1) == 0)
+ c <<= 4;
+
+ score[i>>1] |= c;
+ }
+ return 1;
+}
+
+int
+scoreFmt(Fmt *f)
+{
+ uchar *v;
+ int i;
+ u32int addr;
+
+ v = va_arg(f->args, uchar*);
+ if(v == nil){
+ fmtprint(f, "*");
+ }else if((addr = globalToLocal(v)) != NilBlock)
+ fmtprint(f, "0x%.8ux", addr);
+ else{
+ for(i = 0; i < VtScoreSize; i++)
+ fmtprint(f, "%2.2ux", v[i]);
+ }
+
+ return 0;
+}
+
+Atree*
+atreeinit(char *arg)
+{
+ Atree *a;
+ uchar score[VtScoreSize];
+
+ vtAttach();
+
+ fmtinstall('V', scoreFmt);
+ fmtinstall('R', vtErrFmt);
+
+ z = vtDial(nil, 1);
+ if(z == nil)
+ fprint(2, "warning: cannot dial venti: %R\n");
+ if(!vtConnect(z, 0)){
+ fprint(2, "warning: cannot connect to venti: %R\n");
+ z = nil;
+ }
+ a = mallocz(sizeof(Atree), 1);
+ if(strncmp(arg, "vac:", 4) == 0){
+ if(!parseScore(score, arg+4, strlen(arg+4))){
+ fprint(2, "cannot parse score\n");
+ return nil;
+ }
+ a->root = initxvacroot(score);
+ }else
+ a->root = initxcache(arg);
+ a->resizefd = -1;
+ return a;
+}
+
+/* --- tree.c */
+enum
+{
+ Nubwidth = 11,
+ Nubheight = 11,
+ Linewidth = Nubwidth*2+4,
+};
+
+uint
+drawtext(char *s, Image *m, Image *clipr, Point o)
+{
+ char *t, *nt, *e;
+ uint dy;
+
+ if(s == nil)
+ s = "???";
+
+ dy = 0;
+ for(t=s; t&&*t; t=nt){
+ if(nt = strchr(t, '\n')){
+ e = nt;
+ nt++;
+ }else
+ e = t+strlen(t);
+
+ _string(m, Pt(o.x, o.y+dy), display->black, ZP, display->defaultfont,
+ t, nil, e-t, clipr->clipr, nil, ZP, SoverD);
+ dy += display->defaultfont->height;
+ }
+ return dy;
+}
+
+void
+drawnub(Image *m, Image *clipr, Point o, Tnode *t)
+{
+ clipr = nil;
+
+ if(t->nkid == 0)
+ return;
+ if(t->nkid == -1 && t->expand == nil)
+ return;
+
+ o.y += (display->defaultfont->height-Nubheight)/2;
+ draw(m, rectaddpt(Rect(0,0,1,Nubheight), o), display->black, clipr, ZP);
+ draw(m, rectaddpt(Rect(0,0,Nubwidth,1), o), display->black, clipr, o);
+ draw(m, rectaddpt(Rect(Nubwidth-1,0,Nubwidth,Nubheight), o),
+ display->black, clipr, addpt(o, Pt(Nubwidth-1, 0)));
+ draw(m, rectaddpt(Rect(0, Nubheight-1, Nubwidth, Nubheight), o),
+ display->black, clipr, addpt(o, Pt(0, Nubheight-1)));
+
+ draw(m, rectaddpt(Rect(0, Nubheight/2, Nubwidth, Nubheight/2+1), o),
+ display->black, clipr, addpt(o, Pt(0, Nubheight/2)));
+ if(!t->expanded)
+ draw(m, rectaddpt(Rect(Nubwidth/2, 0, Nubwidth/2+1, Nubheight), o),
+ display->black, clipr, addpt(o, Pt(Nubwidth/2, 0)));
+
+}
+
+uint
+drawnode(Tnode *t, Image *m, Image *clipr, Point o)
+{
+ int i;
+ char *fs, *s;
+ uint dy;
+ Point oo;
+
+ if(t == nil)
+ return 0;
+
+ t->offset = o;
+
+ oo = Pt(o.x+Nubwidth+2, o.y);
+// if(t->draw)
+// dy = (*t->draw)(t, m, clipr, oo);
+// else{
+ fs = nil;
+ if(t->str)
+ s = t->str;
+ // else if(t->strfn)
+ // fs = s = (*t->strfn)(t);
+ else
+ s = "???";
+ dy = drawtext(s, m, clipr, oo);
+ free(fs);
+// }
+
+ if(t->expanded){
+ if(t->nkid == -1 && t->expand)
+ (*t->expand)(t);
+ oo = Pt(o.x+Nubwidth+(Linewidth-Nubwidth)/2, o.y+dy);
+ for(i=0; i<t->nkid; i++)
+ oo.y += drawnode(t->kid[i], m, clipr, oo);
+ dy = oo.y - o.y;
+ }
+ drawnub(m, clipr, o, t);
+ return dy;
+}
+
+void
+drawtree(Tree *t, Image *m, Rectangle r)
+{
+ Point p;
+
+ draw(m, r, display->white, nil, ZP);
+
+ replclipr(t->clipr, 1, r);
+ p = addpt(t->offset, r.min);
+ drawnode(t->root, m, t->clipr, p);
+}
+
+Tnode*
+findnode(Tnode *t, Point p)
+{
+ int i;
+ Tnode *tt;
+
+ if(ptinrect(p, rectaddpt(Rect(0,0,Nubwidth, Nubheight), t->offset)))
+ return t;
+ if(!t->expanded)
+ return nil;
+ for(i=0; i<t->nkid; i++)
+ if(tt = findnode(t->kid[i], p))
+ return tt;
+ return nil;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: vtree /dev/sdC0/fossil\n");
+ exits("usage");
+}
+
+Tree t;
+
+void
+eresized(int new)
+{
+ Rectangle r;
+ r = screen->r;
+ if(new && getwindow(display, Refnone) < 0)
+ fprint(2,"can't reattach to window");
+ drawtree(&t, screen, screen->r);
+}
+
+enum
+{
+ Left = 1<<0,
+ Middle = 1<<1,
+ Right = 1<<2,
+
+ MMenu = 2,
+};
+
+char *items[] = { "exit", 0 };
+enum { IExit, };
+
+Menu menu;
+
+void
+main(int argc, char **argv)
+{
+ int n;
+ char *dir;
+ Event e;
+ Point op, p;
+ Tnode *tn;
+ Mouse m;
+ int Eready;
+ Atree *fs;
+
+ ARGBEGIN{
+ case 'a':
+ showinactive = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ switch(argc){
+ default:
+ usage();
+ case 1:
+ dir = argv[0];
+ break;
+ }
+
+ fs = atreeinit(dir);
+ initdraw(0, "/lib/font/bit/lucidasans/unicode.8.font", "tree");
+ t.root = fs->root;
+ t.offset = ZP;
+ t.clipr = allocimage(display, Rect(0,0,1,1), GREY1, 1, DOpaque);
+
+ eresized(0);
+ flushimage(display, 1);
+
+ einit(Emouse);
+
+ menu.item = items;
+ menu.gen = 0;
+ menu.lasthit = 0;
+ if(fs->resizefd > 0){
+ Eready = 1<<3;
+ estart(Eready, fs->resizefd, 1);
+ }else
+ Eready = 0;
+
+ for(;;){
+ switch(n=eread(Emouse|Eready, &e)){
+ default:
+ if(Eready && n==Eready)
+ eresized(0);
+ break;
+ case Emouse:
+ m = e.mouse;
+ switch(m.buttons){
+ case Left:
+ op = t.offset;
+ p = m.xy;
+ do {
+ t.offset = addpt(t.offset, subpt(m.xy, p));
+ p = m.xy;
+ eresized(0);
+ m = emouse();
+ }while(m.buttons == Left);
+ if(m.buttons){
+ t.offset = op;
+ eresized(0);
+ }
+ break;
+ case Middle:
+ n = emenuhit(MMenu, &m, &menu);
+ if(n == -1)
+ break;
+ switch(n){
+ case IExit:
+ exits(nil);
+ }
+ break;
+ case Right:
+ do
+ m = emouse();
+ while(m.buttons == Right);
+ if(m.buttons)
+ break;
+ tn = findnode(t.root, m.xy);
+ if(tn){
+ tn->expanded = !tn->expanded;
+ eresized(0);
+ }
+ break;
+ }
+ }
+ }
+}