ref: d4cf8be779e7eab5fd6667ef870b6e620a1d9709
dir: /sys/src/cmd/vdiff.c/
#include <u.h>
#include <libc.h>
#include <plumb.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <bio.h>
enum { Meminc = 32 };
typedef struct Block Block;
typedef struct Line Line;
typedef struct Col Col;
typedef struct Patch Patch;
struct Block {
Rectangle r;
Rectangle sr;
int v;
char *f;
Line **lines;
int nlines;
};
struct Line {
int t;
int n;
char *s;
};
struct Col {
Image *bg;
Image *fg;
};
struct Patch {
char *name;
Block **blocks;
int nblocks;
};
enum
{
Lfile = 0,
Lsep,
Ladd,
Ldel,
Lnone,
Ncols,
Lterm = -1,
Lhash = -2,
};
enum
{
Mcollapse,
Mexpand,
Nmenu,
};
enum
{
Scrollwidth = 12,
Scrollgap = 2,
Margin = 8,
Hpadding = 4,
Vpadding = 2,
};
Mousectl *mctl;
Keyboardctl *kctl;
Rectangle sr;
Rectangle scrollr;
Rectangle scrposr;
Rectangle viewr;
Col cols[Ncols];
Col scrlcol;
Image *trlcol;
Image *bord;
Image *expander[2];
Image *fb;
int totalh;
int viewh;
int scrollsize;
int offset;
int lineh;
int scrolling;
int oldbuttons;
Patch *patches;
int npatches;
Patch *cur;
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)
{
int fd, i;
char *p, wd[256], addr[300]={0};
fd = plumbopen("send", OWRITE);
if(fd<0)
return;
for(i = 0; i < nstrip; i++)
if((p = strchr(f, '/')) != nil)
f = p+1;
getwd(wd, sizeof wd);
snprint(addr, sizeof addr, "%s:%d", f, l);
plumbsendtext(fd, "vdiff", "edit", wd, addr);
close(fd);
}
void
renderline(Image *b, Rectangle r, int pad, int lt, char *ls)
{
Point p0, p;
Rectangle trlr;
Rune rn;
char *s;
int off, tab, nc, hastrl;
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;
hastrl = 0;
for(s = ls, nc = -1, tab = 0; *s || tab > 0; nc++, tab--, off--){
if(tab <= 0 && *s && *s == '\t'){
tab = 4 - nc % 4;
s++;
}
if(tab > 0){
p0 = p;
if(off <= 0)
p = runestring(b, p, cols[lt].bg, ZP, font, L"█");
if(hastrl)
trlr.max = addpt(p, Pt(0, font->height));
else{
trlr.min = p0;
hastrl = 1;
}
}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);
p0 = p;
if(off <= 0)
p = runestringn(b, p, cols[lt].fg, ZP, font, &rn, 1);
if(isspacerune(rn)){
if(hastrl)
trlr.max = addpt(p, Pt(0, font->height));
else{
trlr.min = p0;
hastrl = 1;
}
}else if(hastrl)
hastrl = 0;
}
}
if(hastrl)
draw(b, trlr, trlcol, nil, ZP);
}
void
renderblock(Block *b, Rectangle sr)
{
Rectangle r, lr, br;
Line *l;
int i, pad;
pad = 0;
r = insetrect(sr, 1);
if(b->f != nil){
pad = Margin;
lr = r;
lr.max.y = lr.min.y + lineh;
br = rectaddpt(expander[0]->r, Pt(lr.min.x+Hpadding, lr.min.y+Vpadding));
border(fb, sr, 1, bord, ZP);
renderline(fb, lr, Dx(expander[0]->r)+Hpadding, Lfile, b->f);
draw(fb, 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(fb, lr, pad, l->t, l->s);
}
}
void
redraw(void)
{
Rectangle clipr;
int i, h, y, ye, vmin, vmax;
Block *b;
draw(fb, fb->r, cols[Lnone].bg, nil, ZP);
draw(fb, scrollr, scrlcol.bg, nil, ZP);
if(viewh < totalh){
h = ((double)viewh/totalh)*Dy(scrollr);
y = ((double)offset/totalh)*Dy(scrollr);
ye = scrollr.min.y + y + h;
if(ye >= scrollr.max.y)
ye = scrollr.max.y;
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(fb, scrposr, scrlcol.fg, nil, ZP);
vmin = viewr.min.y + offset;
vmax = viewr.max.y + offset;
clipr = screen->clipr;
replclipr(fb, 0, viewr);
for(i = 0; i < cur->nblocks; i++){
b = cur->blocks[i];
if(b->sr.min.y <= vmax && b->sr.max.y >= vmin)
renderblock(b, rectaddpt(b->sr, Pt(0, -offset)));
}
replclipr(fb, 0, clipr);
draw(screen, screen->r, fb, nil, fb->r.min);
flushimage(display, 1);
}
void
pan(int off)
{
int max;
max = Dx(scrollr) + Margin + Hpadding + maxlength*spacew + 2*ellipsisw + Hpadding + Margin - Dx(cur->blocks[0]->r)/2;
Δpan += off * spacew;
if(Δpan < 0 || max <= 0)
Δpan = 0;
else if(Δpan > max)
Δpan = max;
redraw();
}
void
clampoffset(int off)
{
if(offset<0)
offset = 0;
if(offset+viewh>totalh){
if(off > 0)
offset = totalh - viewh;
else
offset = 0;
}
}
void
scroll(int off)
{
if(off<0 && offset<=0)
return;
if(off>0 && offset+viewh>totalh)
return;
offset += off;
clampoffset(off);
redraw();
}
void
blockresize(Block *b)
{
int w, h;
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);
}
void
eresize(int new)
{
Rectangle listr;
Block *b;
Point p;
int i;
if(new && getwindow(display, Refnone)<0)
sysfatal("cannot reattach: %r");
sr = screen->r;
scrollr = sr;
scrollr.max.x = scrollr.min.x+Scrollwidth+Scrollgap;
listr = sr;
listr.min.x = scrollr.max.x;
viewr = insetrect(listr, Margin);
viewh = Dy(viewr);
lineh = Vpadding+font->height+Vpadding;
totalh = - Margin + Vpadding + 1;
p = addpt(viewr.min, Pt(0, totalh));
for(i = 0; i < cur->nblocks; i++){
b = cur->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(viewh <= totalh)
clampoffset(1);
else
clampoffset(0);
free(fb);
fb = allocimage(display, screen->r, screen->chan, 0, DBlack);
if(fb == nil)
sysfatal("allocimage: %r");
redraw();
}
void
ekeyboard(Rune k)
{
switch(k){
case 'q':
case Kdel:
threadexitsall(nil);
break;
case Khome:
scroll(-totalh);
break;
case Kend:
scroll(totalh);
break;
case Kpgup:
scroll(-viewh);
break;
case Kpgdown:
scroll(viewh);
break;
case Kup:
scroll(-scrollsize);
break;
case Kdown:
scroll(scrollsize);
break;
case Kleft:
pan(-4);
break;
case Kright:
pan(4);
break;
}
}
char*
genmenu(int i)
{
switch(i){
case Mcollapse:
return "collapse";
case Mexpand:
return "expand";
default:
i -= Nmenu;
if(i >= npatches || npatches == 1)
return nil;
if(patches[i].name == nil)
patches[i].name = smprint("%d", i);
return patches[i].name;
}
}
void
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);
}
}
Menu menu = {
nil,
genmenu,
};
void
collapse(int v)
{
int i;
for(i = 0; i < cur->nblocks; i++)
if(cur->blocks[i]->f != nil)
cur->blocks[i]->v = v;
eresize(0);
}
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)
scrolling = 0;
n = (m.xy.y - viewr.min.y - Margin)/lineh * lineh;
if(scrolling){
if(m.buttons&1){
scroll(-n);
return;
}else if(m.buttons&2){
offset = (m.xy.y - scrollr.min.y) * totalh/Dy(scrollr);
offset = offset/lineh * lineh;
if(viewh <= totalh)
clampoffset(1);
else
clampoffset(0);
redraw();
}else if(m.buttons&4){
scroll(n);
return;
}
}else if(!scrolling && m.buttons&2){
n = menuhit(2, mctl, &menu, nil);
switch(n){
case -1:
break;
case Mexpand: case Mcollapse:
collapse(n);
break;
default:
n -= Nmenu;
if(cur == patches+n)
break;
cur = patches+n;
eresize(0);
}
}else if(m.buttons&8){
scroll(-n);
}else if(m.buttons&16){
scroll(n);
}else if((oldbuttons^m.buttons) != 0 && ptinrect(m.xy, viewr)){
for(i = 0; i < cur->nblocks; i++){
b = cur->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 = ecolor(fg);
c->bg = ecolor(bg);
}
void
initcols(int black)
{
if(black){
bord = ecolor(0x888888FF^(~0xFF));
initcol(&scrlcol, DBlack, 0x999999FF^(~0xFF));
initcol(&cols[Lfile], DWhite, 0x333333FF);
initcol(&cols[Lsep], DBlack, DPurpleblue);
initcol(&cols[Ladd], DWhite, 0x002800FF);
initcol(&cols[Ldel], DWhite, 0x3F0000FF);
initcol(&cols[Lnone], DWhite, DBlack);
trlcol = ecolor(0x9F0000FF);
}else{
bord = ecolor(0x888888FF);
initcol(&scrlcol, DWhite, 0x999999FF);
initcol(&cols[Lfile], DBlack, 0xEFEFEFFF);
initcol(&cols[Lsep], DBlack, 0xEAFFFFFF);
initcol(&cols[Ladd], DBlack, 0xE6FFEDFF);
initcol(&cols[Ldel], DBlack, 0xFFEEF0FF);
initcol(&cols[Lnone], DBlack, DWhite);
trlcol = ecolor(0xFF8890FF);
}
}
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);
if(expander[0] == nil)
sysfatal("allocimage: %r");
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);
if(expander[1] == nil)
sysfatal("allocimage: %r");
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->v = 1;
b->f = nil;
b->lines = nil;
b->nlines = 0;
if(cur->nblocks%Meminc == 0)
cur->blocks = erealloc(cur->blocks, (cur->nblocks+Meminc)*sizeof *cur->blocks);
cur->blocks[cur->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)
{
int type;
type = Lnone;
if(strncmp(text, "⑨", 1)==0)
type = Lterm;
else if(strncmp(text, "diff", 4)==0)
type = Lhash;
else if(strncmp(text, "+++", 3)==0)
type = Lfile;
else if(strncmp(text, "---", 3)==0){
if(strlen(text) > 4)
type = Lfile;
}else if(strncmp(text, "@@", 2)==0)
type = Lsep;
else if(strncmp(text, "+", 1)==0)
type = Ladd;
else if(strncmp(text, "-", 1)==0)
type = Ldel;
return type;
}
int
lineno(char *s)
{
char *p, *t[5];
int n, l;
p = strdup(s);
n = tokenize(p, t, 5);
if(n<=0)
return -1;
l = atoi(t[2]);
free(p);
return l;
}
void
parse(int fd, char *name)
{
Biobuf *bp;
Block *b;
char *s, *f, *tab;
int t, n, ab, len;
int gotterm;
bp = Bfdopen(fd, OREAD);
if(bp==nil)
sysfatal("Bfdopen: %r");
gotterm = 0;
goto New;
for(;;){
s = Brdstr(bp, '\n', 1);
if(s==nil)
break;
t = linetype(s);
switch(t){
case Lterm:
gotterm = 1;
/* remove '--' and extra newline */
b->nlines--;
free(Brdstr(bp, '\n', 1));
New:
npatches++;
patches = realloc(patches, sizeof *patches * npatches);
cur = patches+npatches-1;
cur->blocks = nil;
cur->nblocks = 0;
cur->name = name;
b = addblock();
n = 0;
ab = 0;
break;
case Lfile:
if(s[0] == '-'){
b = addblock();
b->f = s+4;
if(strncmp(b->f, "a/", 2) == 0){
ab = 1;
b->f++;
}
}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;
if(strcmp(f, "/dev/null") != 0)
b->f = f;
}
break;
case Lsep:
n = lineno(s) - 1; /* -1 as the separator is not an actual line */
if(0){
case Lhash:
f = strchr(s, ' ');
if(f != nil && (f = strchr(f+1, ' '))){
f++;
if(strcmp(f, "uncommitted") != 0){
if(name != nil)
cur->name = smprint("%s %.*s", name, 9, f);
else
cur->name = smprint("%.*s", 9, f);
}
}
t = Lnone;
case Ladd:
case Lnone:
++n;
}
default:
addline(b, t, n, s);
len = strlen(s);
if(len > maxlength)
maxlength = len;
break;
}
}
if(gotterm)
npatches--;
Bterm(bp);
}
void
usage(void)
{
fprint(2, "usage: %s [-b] [-p nstrip] [patches...]\n", argv0);
exits("usage");
}
void
threadmain(int argc, char *argv[])
{
enum { Emouse, Eresize, Ekeyboard, };
Mouse m;
Rune k;
Alt a[] = {
{ nil, &m, CHANRCV },
{ nil, nil, CHANRCV },
{ nil, &k, CHANRCV },
{ nil, nil, CHANEND },
};
int i, b, fd;
b = 0;
ARGBEGIN{
case 'b':
b = 1;
break;
case 'p':
nstrip = atoi(EARGF(usage()));
break;
default:
usage();
break;
}ARGEND;
if(argc == 0)
parse(0, nil);
else for(i = 0; i < argc; i++){
fd = open(argv[i], OREAD);
if(fd < 0)
sysfatal("open: %r");
parse(fd, argv[i]);
close(fd);
}
cur = patches;
if(cur->nblocks==1 && cur->blocks[0]->nlines==0){
fprint(2, "no diff\n");
exits(nil);
}
if(initdraw(nil, nil, "vdiff")<0)
sysfatal("initdraw: %r");
if((mctl = initmouse(nil, screen)) == nil)
sysfatal("initmouse: %r");
if((kctl = initkeyboard(nil)) == nil)
sysfatal("initkeyboard: %r");
a[Emouse].c = mctl->c;
a[Eresize].c = mctl->resizec;
a[Ekeyboard].c = kctl->c;
initcols(b);
initicons();
spacew = stringwidth(font, " ");
ellipsisw = stringwidth(font, ellipsis);
eresize(0);
for(;;){
switch(alt(a)){
case Emouse:
emouse(m);
break;
case Eresize:
eresize(1);
break;
case Ekeyboard:
ekeyboard(k);
break;
}
}
}