shithub: sam

ref: 69d4e9e10a45517ad32d399bfbf61390d5a43208
dir: /samterm/main.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include "flayer.h"
#include "samterm.h"

int	mainstacksize = 16*1024;

Text	cmd;
Rune	*scratch;
long	nscralloc;
Cursor	*cursor;
Flayer	*which = 0;
Flayer	*work = 0;
long	snarflen;
long	typestart = -1;
long	typeend = -1;
long	typeesc = -1;
long	modified = 0;		/* strange lookahead for menus */
char	hostlock = 1;
char	hasunlocked = 0;
int	maxtab = 8;
int	autoindent;
int	spacesindent;

void
threadmain(int argc, char *argv[])
{
	int i, got, nclick, scr, chord;
	Text *t;
	Rectangle r;
	Flayer *nwhich;
	ulong p;

	rfork(RFENVG|RFNAMEG);

	getscreen(argc, argv);
	iconinit();
	initio();
	scratch = alloc(100*RUNESIZE);
	nscralloc = 100;
	r = screen->r;
	r.max.y = r.min.y + Dy(r)/5;
	flstart(screen->clipr);
	rinit(&cmd.rasp);
	flnew(&cmd.l[0], gettext, 1, &cmd);
	flinit(&cmd.l[0], r, font, cmdcols);
	cmd.nwin = 1;
	which = &cmd.l[0];
	cmd.tag = Untagged;
	outTs(Tversion, VERSION);
	startnewfile(Tstartcmdfile, &cmd);

	got = 0;
	chord = 0;
	for(;;got = waitforio(1)){
		if(hasunlocked && RESIZED())
			resize();
		if(got & (1 << RHost))
			rcv();
		if(got & (1 << RPlumb)){
			for(i = 0; !cmd.l[i].textfn; i++)
				;
			current(&cmd.l[i]);
			flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes);
			type(which, RPlumb);
		}
		if(got & (1 << RKeyboard)){
			if(which)
				type(which, RKeyboard);
			else
				kbdblock();
		}
		if(got & (1 << RMouse)){
			if(hostlock == 2 || !ptinrect(mousep->xy, screen->r)){
				mouseunblock();
				continue;
			}
			nwhich = flwhich(mousep->xy);
			scr = which && (ptinrect(mousep->xy, which->scroll) ||
				mousep->buttons & (8|16));
			if(mousep->buttons)
				flushtyping(1);
			if((mousep->buttons & 1) == 0)
				chord = 0;
			if(chord && which && which == nwhich){
				chord |= mousep->buttons;
				t = which->user1;
				if(!t->lock){
					int w = which-t->l;
					if(chord & 2){
						cut(t, w, 1, 1);
						chord &= ~2;
					}
					if(chord & 4){
						paste(t, w);
						chord &= ~4;
					}
				}
			}else if(mousep->buttons & (1|8)){
				if(scr)
					scroll(which, (mousep->buttons & 8) ? 4 : 1);
				else if(nwhich && nwhich != which)
					current(nwhich);
				else if(ptinrect(mousep->xy, which->f.r)){
					t = which->user1;
					nclick = flselect(which, &p);
					if(nclick > 0){
						if(nclick > 1)
							outTsl(Ttclick, t->tag, p);
						else
							outTsl(Tdclick, t->tag, p);
						t->lock++;
					}else if(t != &cmd)
						outcmd();
					if(mousep->buttons & 1)
						chord = mousep->buttons;
				}
			}else if((mousep->buttons & 2) && which){
				if(scr)
					scroll(which, 2);
				else
					menu2hit();
			}else if(mousep->buttons & (4|16)){
				if(scr)
					scroll(which, (mousep->buttons & 16)? 5 : 3);
				else
					menu3hit();
			}
			mouseunblock();
		}
	}
}

void
resize(void)
{
	int i;

	flresize(screen->clipr);
	for(i = 0; i < nname; i++)
		if(text[i])
			hcheck(text[i]->tag);
}

void
current(Flayer *nw)
{
	Text *t;

	if(which)
		flborder(which, 0);
	if(nw){
		flushtyping(1);
		flupfront(nw);
		flborder(nw, 1);
		buttons(Up);
		t = nw->user1;
		t->front = nw - t->l;
		if(t != &cmd)
			work = nw;
	}
	which = nw;
}

void
closeup(Flayer *l)
{
	Text *t = l->user1;
	int m;

	m = whichmenu(t->tag);
	if(m < 0)
		return;
	flclose(l);
	if(l == which){
		which = 0;
		current(flwhich(Pt(0, 0)));
	}
	if(l == work)
		work = 0;
	if(--t->nwin == 0){
		rclear(&t->rasp);
		free((uchar *)t);
		text[m] = 0;
	}else if(l == &t->l[t->front]){
		for(m=0; m < NL; m++)	/* find one; any one will do */
			if(t->l[m].textfn){
				t->front = m;
				return;
			}
		panic("close");
	}
}

