shithub: mongrel

ref: 8ebb6feb1ca4d27d7a6155846dc7ea0c880c9316
dir: /text.c/

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

typedef struct Highlight Highlight;

struct Highlight
{
	char *pattern;
	ulong color;
	Reprog *re;
	Image *i;
};

Highlight hilights[] = {
	/* diff */
	{ "^--- a.*$", 0x458588ff, nil },
	{ "^\\+\\+\\+ b.*$", 0x458588ff, nil },
	{ "^-[^\\-]+$",   0xcc241dFF, nil },
	{ "^\\+.*$", 0x98971AFF, nil },
	/* quote */
	{ "^>.*$", 0x928374FF, nil },
};

enum
{
	Scrollwidth = 12,
	Padding = 4,
};

enum
{
	Msnarf,
	Mplumb,
};
char *menu2str[] = {
	"snarf",
	"plumb",
	nil,
};
Menu menu2 = { menu2str };

Image*
findhilight(char *s)
{
	int i;

	for(i = 0; i < nelem(hilights); i++)
		if(regexec(hilights[i].re, s, nil, 0))
			return hilights[i].i;
	return nil;
}

void
computelines(Text *t)
{
	int i, x, w, l, c, n, s, wrap;
	Rune r;
	char buf[4096] = {0};
	Image *h;

	t->lines[0] = 0;
	t->nlines = 1;
	w = Dx(t->textr);
	x = 0;
	s = 0;
	wrap = 0;
	h = nil;
	for(i = 0; i < t->ndata; ){
		c = chartorune(&r, &t->data[i]);
		if(r == '\n'){
			if(i + c == t->ndata)
				break;
			n = i + c - t->lines[t->nlines - 1];
			if(wrap)
					t->high[t->nlines - 1] = h;
			else{
				strncpy(buf, t->data + t->lines[t->nlines - 1], n);
				buf[n] = 0;
				t->high[t->nlines - 1] = findhilight(buf);
			}
			t->lines[t->nlines++] = i + c;
			x = 0;
			wrap = 0;
			h = nil;
		}else{
			l = 0;
			if(r == '\t'){
				x += stringwidth(t->font, "    ");
			}else{
				l = runestringnwidth(t->font, &r, 1);
				x += l;
				if(r == ' ')
					s = i + 1;
			}
			if(x > w){
				i = s;
				n = i - t->lines[t->nlines - 1];
				strncpy(buf, t->data + t->lines[t->nlines - 1], n);
				buf[n] = 0;
				h = findhilight(buf);
				t->high[t->nlines - 1] = h;
				t->lines[t->nlines++] = i;
				x = l;
				wrap = 1;
			}
		}
		i += c;
	}
}

int
indexat(Text *t, Point p)
{
	int line, i, s, e, x, c, l;
	Rune r;

	if(!ptinrect(p, t->textr))
		return -1;
	line = t->offset + ((p.y - t->textr.min.y) / font->height);
	s = t->lines[line];
	if(line+1 >= t->nlines)
		e = t->ndata;
	else
		e = t->lines[line+1] - 2; /* make sure we exclude the newline */
	c = 0;
	x = t->textr.min.x;
	for(i = s; i < e; ){
		c = chartorune(&r, &t->data[i]);
		if(r == '\t')
			l = stringwidth(t->font, "    ");
		else
			l = runestringnwidth(t->font, &r, 1);
		if(x <= p.x && p.x <= x+l)
			break;
		i += c;
		x += l;
	}
	if(r == '\n')
		i -= c;
	return i;
}

void
textinit(Text *t, Image *b, Rectangle r, Font *f, Image *cols[NCOLS])
{
	int i;

	memset(t, 0, sizeof *t);
	t->b = b;
	t->font = f;
	t->s0 = -1;
	t->s1 = -1;
	t->offset = 0;
	textresize(t, r);
	memmove(t->cols, cols, sizeof t->cols);
	for(i = 0; i < nelem(hilights); i++){
		hilights[i].re = regcomp(hilights[i].pattern);
		hilights[i].i  = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, hilights[i].color);
	}
}

void
textset(Text *t, char *data, usize ndata)
{
	t->s0 = -1;
	t->s1 = -1;
	t->offset = 0;
	t->data = data;
	t->ndata = ndata;
	computelines(t);
}

void
textresize(Text *t, Rectangle r)
{
	t->r = r;
	t->vlines = Dy(t->r) / t->font->height;
	t->scrollr = rectaddpt(Rect(0, 0, Scrollwidth, Dy(r)), r.min);
	t->textr = r;
	t->textr.min.x = t->scrollr.max.x + Padding;
	if(t->nlines > 0)
		computelines(t);
}

int
selected(Text *t, int index)
{
	int s0, s1;

	if(t->s0 < 0 || t->s1 < 0)
		return 0;
	s0 = t->s0 < t->s1 ? t->s0 : t->s1;
	s1 = t->s0 > t->s1 ? t->s0 : t->s1;
	return s0 <= index && index <= s1;
}

void
drawline(Text *t, int index)
{
	int i, s, e, sel;
	Point p;
	Rune r;
	Image *fg, *bg;

	s = t->lines[t->offset+index];
	if(t->offset+index+1 >= t->nlines)
		e = t->ndata;
	else
		e = t->lines[t->offset+index+1];
	p = addpt(t->textr.min, Pt(0, index*font->height));
	for(i = s; i < e; ){
		sel = selected(t, i);
		fg = sel ? t->cols[HTEXT] : t->cols[TEXT];
		bg = sel ? t->cols[HIGH]  : t->cols[BACK];
		if(!sel && t->high[t->offset+index] != nil)
			fg = t->high[t->offset+index];
		i += chartorune(&r, &t->data[i]);
		if(r == '\n')
			if(s + 1 == e) /* empty line */
				r = L' ';
			else
				continue;
		if(r == '\t')
			p = stringbg(t->b, p, fg, ZP, t->font, "    ", bg, ZP);
		else
			p = runestringnbg(t->b, p, fg, ZP, t->font, &r, 1, bg, ZP);
	}
}

