shithub: vdiff

ref: a0b6286313c067b883ce1330499cf02f5cca6c0f
dir: vdiff/vdiff.c

View raw version
#include <u.h>
#include <libc.h>
#include <plumb.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <bio.h>

typedef struct Line Line;
typedef struct Col Col;

struct Line {
	int t;
	char *s;
	char *f;
	int l;
};

struct Col {
	Image *bg;
	Image *fg;
};

enum
{
	Lfile = 0,
	Lsep,
	Ladd,
	Ldel,
	Lnone,
	Ncols,
};	

enum
{
	Scrollwidth = 12,
	Scrollgap = 2,
	Margin = 8,
	Hpadding = 4,
	Vpadding = 2,
};

Mousectl *mctl;
Keyboardctl *kctl;
Rectangle sr;
Rectangle scrollr;
Rectangle scrposr;
Rectangle listr;
Rectangle textr;
Col cols[Ncols];
Col scrlcol;
int scrollsize;
int lineh;
int nlines;
int offset;
int scrolling;
int oldbuttons;
Line **lines;
int lsize;
int lcount;
int maxlength;
int Δpan;
int nstrip;
const char ellipsis[] = "...";

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
drawline(Rectangle r, Line *l)
{
	Point p;
	Rune  rn;
	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--){
		if(tab <= 0 && *s == '\t'){
			tab = 4 - nc % 4;
			s++;
		}
		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);
			break;
		}else{
			s += chartorune(&rn, s);
			if(off <= 0)
				p = runestringn(screen, p, cols[l->t].fg, ZP, font, &rn, 1);
		}
	}
}

void
redraw(void)
{
	Rectangle lr;
	int i, h, y;

	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);
	}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]);
	}
	flushimage(display, 1);
}

void
pan(int off)
{
	int max;

	max = Hpadding + maxlength * stringwidth(font, " ") + 2 * stringwidth(font, ellipsis) - Dx(textr);
	Δpan += off * stringwidth(font, " ");
	if(Δpan < 0 || max <= 0)
		Δpan = 0;
	else if(Δpan > max)
		Δpan = max;
	redraw();
}

void
clampoffset(void)
{
	if(offset<0)
		offset = 0;
	if(offset+nlines>lcount)
		offset = lcount-nlines+1;
}

void
scroll(int off)
{
	if(off<0 && offset<=0)
		return;
	if(off>0 && offset+nlines>lcount)
		return;
	offset += off;
	clampoffset();
	redraw();
}

int
indexat(Point p)
{
	int n;

	if (!ptinrect(p, textr))
		return -1;
	n = (p.y - textr.min.y) / lineh;
	if ((n+offset) >= lcount)
		return -1;
	return n;
}

void
eresize(void)
{
	if(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;
	textr = insetrect(listr, Margin);
	lineh = Vpadding+font->height+Vpadding;
	nlines = Dy(textr)/lineh;
	scrollsize = mousescrollsize(nlines);
	if(offset > 0 && offset+nlines>lcount)
		offset = lcount-nlines+1;
	redraw();
}

void
ekeyboard(Rune k)
{
	switch(k){
	case 'q':
	case Kdel:
		threadexitsall(nil);
		break;
	case Khome:
		scroll(-1000000);
		break;
	case Kend:
		scroll(1000000);
		break;
	case Kpgup:
		scroll(-nlines);
		break;
	case Kpgdown:
		scroll(nlines);
		break;
	case Kup:
		scroll(-1);
		break;
	case Kdown:
		scroll(1);
		break;
	case Kleft:
		pan(-4);
		break;
	case Kright:
		pan(4);
		break;
	}
}

void
emouse(Mouse m)
{
	int n;

	if(oldbuttons == 0 && m.buttons != 0 && ptinrect(m.xy, scrollr))
		scrolling = 1;
	else if(m.buttons == 0)
		scrolling = 0;

	if(scrolling){
		if(m.buttons&1){
			n = (m.xy.y - scrollr.min.y) / lineh;
			if(-n<lcount-offset){
				scroll(-n);
			} else {
				scroll(-lcount+offset);
			}
			return;
		}else if(m.buttons&2){
			n = (m.xy.y - scrollr.min.y) * lcount / Dy(scrollr);
			offset = n;
			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);
			}
			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);
	}
	oldbuttons = m.buttons;
}

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);
}

void
initcols(int black)
{
	if(black){
		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);
	}else{
		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);
	}
}

int
linetype(char *text)
{
	int type;

	type = Lnone;
	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;
}

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)
{
	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)
{
	Biobuf *bp;
	Line *l;
	char *s, *f, *t;
	int n, ab;

	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");
	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)
					f += 1;
			}
			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)
			++n;
		lines[lcount++] = l;
		if(lcount>=lsize){
			lsize *= 2;
			lines = realloc(lines, lsize*sizeof *lines);
			if(lines==nil)
				sysfatal("realloc: %r");
		}
	}
}

void
usage(void)
{
	fprint(2, "%s [-b] [-p n]\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 b;

	scrolling = 0;
	oldbuttons = 0;
	b = 0;
	ARGBEGIN{
	case 'b':
		b = 1;
		break;
	case 'p':
		nstrip = atoi(EARGF(usage()));
		break;
	default:
		usage();
		break;
	}ARGEND;

	parse(0);
	if(lcount==0){
		fprint(2, "no diff\n");
		exits(nil);
	}
	if(initdraw(nil, nil, "vdiff")<0)
		sysfatal("initdraw: %r");
	display->locking = 0;
	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);
	eresize();
	for(;;){
		switch(alt(a)){
		case Emouse:
			emouse(m);
			break;
		case Eresize:
			eresize();
			break;
		case Ekeyboard:
			ekeyboard(k);
			break;
		}
	}
}