Flayer*
findl(Text *t)
{
	int i;

	for(i = 0; i < NL; i++)
		if(!t->l[i].textfn)
			return &t->l[i];
	return 0;
}

void
duplicate(Flayer *l, Rectangle r, Font *f, int close)
{
	Text *t = l->user1;
	Flayer *nl = findl(t);
	Rune *rp;
	ulong n;

	if(nl){
		flnew(nl, gettext, l->user0, (char *)t);
		flinit(nl, r, f, l->f.cols);
		nl->origin = l->origin;
		rp = (*l->textfn)(l, l->f.nchars, &n);
		flinsert(nl, rp, rp+n, l->origin);
		flsetselect(nl, l->p0, l->p1);
		if(close){
			flclose(l);
			if(l == which)
				which = nil;
		}else
			t->nwin++;
		current(nl);
		hcheck(t->tag);
	}
	setcursor(mousectl, cursor);
}

void
buttons(int updown)
{
	while(((mousep->buttons & 7) != 0) != updown)
		if(readmouse(mousectl) < 0)
			panic("mouse");
}

int
getr(Rectangle *rp)
{
	Point p;
	Rectangle r;

	*rp = getrect(3, mousectl);
	if(rp->max.x && rp->max.x-rp->min.x<=5 && rp->max.y-rp->min.y<=5){
		p = rp->min;
		r = cmd.l[cmd.front].entire;
		*rp = screen->r;
		if(cmd.nwin==1){
			if (p.y <= r.min.y)
				rp->max.y = r.min.y;
			else if (p.y >= r.max.y)
				rp->min.y = r.max.y;
			if (p.x <= r.min.x)
				rp->max.x = r.min.x;
			else if (p.x >= r.max.x)
				rp->min.x = r.max.x;
		}
	}
	return rectclip(rp, screen->r) &&
	   rp->max.x-rp->min.x>100 && rp->max.y-rp->min.y>40;
}

void
snarf(Text *t, int w)
{
	Flayer *l = &t->l[w];

	if(l->p1 > l->p0){
		snarflen = l->p1 - l->p0;
		outTsll(Tsnarf, t->tag, l->p0, l->p1);
	}
}

void
cut(Text *t, int w, int save, int check)
{
	long p0, p1;
	Flayer *l;
	l = &t->l[w];
	
	p0 = l->p0;
	p1 = l->p1;
	if(p0 == p1)
		return;
	if(p0 < 0)
		panic("cut");
	if(save)
		snarf(t, w);
	outTsll(Tcut, t->tag, p0, p1);
	flsetselect(l, p0, p0);
	t->lock++;
	hcut(t->tag, p0, p1 - p0);
	if(check)
		hcheck(t->tag);
}

void
paste(Text *t, int w)
{
	if(snarflen){
		cut(t, w, 0, 0);
		t->lock++;
		outTsl(Tpaste, t->tag, t->l[w].p0);
	}
}

Rune
raspc(Rasp *r, long p)
{
	ulong n;

	rload(r, p, p+1, &n);
	if(n)
		return scratch[0];
	return 0;
}

int
getcol(Rasp *r, long p)
{
	int c;

	for(c = 0; p > 0; c++)
		if(raspc(r, --p) == '\n')
			break;
	return c;
}

long
del(Rasp *r, long o, long p)
{
	int i, col, n;

	if(--p < o)
		return o;
	if(!spacesindent || raspc(r, p) != ' ')
		return p;
	col = (getcol(r, p) + 1) % maxtab;;
	if((n = col) == 0)
		n = maxtab;
	for(i = 0; p-1>=o && raspc(r, p-1)==' ' && i<n-1; --p, i++)
		;
	return p>=o? p : o;
}

long
ctlw(Rasp *r, long o, long p)
{
	int c;

	if(--p < o)
		return o;
	if(raspc(r, p)=='\n')
		return p;
	for(; p>=o && !isalpharune(c=raspc(r, p)); --p)
		if(c=='\n')
			return p+1;
	for(; p>o && isalpharune(raspc(r, p-1)); --p)
		;
	return p>=o? p : o;
}

long
ctlu(Rasp *r, long o, long p)
{
	if(--p < o)
		return o;
	if(raspc(r, p) == '\n')
		return p;
	for(; p-1 >= o && raspc(r, p-1) != '\n'; --p)
		;
	return p >= o? p : o;
}

void
request(Text *t, int n, int m)
{
	Rasp *r;
	int len;
	int c;

	r = &t->rasp;
	do{
		len = m - n;
		if(len > TBLOCKSIZE)
			len = TBLOCKSIZE;
		if((c = rcontig(r, n, n+len, 1)) == len)
			continue;
		outTsls(Trequest, t->tag, n+c, len-c);
		t->lock++;
		if(waitforio(1) & (1 << RHost))
			rcv();
	}while((n += len) < m);
}

