ref: 50a3064b6ae10e6c26e552c7cd33aa4ca98e1527
parent: a0b6286313c067b883ce1330499cf02f5cca6c0f
author: phil9 <[email protected]>
date: Sun Feb 4 08:16:00 EST 2024
make file blocks collapsible this required an almost complete rewrite as the initial logic was line oriented and lead to too many hacks to make collapsing work. While at it, changed the UI a bit to make blocks more distinctive.
--- a/vdiff.c
+++ b/vdiff.c
@@ -7,14 +7,26 @@
#include <keyboard.h>
#include <bio.h>
+enum { Meminc = 32 };
+
+typedef struct Block Block;
typedef struct Line Line;
typedef struct Col Col;
+struct Block {
+ Image *b;
+ Rectangle r;
+ Rectangle sr;
+ int v;
+ char *f;
+ Line **lines;
+ int nlines;
+};
+
struct Line {
int t;
+ int n;
char *s;
- char *f;
- int l;
};
struct Col {
@@ -46,24 +58,49 @@
Rectangle sr;
Rectangle scrollr;
Rectangle scrposr;
-Rectangle listr;
-Rectangle textr;
+Rectangle viewr;
Col cols[Ncols];
Col scrlcol;
+Image *bord;
+Image *expander[2];
+int totalh;
+int viewh;
int scrollsize;
-int lineh;
-int nlines;
int offset;
+int lineh;
int scrolling;
int oldbuttons;
-Line **lines;
-int lsize;
-int lcount;
+Block **blocks;
+int nblocks;
int maxlength;
int Δpan;
int nstrip;
const char ellipsis[] = "...";
+int ellipsisw;
+int spacew;
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ sysfatal("malloc: %r");
+ return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+ void *q;
+
+ q = realloc(p, n);
+ if(q == nil)
+ sysfatal("realloc: %r");
+ return q;
+}
+
void
plumb(char *f, int l)
{
@@ -83,7 +120,7 @@
}
void
-drawline(Rectangle r, Line *l)
+renderline(Image *b, Rectangle r, int pad, int lt, char *ls)
{
Point p;
Rune rn;
@@ -90,10 +127,10 @@
char *s;
int off, tab, nc;
- draw(screen, r, cols[l->t].bg, nil, ZP);
- p = Pt(r.min.x + Hpadding, r.min.y + (Dy(r)-font->height)/2);
- off = Δpan / stringwidth(font, " ");
- for(s = l->s, nc = -1, tab = 0; *s; nc++, tab--, off--){
+ draw(b, r, cols[lt].bg, nil, ZP);
+ p = Pt(r.min.x + pad + Hpadding, r.min.y + (Dy(r)-font->height)/2);
+ off = Δpan / spacew;
+ for(s = ls, nc = -1, tab = 0; *s; nc++, tab--, off--){
if(tab <= 0 && *s == '\t'){
tab = 4 - nc % 4;
s++;
@@ -100,37 +137,78 @@
}
if(tab > 0){
if(off <= 0)
- p = runestring(screen, p, cols[l->t].bg, ZP, font, L"█");
- }else if((p.x+Hpadding+stringwidth(font, " ")+stringwidth(font, ellipsis)>=textr.max.x)){
- string(screen, p, cols[l->t].fg, ZP, font, ellipsis);
+ p = runestring(b, p, cols[lt].bg, ZP, font, L"█");
+ }else if((p.x+Hpadding+spacew+ellipsisw>=b->r.max.x)){
+ string(b, p, cols[lt].fg, ZP, font, ellipsis);
break;
}else{
s += chartorune(&rn, s);
if(off <= 0)
- p = runestringn(screen, p, cols[l->t].fg, ZP, font, &rn, 1);
+ p = runestringn(b, p, cols[lt].fg, ZP, font, &rn, 1);
}
}
}
void
+renderblock(Block *b)
+{
+ Rectangle r, lr, br;
+ Line *l;
+ int i, pad;
+
+ pad = 0;
+ r = insetrect(b->r, 1);
+ draw(b->b, b->r, cols[Lnone].bg, nil, ZP);
+ if(b->f != nil){
+ pad = Margin;
+ lr = r;
+ lr.max.y = lineh;
+ br = rectaddpt(expander[0]->r, Pt(lr.min.x+Hpadding, lr.min.y+Vpadding));
+ border(b->b, b->r, 1, bord, ZP);
+ renderline(b->b, lr, Dx(expander[0]->r)+Hpadding, Lfile, b->f);
+ draw(b->b, br, expander[b->v], nil, ZP);
+ r.min.y += lineh;
+ }
+ if(b->v == 0)
+ return;
+ for(i = 0; i < b->nlines; i++){
+ l = b->lines[i];
+ lr = Rect(r.min.x, r.min.y+i*lineh, r.max.x, r.min.y+(i+1)*lineh);
+ renderline(b->b, lr, pad, l->t, l->s);
+ }
+}
+
+void
redraw(void)
{
- Rectangle lr;
- int i, h, y;
+ Rectangle clipr;
+ int i, h, y, ye, vmin, vmax;
+ Block *b;
draw(screen, sr, cols[Lnone].bg, nil, ZP);
draw(screen, scrollr, scrlcol.bg, nil, ZP);
- if(lcount>0){
- h = ((double)nlines/lcount)*Dy(scrollr);
- y = ((double)offset/lcount)*Dy(scrollr);
- scrposr = Rect(scrollr.min.x, scrollr.min.y+y, scrollr.max.x-1, scrollr.min.y+y+h);
+ if(viewh < totalh){
+ h = ((double)viewh/totalh)*Dy(scrollr);
+ y = ((double)offset/totalh)*Dy(scrollr);
+ ye = scrollr.min.y + y + h - 1;
+ if(ye >= scrollr.max.y)
+ ye = scrollr.max.y - 1;
+ scrposr = Rect(scrollr.min.x, scrollr.min.y+y+1, scrollr.max.x-1, ye);
}else
scrposr = Rect(scrollr.min.x, scrollr.min.y, scrollr.max.x-1, scrollr.max.y);
draw(screen, scrposr, scrlcol.fg, nil, ZP);
- for(i=0; i<nlines && offset+i<lcount; i++){
- lr = Rect(textr.min.x, textr.min.y+i*lineh, textr.max.x, textr.min.y+(i+1)*lineh);
- drawline(lr, lines[offset+i]);
+ vmin = viewr.min.y + offset;
+ vmax = viewr.max.y + offset;
+ clipr = screen->clipr;
+ replclipr(screen, 0, viewr);
+ for(i = 0; i < nblocks; i++){
+ b = blocks[i];
+ if(b->sr.min.y <= vmax && b->sr.max.y >= vmin){
+ renderblock(b);
+ draw(screen, rectaddpt(b->sr, Pt(0, -offset)), b->b, nil, ZP);
+ }
}
+ replclipr(screen, 0, clipr);
flushimage(display, 1);
}
@@ -139,8 +217,8 @@
{
int max;
- max = Hpadding + maxlength * stringwidth(font, " ") + 2 * stringwidth(font, ellipsis) - Dx(textr);
- Δpan += off * stringwidth(font, " ");
+ max = Hpadding + Margin + Hpadding + maxlength * spacew + 2 * ellipsisw - Dx(blocks[0]->r);
+ Δpan += off * spacew;
if(Δpan < 0 || max <= 0)
Δpan = 0;
else if(Δpan > max)
@@ -153,8 +231,8 @@
{
if(offset<0)
offset = 0;
- if(offset+nlines>lcount)
- offset = lcount-nlines+1;
+ if(offset+viewh>totalh)
+ offset = totalh - viewh;
}
void
@@ -162,7 +240,7 @@
{
if(off<0 && offset<=0)
return;
- if(off>0 && offset+nlines>lcount)
+ if(off>0 && offset+viewh>totalh)
return;
offset += off;
clampoffset();
@@ -169,23 +247,31 @@
redraw();
}
-int
-indexat(Point p)
+void
+blockresize(Block *b)
{
- int n;
+ int w, h;
- if (!ptinrect(p, textr))
- return -1;
- n = (p.y - textr.min.y) / lineh;
- if ((n+offset) >= lcount)
- return -1;
- return n;
+ w = Dx(viewr) - 2; /* add 2 for border */
+ h = 0 + 2;
+ if(b->f != nil)
+ h += lineh;
+ if(b->v)
+ h += b->nlines*lineh;
+ b->r = Rect(0, 0, w, h);
+ freeimage(b->b);
+ b->b = allocimage(display, b->r, screen->chan, 0, DNofill);
}
void
-eresize(void)
+eresize(int new)
{
- if(getwindow(display, Refnone)<0)
+ Rectangle listr;
+ Block *b;
+ Point p;
+ int i;
+
+ if(new && getwindow(display, Refnone)<0)
sysfatal("cannot reattach: %r");
sr = screen->r;
scrollr = sr;
@@ -192,12 +278,22 @@
scrollr.max.x = scrollr.min.x+Scrollwidth+Scrollgap;
listr = sr;
listr.min.x = scrollr.max.x;
- textr = insetrect(listr, Margin);
+ viewr = insetrect(listr, Margin);
+ viewh = Dy(viewr);
lineh = Vpadding+font->height+Vpadding;
- nlines = Dy(textr)/lineh;
- scrollsize = mousescrollsize(nlines);
- if(offset > 0 && offset+nlines>lcount)
- offset = lcount-nlines+1;
+ totalh = - Margin + Vpadding + 1;
+ p = addpt(viewr.min, Pt(0, totalh));
+ for(i = 0; i < nblocks; i++){
+ b = blocks[i];
+ blockresize(b);
+ b->sr = rectaddpt(b->r, p);
+ p.y += Margin + Dy(b->r);
+ totalh += Margin + Dy(b->r);
+ }
+ totalh = totalh - Margin + Vpadding;
+ scrollsize = viewh / 2.0;
+ if(offset > 0 && offset+viewh>totalh)
+ offset = totalh - viewh;
redraw();
}
@@ -210,22 +306,22 @@
threadexitsall(nil);
break;
case Khome:
- scroll(-1000000);
+ scroll(-totalh);
break;
case Kend:
- scroll(1000000);
+ scroll(totalh);
break;
case Kpgup:
- scroll(-nlines);
+ scroll(-viewh);
break;
case Kpgdown:
- scroll(nlines);
+ scroll(viewh);
break;
case Kup:
- scroll(-1);
+ scroll(-scrollsize);
break;
case Kdown:
- scroll(1);
+ scroll(scrollsize);
break;
case Kleft:
pan(-4);
@@ -237,10 +333,28 @@
}
void
-emouse(Mouse m)
+blockmouse(Block *b, Mouse m)
{
+ Line *l;
int n;
+ n = (m.xy.y + offset - b->sr.min.y) / lineh;
+ if(n == 0 && b->f != nil && m.buttons&1){
+ b->v = !b->v;
+ eresize(0);
+ }else if(n > 0 && m.buttons&4){
+ l = b->lines[n-1];
+ if(l->t != Lsep)
+ plumb(b->f, l->n);
+ }
+}
+
+void
+emouse(Mouse m)
+{
+ Block *b;
+ int n, i;
+
if(oldbuttons == 0 && m.buttons != 0 && ptinrect(m.xy, scrollr))
scrolling = 1;
else if(m.buttons == 0)
@@ -247,46 +361,50 @@
scrolling = 0;
if(scrolling){
+ n = 5*(m.xy.y - scrollr.min.y);
if(m.buttons&1){
- n = (m.xy.y - scrollr.min.y) / lineh;
- if(-n<lcount-offset){
- scroll(-n);
- } else {
- scroll(-lcount+offset);
- }
+ scroll(-n);
return;
}else if(m.buttons&2){
- n = (m.xy.y - scrollr.min.y) * lcount / Dy(scrollr);
- offset = n;
+ offset = (m.xy.y - scrollr.min.y) * totalh/Dy(scrollr);
clampoffset();
redraw();
}else if(m.buttons&4){
- n = (m.xy.y - scrollr.min.y) / lineh;
- if(n<lcount-offset){
- scroll(n);
- } else {
- scroll(lcount-offset);
- }
+ scroll(n);
return;
}
- }else{
- if(m.buttons&4){
- n = indexat(m.xy);
- if(n>=0 && lines[n+offset]->f != nil)
- plumb(lines[n+offset]->f, lines[n+offset]->l);
- }else if(m.buttons&8)
- scroll(-scrollsize);
- else if(m.buttons&16)
- scroll(scrollsize);
+ }else if(m.buttons&8){
+ scroll(-scrollsize);
+ }else if(m.buttons&16){
+ scroll(scrollsize);
+ }else if(m.buttons != 0 && ptinrect(m.xy, viewr)){
+ for(i = 0; i < nblocks; i++){
+ b = blocks[i];
+ if(ptinrect(addpt(m.xy, Pt(0, offset)), b->sr)){
+ blockmouse(b, m);
+ break;
+ }
+ }
}
oldbuttons = m.buttons;
}
+Image*
+ecolor(ulong n)
+{
+ Image *i;
+
+ i = allocimage(display, Rect(0,0,1,1), screen->chan, 1, n);
+ if(i == nil)
+ sysfatal("allocimage: %r");
+ return i;
+}
+
void
initcol(Col *c, ulong fg, ulong bg)
{
- c->fg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, fg);
- c->bg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, bg);
+ c->fg = ecolor(fg);
+ c->bg = ecolor(bg);
}
void
@@ -293,6 +411,7 @@
initcols(int black)
{
if(black){
+ bord = ecolor(0x888888FF^(~0xFF));
initcol(&scrlcol, DBlack, 0x999999FF^(~0xFF));
initcol(&cols[Lfile], DWhite, 0x333333FF);
initcol(&cols[Lsep], DBlack, DPurpleblue);
@@ -300,6 +419,7 @@
initcol(&cols[Ldel], DWhite, 0x3F0000FF);
initcol(&cols[Lnone], DWhite, DBlack);
}else{
+ bord = ecolor(0x888888FF);
initcol(&scrlcol, DWhite, 0x999999FF);
initcol(&cols[Lfile], DBlack, 0xEFEFEFFF);
initcol(&cols[Lsep], DBlack, 0xEAFFFFFF);
@@ -309,6 +429,62 @@
}
}
+void
+initicons(void)
+{
+ int w, h;
+ Point p[4];
+
+ w = font->height;
+ h = font->height;
+ expander[0] = allocimage(display, Rect(0, 0, w, h), screen->chan, 0, DNofill);
+ draw(expander[0], expander[0]->r, cols[Lfile].bg, nil, ZP);
+ p[0] = Pt(0.25*w, 0.25*h);
+ p[1] = Pt(0.25*w, 0.75*h);
+ p[2] = Pt(0.75*w, 0.5*h);
+ p[3] = p[0];
+ fillpoly(expander[0], p, 4, 0, bord, ZP);
+ expander[1] = allocimage(display, Rect(0, 0, w, h), screen->chan, 0, DNofill);
+ draw(expander[1], expander[1]->r, cols[Lfile].bg, nil, ZP);
+ p[0] = Pt(0.25*w, 0.25*h);
+ p[1] = Pt(0.75*w, 0.25*h);
+ p[2] = Pt(0.5*w, 0.75*h);
+ p[3] = p[0];
+ fillpoly(expander[1], p, 4, 0, bord, ZP);
+ flushimage(display, 0);
+}
+
+Block*
+addblock(void)
+{
+ Block *b;
+
+ b = emalloc(sizeof *b);
+ b->b = nil;
+ b->v = 1;
+ b->f = nil;
+ b->lines = nil;
+ b->nlines = 0;
+ if(nblocks%Meminc == 0)
+ blocks = erealloc(blocks, (nblocks+Meminc)*sizeof *blocks);
+ blocks[nblocks++] = b;
+ return b;
+}
+
+void
+addline(Block *b, int t, int n, char *s)
+{
+ Line *l;
+
+ l = emalloc(sizeof *l);
+ l->t = t;
+ l->n = n;
+ l->s = s;
+ if(b->nlines%Meminc == 0)
+ b->lines = erealloc(b->lines, (b->nlines+Meminc)*sizeof(Line*));
+ b->lines[b->nlines++] = l;
+}
+
int
linetype(char *text)
{
@@ -329,28 +505,6 @@
return type;
}
-Line*
-parseline(char *f, int n, char *s)
-{
- Line *l;
- int len;
-
- l = malloc(sizeof *l);
- if(l==nil)
- sysfatal("malloc: %r");
- l->t = linetype(s);
- l->s = s;
- l->l = n;
- if(l->t != Lfile && l->t != Lsep)
- l->f = f;
- else
- l->f = nil;
- len = strlen(s);
- if(len > maxlength)
- maxlength = len;
- return l;
-}
-
int
lineno(char *s)
{
@@ -370,48 +524,55 @@
parse(int fd)
{
Biobuf *bp;
- Line *l;
- char *s, *f, *t;
- int n, ab;
+ Block *b;
+ char *s, *f, *tab;
+ int t, n, ab, len;
+ blocks = nil;
+ nblocks = 0;
ab = 0;
n = 0;
- f = nil;
- lsize = 64;
- lcount = 0;
- lines = malloc(lsize * sizeof *lines);
- if(lines==nil)
- sysfatal("malloc: %r");
bp = Bfdopen(fd, OREAD);
if(bp==nil)
sysfatal("Bfdopen: %r");
+ b = addblock();
for(;;){
s = Brdstr(bp, '\n', 1);
if(s==nil)
break;
- l = parseline(f, n, s);
- if(l->t == Lfile && l->s[0] == '-' && strncmp(l->s+4, "a/", 2)==0)
- ab = 1;
- if(l->t == Lfile && l->s[0] == '+'){
- f = l->s+4;
- if(ab && strncmp(f, "b/", 2)==0){
- f += 1;
- if(access(f, AEXIST) < 0)
+ t = linetype(s);
+ switch(t){
+ case Lfile:
+ if(s[0] == '-'){
+ b = addblock();
+ if(strncmp(s+4, "a/", 2) == 0)
+ ab = 1;
+ }else if(s[0] == '+'){
+ f = s+4;
+ if(ab && strncmp(f, "b/", 2) == 0){
f += 1;
+ if(access(f, AEXIST) < 0)
+ f += 1;
+ }
+ tab = strchr(f, '\t');
+ if(tab != nil)
+ *tab = 0;
+ b->f = f;
}
- t = strchr(f, '\t');
- if(t!=nil)
- *t = 0;
- }else if(l->t == Lsep)
- n = lineno(l->s);
- else if(l->t == Ladd || l->t == Lnone)
+ break;
+ case Lsep:
+ n = lineno(s) - 1; /* -1 as the separator is not an actual line */
+ if(0){
+ case Ladd:
+ case Lnone:
++n;
- lines[lcount++] = l;
- if(lcount>=lsize){
- lsize *= 2;
- lines = realloc(lines, lsize*sizeof *lines);
- if(lines==nil)
- sysfatal("realloc: %r");
+ }
+ default:
+ addline(b, t, n, s);
+ len = strlen(s);
+ if(len > maxlength)
+ maxlength = len;
+ break;
}
}
}
@@ -453,7 +614,7 @@
}ARGEND;
parse(0);
- if(lcount==0){
+ if(nblocks==0){
fprint(2, "no diff\n");
exits(nil);
}
@@ -468,7 +629,10 @@
a[Eresize].c = mctl->resizec;
a[Ekeyboard].c = kctl->c;
initcols(b);
- eresize();
+ initicons();
+ spacew = stringwidth(font, " ");
+ ellipsisw = stringwidth(font, ellipsis);
+ eresize(0);
for(;;){
switch(alt(a)){
case Emouse:
@@ -475,7 +639,7 @@
emouse(m);
break;
case Eresize:
- eresize();
+ eresize(1);
break;
case Ekeyboard:
ekeyboard(k);