void
textdraw(Text *t)
{
	int i, h, y;
	Rectangle sr;

	draw(t->b, t->r, t->cols[BACK], nil, ZP);
	draw(t->b, t->scrollr, t->cols[BORD], nil, ZP);
	border(t->b, t->scrollr, 0, t->cols[TEXT], ZP);
	if(t->nlines > 0){
		h = ((double)t->vlines / t->nlines) * Dy(t->scrollr);
		y = ((double)t->offset / t->nlines) * Dy(t->scrollr);
		sr = Rect(t->scrollr.min.x + 1, t->scrollr.min.y + y + 1, t->scrollr.max.x - 1, t->scrollr.min.y + y + h - 1);
	}else
		sr = insetrect(t->scrollr, -1);
	draw(t->b, sr, t->cols[BACK], nil, ZP);
	for(i = 0; i < t->vlines; i++){
		if(t->offset+i >= t->nlines)
			break;
		drawline(t, i);
	}
	flushimage(display, 1);
}

static
void
scroll(Text *t, int lines)
{
	if(t->nlines <= t->vlines)
		return;
	if(lines < 0 && t->offset == 0)
		return;
	if(lines > 0 && t->offset + t->vlines >= t->nlines)
		return;
	t->offset += lines;
	if(t->offset < 0)
		t->offset = 0;
	if(t->offset + t->nlines%t->vlines >= t->nlines)
		t->offset = t->nlines - t->nlines%t->vlines;
	textdraw(t);
}

void
textkeyboard(Text *t, Rune k)
{
	switch(k){
	case Kup:
		scroll(t, -1);
		break;
	case Kdown:
		scroll(t, 1);
		break;
	case Kpgup:
		scroll(t, -t->vlines);
		break;
	case Kpgdown:
		scroll(t, t->vlines);
		break;
	case Khome:
		scroll(t, -t->nlines);
		break;
	case Kend:
		scroll(t, t->nlines);
		break;
	}
}

void
snarfsel(Text *t)
{
	int fd, s0, s1;

	if(t->s0 < 0 || t->s1 < 0)
		return;
	fd = open("/dev/snarf", OWRITE);
	if(fd < 0)
		return;
	s0 = t->s0 < t->s1 ? t->s0 : t->s1;
	s1 = t->s0 > t->s1 ? t->s0 : t->s1;
	write(fd, &t->data[s0], s1 - s0 + 1);
	close(fd);
}

void
plumbsel(Text *t)
{
	int fd, s0, s1;
	char *s;

	if(t->s0 < 0 || t->s1 < 0)
		return;
	fd = plumbopen("send", OWRITE);
	if(fd < 0)
		return;
	s0 = t->s0 < t->s1 ? t->s0 : t->s1;
	s1 = t->s0 > t->s1 ? t->s0 : t->s1;
	s = smprint("%.*s", s1 - s0 + 1, &t->data[s0]);
	plumbsendtext(fd, argv0, nil, nil, s);
	free(s);
	close(fd);
}

void
menu2hit(Text *t, Mousectl *mc)
{
	int n;

	n = menuhit(2, mc, &menu2, nil);
	switch(n){
		case Msnarf:
			snarfsel(t);
			break;
		case Mplumb:
			plumbsel(t);
			break;
	}
}

void
textmouse(Text *t, Mousectl *mc)
{
	static selecting = 0;
	Point p;
	int n;

	if(ptinrect(mc->xy, t->scrollr)){
		if(mc->buttons == 1){
			n = (mc->xy.y - t->scrollr.min.y) / font->height;
			scroll(t, -n);
		}else if(mc->buttons == 2){
			t->offset = (mc->xy.y - t->scrollr.min.y) * t->nlines / Dy(t->scrollr);
			textdraw(t);
		}else if(mc->buttons == 4){
			n = (mc->xy.y - t->scrollr.min.y) / font->height;
			scroll(t, n);
		}
	}else if(ptinrect(mc->xy, t->textr)){
		if(mc->buttons == 0)
			selecting = 0;
		if(mc->buttons == 1){
			if(!selecting){
				selecting = 1;
				t->s0 = t->s1 = -1;
				n = indexat(t, mc->xy);
				if(n < 0)
					return;
				t->s0 = n;
				t->s1 = -1;
				textdraw(t);
			}else{
				n = indexat(t, mc->xy);
				if(n < 0)
					return;
				t->s1 = n;
			}
			for(readmouse(mc); mc->buttons == 1; readmouse(mc)){
				p = mc->xy;
				if(p.y <= t->textr.min.y){
					scroll(t, -1);
					p.y = t->textr.min.y + 1;
				}else if(p.y >= t->textr.max.y){
					scroll(t, 1);
					p.y = t->textr.max.y - 1;
				}
				n = indexat(t, p);
				if(n < 0)
					break;
				t->s1 = n;
				textdraw(t);
			}
		}else if(mc->buttons == 2){
			menu2hit(t, mc);
		}else if(mc->buttons == 8){
			n = mousescrollsize(t->vlines);
			scroll(t, -n);
		}else if(mc->buttons == 16){
			n = mousescrollsize(t->vlines);
			scroll(t, n);
		}
	}
}