long
bound(Text *t, long a)
{
	if(a >= t->rasp.nrunes)
		a = t->rasp.nrunes-1;
	if(a < 0)
		a = 0;
	return a;
}

/* todo: cleanup. */
void
center(Flayer *l, long a, long nl, int req)
{
	Frame *f = &l->f;
	Text *t = l->user1;
	Rune r;
	int n;

	a = bound(t, a);
	if(req){
		if(nl > 0)
			a += f->nchars;
		n = bound(t, a + nl * l->width);
		if(n != a)
			request(t, n < a? n: a, n < a? a: n);
	}

	if(nl <= 0){
		while(nl++ <= 0 && a > 0)
			for(n = 0; n < l->width && a > 0; n++){
				r = raspc(&t->rasp, --a);
				if(r == '\n')
					break;
				if(r == '\t')
					n += maxtab - (n % maxtab) - 1;
			}
		a += a != 0;
	}else{
		a -= f->nchars;
		while(nl-- > 0 && a < t->rasp.nrunes)
			for(n = 0; n < l->width && a < t->rasp.nrunes; n++){
				r = raspc(&t->rasp, a++);
				if(r == '\n')
					break;
				if(r == '\t')
					n += maxtab - (n % maxtab) - 1;
			}
	}
	horigin(t->tag, a);
}

void
flushtyping(int clearesc)
{
	Text *t;
	ulong n;

	if(clearesc)
		typeesc = -1;
	if(typestart == typeend) {
		modified = 0;
		return;
	}
	t = which->user1;
	if(t != &cmd)
		modified = 1;
	rload(&t->rasp, typestart, typeend, &n);
	scratch[n] = 0;
	if(t == &cmd && typeend == t->rasp.nrunes && scratch[typeend-typestart-1] == '\n'){
		setlock();
		outcmd();
	}
	outTslS(Ttype, t->tag, typestart, scratch);
	typestart = -1;
	typeend = -1;
}

int
nontypingkey(int c)
{
	switch(c){
	case Kup:
	case Kdown:
	case Khome:
	case Kend:
	case Kpgdown:
	case Kpgup:
	case Kleft:
	case Kright:
	case Ksoh:
	case Kenq:
	case Kstx:
	case Kbel:
		return 1;
	}
	return 0;
}

void
type(Flayer *l, int res)	/* what a bloody mess this is */
{
	Text *t = l->user1;
	Rune buf[100];
	Rune *p = buf;
	int scrollkey;
	int c, i;
	long a, a0;

	scrollkey = 0;
	if(res == RKeyboard)
		scrollkey = nontypingkey(qpeekc());	/* ICK */

	if(hostlock || t->lock){
		kbdblock();
		return;
	}
	a = l->p0;
	if(a != l->p1 && !scrollkey){
		flushtyping(1);
		cut(t, t->front, 1, 1);
		return;	/* it may now be locked */
	}

	while((c = kbdchar()) > 0){
		if(res == RKeyboard){
			if(nontypingkey(c) || c == Kesc)
				break;
			/* backspace, ctrl-u, ctrl-w, del */
			if(c == Kbs || c == Knack || c == Ketb || c == Kdel)
				break;
		}
		if(spacesindent && c == '\t'){
			int col, n;
			col = getcol(&t->rasp, a);
			n = maxtab - col % maxtab;
			for(i = 0; i < n && p < buf+nelem(buf); i++)
				*p++ = ' ';
		} else
			*p++ = c;
		if(c == '\n' && autoindent && t != &cmd){
			/* autoindent */
			int cursor, ch;
			cursor = ctlu(&t->rasp, 0, a+(p-buf)-1);
			while(p < buf+nelem(buf)){
				ch = raspc(&t->rasp, cursor++);
				if(ch == ' ' || ch == '\t')
					*p++ = ch;
				else
					break;
			}
		}
		if(c == '\n' || p >= buf+nelem(buf))
			break;
	}

	if(p > buf){
		if(typestart < 0)
			typestart = a;
		if(typeesc < 0)
			typeesc = a;
		hgrow(t->tag, a, p-buf, 0);
		t->lock++;	/* pretend we Trequest'ed for hdata */
		hdata(t->tag, a, buf, p-buf);
		a += p-buf;
		l->p0 = a;
		l->p1 = a;
		typeend = a;
		if(c == '\n' || typeend-typestart > nelem(buf))
			flushtyping(0);
		if(!t->lock && a < l->origin || a > l->origin+l->f.nchars)
			center(l, a, -(l->f.maxlines/3), 0);
	}

	switch(c){
	case Kdown:
		flushtyping(0);
		center(l, l->origin, 1, 1);
		break;
	case Kpgdown:
		flushtyping(0);
		center(l, l->origin, l->f.maxlines, 1);
		break;
	case Kup:
		flushtyping(0);
		center(l, l->origin, -1, 1);
		break;
	case Kpgup:
		flushtyping(0);
		center(l, l->origin, -l->f.maxlines, 1);
		break;
	case Kleft:
		flushtyping(0);
		a0 = l->p0;
		a0 -= (l->p0 > 0);
		flsetselect(l, a0, a0);
		if(a0 - l->origin < 0)
			center(l, l->origin, -1, 1);
		break;
	case Kright:
		flushtyping(0);
		a0 = l->p1;
		a0 += a < t->rasp.nrunes;
		flsetselect(l, a0, a0);
		if(a0-l->origin >= l->f.nchars)
			center(l, l->origin, 1, 1);
		break;
	case Khome:
		flushtyping(0);
		center(l, 0, 0, 1);
		break;
	case Kend:
		flushtyping(0);
		center(l, t->rasp.nrunes, 0, 1);
		break;
	case Ksoh:	/* ctrl+a */
		flushtyping(1);
		for(; a > 0; a--)
			if(raspc(&t->rasp, a-1) == '\n')
				break;
		l->p0 = l->p1 = a;
		goto Setsel;
	case Kenq:	/* ctrl+e */
		flushtyping(1);
		for(; a < t->rasp.nrunes; a++)
			if(raspc(&t->rasp, a) == '\n')
				break;
		l->p0 = l->p1 = a;
		goto Setsel;
	case Kbs:
	case Knack:
	case Ketb:
	case Kdel:
		if(!hostlock){
			/* backspacing immediately after outcmd(): sorry */
			if(l->f.p0>0 && a>0){
				switch(c){
				case Kbs:
				case Kdel:	/* del */
					l->p0 = del(&t->rasp, l->origin, a);
					break;
				case Knack:	/* ctrl-u */
					l->p0 = ctlu(&t->rasp, l->origin, a);
					break;
				case Ketb:	/* ctrl-w */
					l->p0 = ctlw(&t->rasp, l->origin, a);
					break;
				}
				l->p1 = a;
				if(l->p1 != l->p0){
					/* cut locally if possible */
					if(typestart<=l->p0 && l->p1<=typeend){
						t->lock++;	/* to call hcut */
						hcut(t->tag, l->p0, l->p1-l->p0);
						/* hcheck is local because we know rasp is contiguous */
						hcheck(t->tag);
					}else{
						flushtyping(0);
						cut(t, t->front, 0, 1);
					}
				}
				if(typeesc >= l->p0)
					typeesc = l->p0;
				if(typestart >= 0){
					if(typestart >= l->p0)
						typestart = l->p0;
					typeend = l->p0;
					if(typestart == typeend){
						typestart = -1;
						typeend = -1;
						modified = 0;
					}
				}
			}
		}
		break;
	case Kstx:
		t = &cmd;
		for(l=t->l; l->textfn==0; l++)
			;
		current(l);
		flushtyping(0);
		a = t->rasp.nrunes;
		flsetselect(l, a, a);
		center(l, a, 0, 1);
		break;
	case Kbel:
 		if(work == nil)
 			return;
 		if(which != work){
 			current(work);
 			return;
 		}
 		t = (Text*)work->user1;
 		l = &t->l[t->front];
 		for(i=t->front; t->nwin>1 && (i = (i+1)%NL) != t->front; )
 			if(t->l[i].textfn){
 				l = &t->l[i];
 				break;
 			}
 		current(l);
		break;
	case Kesc:
		if(typeesc >= 0){
			l->p0 = typeesc;
			l->p1 = a;
			flushtyping(1);
		} /* wet floor */
Setsel:
	default:
		for(l=t->l; l<&t->l[NL]; l++)
			if(l->textfn)
				flsetselect(l, l->p0, l->p1);
	}
}


void
outcmd(void)
{
	if(work)
		outTsll(Tworkfile, ((Text *)work->user1)->tag, work->p0, work->p1);
}

void
panic(char *s)
{
	panic1(display, s);
}

void
panic1(Display*, char *s)
{
	fprint(2, "samterm: panic: ");
	perror(s);
	abort();
}

Rune*
gettext(Flayer *l, long n, ulong *np)
{
	Text *t;

	t = l->user1;
	rload(&t->rasp, l->origin, l->origin+n, np);
	return scratch;
}

long
scrtotal(Flayer *l)
{
	return ((Text *)l->user1)->rasp.nrunes;
}

void*
alloc(ulong n)
{
	void *p;

	p = mallocz(n, 1);
	if(!p)
		panic("alloc");
	return p;
}