9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
From: Fco.J.Ballesteros <nemo@plan9.escet.urjc.es>
To: 9fans@cse.psu.edu
Subject: [9fans] acme: hiding certain files in dir windows
Date: Mon,  5 Nov 2001 09:07:34 +0100	[thread overview]
Message-ID: <20011105080740.64503199B5@mail.cse.psu.edu> (raw)

[-- Attachment #1: Type: text/plain, Size: 566 bytes --]

Hi,

	I added a Ignore command to acme to let it ignore
files matching a regexp when listing dir contents. I use
it to keep the window of my kernel source dir with just
the source files;
but I think it's also marginally useful to
hide ugly dot files on windows browsing unix directories.

The usage is simple:
	Ignore regexp:	ignore matching files
	Ignore:		don't ignore anything
regexps are those of regexp(6).

I attach the changed files here.

By the way, why doesn't acme use regexp(2)?
Shouldn't we converge to regexp(6)?
Just wondering...


[-- Attachment #2: acme.c --]
[-- Type: text/plain, Size: 19396 bytes --]

#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 <auth.h>
#include <fcall.h>
#include <plumb.h>
#include <regexp.h>
#include "dat.h"
#include "fns.h"
	/* for generating syms in mkfile only: */
	#include <bio.h>
	#include "edit.h"

void	mousethread(void*);
void	keyboardthread(void*);
void	waitthread(void*);
void	xfidallocthread(void*);
void plumbproc(void*);

Reffont	**fontcache;
int		nfontcache;
char		wdir[512] = ".";
Reffont	*reffonts[2];
int		snarffd = -1;
int		mainpid;
int		plumbsendfd;
int		plumbeditfd;

enum{
	NSnarf = 1000	/* less than 1024, I/O buffer size */
};
Rune	snarfrune[NSnarf+1];

char		*fontnames[2] =
{
	"/lib/font/bit/lucidasans/euro.8.font",
	"/lib/font/bit/lucm/unicode.9.font",
};

Reprog* ignoreregx;
Command *command;

void	acmeerrorinit(void);
void	readfile(Column*, char*);
int	shutdown(void*, char*);

void
derror(Display*, char *errorstr)
{
	error(errorstr);
}

void
threadmain(int argc, char *argv[])
{
	int i;
	char *p, *loadfile;
	char buf[256];
	Column *c;
	int ncol;
	Display *d;
	static void *arg[1];

	rfork(RFENVG|RFNAMEG);

	ncol = -1;

	loadfile = nil;
	ARGBEGIN{
	case 'b':
		bartflag = TRUE;
		break;
	case 'c':
		p = ARGF();
		if(p == nil)
			goto Usage;
		ncol = atoi(p);
		if(ncol <= 0)
			goto Usage;
		break;
	case 'i':
		p = ARGF();
		if (p  == nil)
			goto Usage;
		ignoreregx = regcomp(p);
		break;
	case 'f':
		fontnames[0] = ARGF();
		if(fontnames[0] == nil)
			goto Usage;
		break;
	case 'F':
		fontnames[1] = ARGF();
		if(fontnames[1] == nil)
			goto Usage;
		break;
	case 'l':
		loadfile = ARGF();
		if(loadfile == nil)
			goto Usage;
		break;
	default:
	Usage:
		fprint(2, "usage: acme -c ncol -f fontname -F fixedwidthfontname -i ignore -l loadfile\n");
		exits("usage");
	}ARGEND

	cputype = getenv("cputype");
	objtype = getenv("objtype");
	home = getenv("home");
	p = getenv("tabstop");
	if(p != nil){
		maxtab = strtoul(p, nil, 0);
		free(p);
	}
	if(maxtab == 0)
		maxtab = 4;
	putenv("font", fontnames[0]);
	snarffd = open("/dev/snarf", OREAD|OCEXEC);
	if(cputype){
		sprint(buf, "/acme/bin/%s", cputype);
		bind(buf, "/bin", MBEFORE);
	}
	bind("/acme/bin", "/bin", MBEFORE);
	getwd(wdir, sizeof wdir);

	if(geninitdraw(nil, derror, nil, "acme", nil, Refnone) < 0){
		fprint(2, "acme: can't open display: %r\n");
		exits("font");
	}
	d = display;
	font = d->defaultfont;

	reffont.f = font;
	reffonts[0] = &reffont;
	incref(&reffont);	/* one to hold up 'font' variable */
	incref(&reffont);	/* one to hold up reffonts[0] */
	fontcache = emalloc(sizeof(Reffont*));
	nfontcache = 1;
	fontcache[0] = &reffont;

	iconinit();
	timerinit();
	rxinit();

	cwait = threadwaitchan();
	ccommand = chancreate(sizeof(Command**), 0);
	ckill = chancreate(sizeof(Rune*), 0);
	cxfidalloc = chancreate(sizeof(Xfid*), 0);
	cxfidfree = chancreate(sizeof(Xfid*), 0);
	cerr = chancreate(sizeof(char*), 0);
	cedit = chancreate(sizeof(int), 0);
	cexit = chancreate(sizeof(int), 0);
	if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil){
		fprint(2, "acme: can't create initial channels: %r\n");
		exits("channels");
	}

	mousectl = initmouse(nil, screen);
	if(mousectl == nil){
		fprint(2, "acme: can't initialize mouse: %r\n");
		exits("mouse");
	}
	mouse = mousectl;
	keyboardctl = initkeyboard(nil);
	if(keyboardctl == nil){
		fprint(2, "acme: can't initialize keyboard: %r\n");
		exits("keyboard");
	}
	mainpid = getpid();
	plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
	if(plumbeditfd >= 0){
		cplumb = chancreate(sizeof(Plumbmsg*), 0);
		proccreate(plumbproc, nil, STACK);
	}
	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);

	fsysinit();

	#define	WPERCOL	8
	disk = diskinit();
	if(loadfile)
		rowload(&row, loadfile, TRUE);
	else{
		rowinit(&row, screen->clipr);
		if(ncol < 0){
			if(argc == 0)
				ncol = 2;
			else{
				ncol = (argc+(WPERCOL-1))/WPERCOL;
				if(ncol < 2)
					ncol = 2;
			}
		}
		if(ncol == 0)
			ncol = 2;
		for(i=0; i<ncol; i++){
			c = rowadd(&row, nil, -1);
			if(c==nil && i==0)
				error("initializing columns");
		}
		c = row.col[row.ncol-1];
		if(argc == 0)
			readfile(c, wdir);
		else
			for(i=0; i<argc; i++){
				p = utfrrune(argv[i], '/');
				if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
					readfile(c, argv[i]);
				else
					readfile(row.col[i/WPERCOL], argv[i]);
			}
	}
	flushimage(display, 1);

	acmeerrorinit();
	threadcreate(keyboardthread, nil, STACK);
	threadcreate(mousethread, nil, STACK);
	threadcreate(waitthread, nil, STACK);
	threadcreate(xfidallocthread, nil, STACK);

	atnotify(shutdown, 1);
	recvul(cexit);
	killprocs();
	threadexitsall(nil);
}

void
readfile(Column *c, char *s)
{
	Window *w;
	Rune rb[256];
	int nb, nr;
	Runestr rs;

	w = coladd(c, nil, nil, -1);
	cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
	rs = cleanrname((Runestr){rb, nr});
	winsetname(w, rs.r, rs.nr);
	textload(&w->body, 0, s, 1);
	w->body.file->mod = FALSE;
	w->dirty = FALSE;
	winsettag(w);
	textscrdraw(&w->body);
	textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
}

char *oknotes[] ={
	"delete",
	"hangup",
	"kill",
	"kilall",
	"exit",
	nil
};

int	dumping;

void
shutdown1(void*, char *msg)
{
	int i;

	notify(nil);
	killprocs();
	if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && strcmp(msg, "kilall")!=0 && getpid()==mainpid){
		dumping = TRUE;
		rowdump(&row, nil);
	}
	for(i=0; oknotes[i]; i++)
		if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
			threadexitsall(msg);
	print("acme: %s\n", msg);
	abort();
}

int
shutdown(void *a, char *msg)	/* extra call to get stack trace on 386 */
{
	shutdown1(a, msg);
	return 1;
}

void
killprocs(void)
{
	Command *c;

	fsysclose();
	if(display)
		flushimage(display, 1);

	for(c=command; c; c=c->next)
		postnote(PNGROUP, c->pid, "hangup");
	remove(acmeerrorfile);
}

static int errorfd;

void
acmeerrorproc(void *)
{
	char *buf;
	int n;

	threadsetname("acmeerrorproc");
	buf = emalloc(8192+1);
	while((n=read(errorfd, buf, 8192)) >= 0){
		buf[n] = '\0';
		sendp(cerr, estrdup(buf));
	}
}

void
acmeerrorinit(void)
{
	int fd, pfd[2];
	char buf[64];

	if(pipe(pfd) < 0)
		error("can't create pipe");
	sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
	fd = create(acmeerrorfile, OWRITE, 0666);
	if(fd < 0){
		remove(acmeerrorfile);
  		fd = create(acmeerrorfile, OWRITE, 0666);
		if(fd < 0)
			error("can't create acmeerror file");
	}
	sprint(buf, "%d", pfd[0]);
	write(fd, buf, strlen(buf));
	close(fd);
	/* reopen pfd[1] close on exec */
	sprint(buf, "/fd/%d", pfd[1]);
	errorfd = open(buf, OREAD|OCEXEC);
	if(errorfd < 0)
		error("can't re-open acmeerror file");
	close(pfd[1]);
	close(pfd[0]);
	proccreate(acmeerrorproc, nil, STACK);
}

void
plumbproc(void *)
{
	Plumbmsg *m;

	threadsetname("plumbproc");
	for(;;){
		m = plumbrecv(plumbeditfd);
		if(m == nil)
			threadexits(nil);
		sendp(cplumb, m);
	}
}

void
keyboardthread(void *)
{
	Rune r;
	Timer *timer;
	Text *t;
	enum { KTimer, KKey, NKALT };
	static Alt alts[NKALT+1];

	alts[KTimer].c = nil;
	alts[KTimer].v = nil;
	alts[KTimer].op = CHANNOP;
	alts[KKey].c = keyboardctl->c;
	alts[KKey].v = &r;
	alts[KKey].op = CHANRCV;
	alts[NKALT].op = CHANEND;

	timer = nil;
	typetext = nil;
	threadsetname("keyboardthread");
	for(;;){
		switch(alt(alts)){
		case KTimer:
			timerstop(timer);
			t = typetext;
			if(t!=nil && t->what==Tag){
				winlock(t->w, 'K');
				wincommit(t->w, t);
				winunlock(t->w);
				flushimage(display, 1);
			}
			alts[KTimer].c = nil;
			alts[KTimer].op = CHANNOP;
			break;
		case KKey:
		casekeyboard:
			typetext = rowtype(&row, r, mouse->xy);
			t = typetext;
			if(t!=nil && t->col!=nil)
				activecol = t->col;
			if(t!=nil && t->w!=nil)
				t->w->body.file->curtext = &t->w->body;
			if(timer != nil)
				timercancel(timer);
			if(t!=nil && t->what==Tag) {
				timer = timerstart(500);
				alts[KTimer].c = timer->c;
				alts[KTimer].op = CHANRCV;
			}else{
				timer = nil;
				alts[KTimer].c = nil;
				alts[KTimer].op = CHANNOP;
			}
			if(nbrecv(keyboardctl->c, &r) > 0)
				goto casekeyboard;
			flushimage(display, 1);
			break;
		}
	}
}

void
mousethread(void *)
{
	Text *t, *argt;
	int but;
	uint q0, q1;
	Window *w;
	Plumbmsg *pm;
	char *act;
	enum { MResize, MMouse, MPlumb, NMALT };
	static Alt alts[NMALT+1];

	threadsetname("mousethread");
	alts[MResize].c = mousectl->resizec;
	alts[MResize].v = nil;
	alts[MResize].op = CHANRCV;
	alts[MMouse].c = mousectl->c;
	alts[MMouse].v = &mousectl->Mouse;
	alts[MMouse].op = CHANRCV;
	alts[MPlumb].c = cplumb;
	alts[MPlumb].v = &pm;
	alts[MPlumb].op = CHANRCV;
	if(cplumb == nil)
		alts[MPlumb].op = CHANNOP;
	alts[NMALT].op = CHANEND;

	for(;;){
		switch(alt(alts)){
		case MResize:
			if(getwindow(display, Refnone) < 0)
				error("attach to window");
			scrlresize();
			rowresize(&row, screen->clipr);
			flushimage(display, 1);
			break;
		case MPlumb:
			if(strcmp(pm->type, "text") == 0){
				act = plumblookup(pm->attr, "action");
				if(act==nil || strcmp(act, "showfile")==0)
					plumblook(pm);
				else if(strcmp(act, "showdata")==0)
					plumbshow(pm);
			}
			flushimage(display, 1);
			plumbfree(pm);
			break;
		case MMouse:
			qlock(&row);
			t = rowwhich(&row, mouse->xy);
			if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
				winlock(mousetext->w, 'M');
				mousetext->eq0 = ~0;
				wincommit(mousetext->w, mousetext);
				winunlock(mousetext->w);
			}
			mousetext = t;
			if(t == nil)
				goto Continue;
			w = t->w;
			if(t==nil || mouse->buttons==0)
				goto Continue;
			but = 0;
			if(mouse->buttons == 1)
				but = 1;
			else if(mouse->buttons == 2)
				but = 2;
			else if(mouse->buttons == 4)
				but = 3;
			barttext = t;
			if(t->what==Body && ptinrect(mouse->xy, t->scrollr)){
				if(but){
					winlock(w, 'M');
					t->eq0 = ~0;
					textscroll(t, but);
					winunlock(w);
				}
				goto Continue;
			}
			if(ptinrect(mouse->xy, t->scrollr)){
				if(but){
					if(t->what == Columntag)
						rowdragcol(&row, t->col, but);
					else if(t->what == Tag){
						coldragwin(t->col, t->w, but);
						if(t->w)
							barttext = &t->w->body;
					}
					if(t->col)
						activecol = t->col;
				}
				goto Continue;
			}
			if(mouse->buttons){
				if(w)
					winlock(w, 'M');
				t->eq0 = ~0;
				if(w)
					wincommit(w, t);
				else
					textcommit(t, TRUE);
				if(mouse->buttons & 1){
					textselect(t);
					if(w)
						winsettag(w);
					argtext = t;
					seltext = t;
					if(t->col)
						activecol = t->col;	/* button 1 only */
					if(t->w!=nil && t==&t->w->body)
						activewin = t->w;
				}else if(mouse->buttons & 2){
					if(textselect2(t, &q0, &q1, &argt))
						execute(t, q0, q1, FALSE, argt);
				}else if(mouse->buttons & 4){
					if(textselect3(t, &q0, &q1))
						look3(t, q0, q1, FALSE);
				}
				if(w)
					winunlock(w);
				goto Continue;
			}
    Continue:
			flushimage(display, 1);
			qunlock(&row);
			break;
		}
	}
}

/*
 * There is a race between process exiting and our finding out it was ever created.
 * This structure keeps a list of processes that have exited we haven't heard of.
 */
typedef struct Pid Pid;
struct Pid
{
	int	pid;
	char	msg[ERRLEN];
	Pid	*next;
};

void
waitthread(void *)
{
	Waitmsg w;
	Command *c, *lc;
	uint pid;
	int found, ncmd;
	Rune *cmd;
	char *err;
	Text *t;
	Pid *pids, *p, *lastp;
	enum { WErr, WKill, WWait, WCmd, NWALT };
	Alt alts[NWALT+1];

	threadsetname("waitthread");
	pids = nil;
	alts[WErr].c = cerr;
	alts[WErr].v = &err;
	alts[WErr].op = CHANRCV;
	alts[WKill].c = ckill;
	alts[WKill].v = &cmd;
	alts[WKill].op = CHANRCV;
	alts[WWait].c = cwait;
	alts[WWait].v = &w;
	alts[WWait].op = CHANRCV;
	alts[WCmd].c = ccommand;
	alts[WCmd].v = &c;
	alts[WCmd].op = CHANRCV;
	alts[NWALT].op = CHANEND;

	command = nil;
	for(;;){
		switch(alt(alts)){
		case WErr:
			qlock(&row);
			warning(nil, "%s", err);
			free(err);
			flushimage(display, 1);
			qunlock(&row);
			break;
		case WKill:
			found = FALSE;
			ncmd = runestrlen(cmd);
			for(c=command; c; c=c->next){
				/* -1 for blank */
				if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
					if(postnote(PNGROUP, c->pid, "kill") < 0)
						warning(nil, "kill %S: %r\n", cmd);
					found = TRUE;
				}
			}
			if(!found)
				warning(nil, "Kill: no process %S\n", cmd);
			free(cmd);
			break;
		case WWait:
			pid = atoi(w.pid);
			lc = nil;
			for(c=command; c; c=c->next){
				if(c->pid == pid){
					if(lc)
						lc->next = c->next;
					else
						command = c->next;
					break;
				}
				lc = c;
			}
			qlock(&row);
			t = &row.tag;
			textcommit(t, TRUE);
			if(c == nil){
				/* helper processes use this exit status */
				if(strncmp(w.msg, "libthread", 9) != 0){
					p = emalloc(sizeof(Pid));
					p->pid = pid;
					strncpy(p->msg, w.msg, sizeof(p->msg));
					p->next = pids;
					pids = p;
				}
			}else{
				if(search(t, c->name, c->nname)){
					textdelete(t, t->q0, t->q1, TRUE);
					textsetselect(t, 0, 0);
				}
				if(w.msg[0])
					warning(c->md, "%s\n", w.msg);
				flushimage(display, 1);
			}
			qunlock(&row);
    Freecmd:
			if(c){
				if(c->iseditcmd)
					sendul(cedit, 0);
				free(c->text);
				free(c->av);
				free(c->name);
				fsysdelid(c->md);
				free(c);
			}
			break;
		case WCmd:
			/* has this command already exited? */
			lastp = nil;
			for(p=pids; p!=nil; p=p->next){
				if(p->pid == c->pid){
					if(p->msg[0])
						warning(c->md, "%s\n", p->msg);
					if(lastp == nil)
						pids = p->next;
					else
						lastp->next = p->next;
					free(p);
					goto Freecmd;
				}
				lastp = p;
			}
			c->next = command;
			command = c;
			qlock(&row);
			t = &row.tag;
			textcommit(t, TRUE);
			textinsert(t, 0, c->name, c->nname, TRUE);
			textsetselect(t, 0, 0);
			flushimage(display, 1);
			qunlock(&row);
			break;
		}
	}
}

void
xfidallocthread(void*)
{
	Xfid *xfree, *x;
	enum { Alloc, Free, N };
	static Alt alts[N+1];

	threadsetname("xfidallocthread");
	alts[Alloc].c = cxfidalloc;
	alts[Alloc].v = nil;
	alts[Alloc].op = CHANRCV;
	alts[Free].c = cxfidfree;
	alts[Free].v = &x;
	alts[Free].op = CHANRCV;
	alts[N].op = CHANEND;

	xfree = nil;
	for(;;){
		switch(alt(alts)){
		case Alloc:
			x = xfree;
			if(x)
				xfree = x->next;
			else{
				x = emalloc(sizeof(Xfid));
				x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
				x->arg = x;
				threadcreate(xfidctl, x->arg, STACK);
			}
			sendp(cxfidalloc, x);
			break;
		case Free:
			x->next = xfree;
			xfree = x;
			break;
		}
	}
}

Reffont*
rfget(int fix, int save, int setfont, char *name)
{
	Reffont *r;
	Font *f;
	int i;

	r = nil;
	if(name == nil){
		name = fontnames[fix];
		r = reffonts[fix];
	}
	if(r == nil){
		for(i=0; i<nfontcache; i++)
			if(strcmp(name, fontcache[i]->f->name) == 0){
				r = fontcache[i];
				goto Found;
			}
		f = openfont(display, name);
		if(f == nil){
			warning(nil, "can't open font file %s: %r\n", name);
			return nil;
		}
		r = emalloc(sizeof(Reffont));
		r->f = f;
		fontcache = realloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
		fontcache[nfontcache++] = r;
	}
    Found:
	if(save){
		incref(r);
		if(reffonts[fix])
			rfclose(reffonts[fix]);
		reffonts[fix] = r;
		fontnames[fix] = name;
	}
	if(setfont){
		reffont.f = r->f;
		incref(r);
		rfclose(reffonts[0]);
		font = r->f;
		reffonts[0] = r;
		incref(r);
		iconinit();
	}
	incref(r);
	return r;
}

void
rfclose(Reffont *r)
{
	int i;

	if(decref(r) == 0){
		for(i=0; i<nfontcache; i++)
			if(r == fontcache[i])
				break;
		if(i >= nfontcache)
			warning(nil, "internal error: can't find font in cache\n");
		else{
			nfontcache--;
			memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
		}
		freefont(r->f);
		free(r);
	}
}

Cursor boxcursor = {
	{-7, -7},
	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
	 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
	{0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
	 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
};

void
iconinit(void)
{
	Rectangle r;
	Image *tmp;

	/* Blue */
	tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
	tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), display->chan, 1, DPalegreygreen);
	tagcols[BORD] = allocimage(display, Rect(0,0,1,1), display->chan, 1, DPurpleblue);
	tagcols[TEXT] = display->black;
	tagcols[HTEXT] = display->black;

	/* Yellow */
	textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
	textcols[HIGH] = allocimage(display, Rect(0,0,1,1), display->chan, 1, DDarkyellow);
	textcols[BORD] = allocimage(display, Rect(0,0,1,1), display->chan, 1, DYellowgreen);
	textcols[TEXT] = display->black;
	textcols[HTEXT] = display->black;

	if(button){
		freeimage(button);
		freeimage(modbutton);
		freeimage(colbutton);
	}

	r = Rect(0, 0, Scrollwid+2, font->height+1);
	button = allocimage(display, r, display->chan, 0, DNofill);
	draw(button, r, tagcols[BACK], nil, r.min);
	r.max.x -= 2;
	border(button, r, 2, tagcols[BORD], ZP);

	r = button->r;
	modbutton = allocimage(display, r, display->chan, 0, DNofill);
	draw(modbutton, r, tagcols[BACK], nil, r.min);
	r.max.x -= 2;
	border(modbutton, r, 2, tagcols[BORD], ZP);
	r = insetrect(r, 2);
	tmp = allocimage(display, Rect(0,0,1,1), display->chan, 1, DMedblue);
	draw(modbutton, r, tmp, nil, ZP);
	freeimage(tmp);

	r = button->r;
	colbutton = allocimage(display, r, display->chan, 0, DPurpleblue);

	but2col = allocimage(display, r, display->chan, 1, 0xAA0000FF);
	but3col = allocimage(display, r, display->chan, 1, 0x006600FF);
}

/*
 * /dev/snarf updates when the file is closed, so we must open our own
 * fd here rather than use snarffd
 */

/* rio truncates larges snarf buffers, so this avoids using the
 * service if the string is huge */

#define MAXSNARF 100*1024

void
putsnarf(void)
{
	int fd, i, n;

	if(snarffd<0 || snarfbuf.nc==0)
		return;
	if(snarfbuf.nc > MAXSNARF)
		return;
	fd = open("/dev/snarf", OWRITE);
	if(fd < 0)
		return;
	for(i=0; i<snarfbuf.nc; i+=n){
		n = snarfbuf.nc-i;
		if(n >= NSnarf)
			n = NSnarf;
		bufread(&snarfbuf, i, snarfrune, n);
		if(fprint(fd, "%.*S", n, snarfrune) < 0)
			break;
	}
	close(fd);
}

void
getsnarf()
{
	int nulls;

	if(snarfbuf.nc > MAXSNARF)
		return;
	if(snarffd < 0)
		return;
	seek(snarffd, 0, 0);
	bufreset(&snarfbuf);
	bufload(&snarfbuf, 0, snarffd, &nulls);
}

[-- Attachment #3: exec.c --]
[-- Type: text/plain, Size: 28262 bytes --]

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <auth.h>
#include <fcall.h>
#include <plumb.h>
#include <regexp.h>
#include "dat.h"
#include "fns.h"

Buffer	snarfbuf;

void	del(Text*, Text*, Text*, int, int, Rune*, int);
void	delcol(Text*, Text*, Text*, int, int, Rune*, int);
void	dump(Text*, Text*, Text*, int, int, Rune*, int);
void	edit(Text*, Text*, Text*, int, int, Rune*, int);
void	exit(Text*, Text*, Text*, int, int, Rune*, int);
void	fontx(Text*, Text*, Text*, int, int, Rune*, int);
void	get(Text*, Text*, Text*, int, int, Rune*, int);
void	id(Text*, Text*, Text*, int, int, Rune*, int);
void	ignore(Text*, Text*, Text*, int, int, Rune*, int);
void	incl(Text*, Text*, Text*, int, int, Rune*, int);
void	kill(Text*, Text*, Text*, int, int, Rune*, int);
void	local(Text*, Text*, Text*, int, int, Rune*, int);
void	look(Text*, Text*, Text*, int, int, Rune*, int);
void	newcol(Text*, Text*, Text*, int, int, Rune*, int);
void	paste(Text*, Text*, Text*, int, int, Rune*, int);
void	put(Text*, Text*, Text*, int, int, Rune*, int);
void	putall(Text*, Text*, Text*, int, int, Rune*, int);
void	sendx(Text*, Text*, Text*, int, int, Rune*, int);
void	sort(Text*, Text*, Text*, int, int, Rune*, int);
void	tab(Text*, Text*, Text*, int, int, Rune*, int);
void	zeroxx(Text*, Text*, Text*, int, int, Rune*, int);

extern  Reprog* ignoreregx;

typedef struct Exectab Exectab;
struct Exectab
{
	Rune	*name;
	void	(*fn)(Text*, Text*, Text*, int, int, Rune*, int);
	int		mark;
	int		flag1;
	int		flag2;
};

Exectab exectab[] = {
	{ L"Cut",		cut,		TRUE,	TRUE,	TRUE	},
	{ L"Del",		del,		FALSE,	FALSE,	XXX		},
	{ L"Delcol",	delcol,	FALSE,	XXX,		XXX		},
	{ L"Delete",	del,		FALSE,	TRUE,	XXX		},
	{ L"Dump",	dump,	FALSE,	TRUE,	XXX		},
	{ L"Edit",		edit,		FALSE,	XXX,		XXX		},
	{ L"Exit",		exit,		FALSE,	XXX,		XXX		},
	{ L"Font",		fontx,	FALSE,	XXX,		XXX		},
	{ L"Get",		get,		FALSE,	TRUE,	XXX		},
	{ L"Ignore",		ignore, FALSE,	XXX,	XXX	},
	{ L"ID",		id,		FALSE,	XXX,		XXX		},
	{ L"Incl",		incl,		FALSE,	XXX,		XXX		},
	{ L"Kill",		kill,		FALSE,	XXX,		XXX		},
	{ L"Load",		dump,	FALSE,	FALSE,	XXX		},
	{ L"Local",		local,	FALSE,	XXX,		XXX		},
	{ L"Look",		look,		FALSE,	XXX,		XXX		},
	{ L"New",		new,		FALSE,	XXX,		XXX		},
	{ L"Newcol",	newcol,	FALSE,	XXX,		XXX		},
	{ L"Paste",		paste,	TRUE,	TRUE,	XXX		},
	{ L"Put",		put,		FALSE,	XXX,		XXX		},
	{ L"Putall",		putall,	FALSE,	XXX,		XXX		},
	{ L"Redo",		undo,	FALSE,	FALSE,	XXX		},
	{ L"Send",		sendx,	TRUE,	XXX,		XXX		},
	{ L"Snarf",		cut,		FALSE,	TRUE,	FALSE	},
	{ L"Sort",		sort,		FALSE,	XXX,		XXX		},
	{ L"Tab",		tab,		FALSE,	XXX,		XXX		},
	{ L"Undo",		undo,	FALSE,	TRUE,	XXX		},
	{ L"Zerox",	zeroxx,	FALSE,	XXX,		XXX		},
	{ nil, 			nil,		0,		0,		0		},
};

Exectab*
lookup(Rune *r, int n)
{
	Exectab *e;
	int nr;

	r = skipbl(r, n, &n);
	if(n == 0)
		return nil;
	findbl(r, n, &nr);
	nr = n-nr;
	for(e=exectab; e->name; e++)
		if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE)
			return e;
	return nil;
}

int
isexecc(int c)
{
	if(isfilec(c))
		return 1;
	return c=='<' || c=='|' || c=='>';
}

void
execute(Text *t, uint aq0, uint aq1, int external, Text *argt)
{
	uint q0, q1;
	Rune *r, *s;
	char *b, *a, *aa;
	Exectab *e;
	int c, n, f;
	Runestr dir;

	q0 = aq0;
	q1 = aq1;
	if(q1 == q0){	/* expand to find word (actually file name) */
		/* if in selection, choose selection */
		if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
			q0 = t->q0;
			q1 = t->q1;
		}else{
			while(q1<t->file->nc && isexecc(c=textreadc(t, q1)) && c!=':')
				q1++;
			while(q0>0 && isexecc(c=textreadc(t, q0-1)) && c!=':')
				q0--;
			if(q1 == q0)
				return;
		}
	}
	r = runemalloc(q1-q0);
	bufread(t->file, q0, r, q1-q0);
	e = lookup(r, q1-q0);
	if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
		f = 0;
		if(e)
			f |= 1;
		if(q0!=aq0 || q1!=aq1){
			bufread(t->file, aq0, r, aq1-aq0);
			f |= 2;
		}
		aa = getbytearg(argt, TRUE, TRUE, &a);
		if(a){
			if(strlen(a) > EVENTSIZE){	/* too big; too bad */
				free(aa);
				free(a);
				warning(nil, "`argument string too long\n");
				return;
			}
			f |= 8;
		}
		c = 'x';
		if(t->what == Body)
			c = 'X';
		n = aq1-aq0;
		if(n <= EVENTSIZE)
			winevent(t->w, "%c%d %d %d %d %.*S\n", c, aq0, aq1, f, n, n, r);
		else
			winevent(t->w, "%c%d %d %d 0 \n", c, aq0, aq1, f, n);
		if(q0!=aq0 || q1!=aq1){
			n = q1-q0;
			bufread(t->file, q0, r, n);
			if(n <= EVENTSIZE)
				winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q1, n, n, r);
			else
				winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1, n);
		}
		if(a){
			winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(a), a);
			if(aa)
				winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(aa), aa);
			else
				winevent(t->w, "%c0 0 0 0 \n", c);
		}
		free(r);
		free(aa);
		free(a);
		return;
	}
	if(e){
		if(e->mark && seltext!=nil)
		if(seltext->what == Body){
			seq++;
			filemark(seltext->w->body.file);
		}
		s = skipbl(r, q1-q0, &n);
		s = findbl(s, n, &n);
		s = skipbl(s, n, &n);
		(*e->fn)(t, seltext, argt, e->flag1, e->flag2, s, n);
		free(r);
		return;
	}

	b = runetobyte(r, q1-q0);
	free(r);
	dir = dirname(t, nil, 0);
	if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
		free(dir.r);
		dir.r = nil;
		dir.nr = 0;
	}
	aa = getbytearg(argt, TRUE, TRUE, &a);
	if(t->w)
		incref(t->w);
	run(t->w, b, dir.r, dir.nr, TRUE, aa, a, FALSE);
}

char*
printarg(Text *argt, uint q0, uint q1)
{
	char *buf;

	if(argt->what!=Body || argt->file->name==nil)
		return nil;
	buf = emalloc(argt->file->nname+32);
	if(q0 == q1)
		sprint(buf, "%.*S:#%d", argt->file->nname, argt->file->name, q0);
	else
		sprint(buf, "%.*S:#%d,#%d", argt->file->nname, argt->file->name, q0, q1);
	return buf;
}

char*
getarg(Text *argt, int doaddr, int dofile, Rune **rp, int *nrp)
{
	int n;
	Expand e;
	char *a;

	*rp = nil;
	*nrp = 0;
	if(argt == nil)
		return nil;
	a = nil;
	textcommit(argt, TRUE);
	if(expand(argt, argt->q0, argt->q1, &e)){
		free(e.bname);
		if(e.nname && dofile){
			e.name = runerealloc(e.name, e.nname+1);
			if(doaddr)
				a = printarg(argt, e.q0, e.q1);
			*rp = e.name;
			*nrp = e.nname;
			return a;
		}
		free(e.name);
	}else{
		e.q0 = argt->q0;
		e.q1 = argt->q1;
	}
	n = e.q1 - e.q0;
	*rp = runemalloc(n+1);
	bufread(argt->file, e.q0, *rp, n);
	if(doaddr)
		a = printarg(argt, e.q0, e.q1);
	*nrp = n;
	return a;
}

char*
getbytearg(Text *argt, int doaddr, int dofile, char **bp)
{
	Rune *r;
	int n;
	char *aa;

	*bp = nil;
	aa = getarg(argt, doaddr, dofile, &r, &n);
	if(r == nil)
		return nil;
	*bp = runetobyte(r, n);
	free(r);
	return aa;
}

void
newcol(Text *et, Text*, Text*, int, int, Rune*, int)
{
	Column *c;

	c = rowadd(et->row, nil, -1);
	if(c)
		winsettag(coladd(c, nil, nil, -1));
}

void
delcol(Text *et, Text*, Text*, int, int, Rune*, int)
{
	int i;
	Column *c;
	Window *w;

	c = et->col;
	if(c==nil || colclean(c)==0)
		return;
	for(i=0; i<c->nw; i++){
		w = c->w[i];
		if(w->nopen[QWevent]+w->nopen[QWaddr]+w->nopen[QWdata] > 0){
			warning(nil, "can't delete column; %.*S is running an external command\n", w->body.file->nname, w->body.file->name);
			return;
		}
	}
	rowclose(et->col->row, et->col, TRUE);
}

void
del(Text *et, Text*, Text*, int flag1, int, Rune*, int)
{
	if(et->col==nil || et->w == nil)
		return;
	if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE))
		colclose(et->col, et->w, TRUE);
}

void
sort(Text *et, Text*, Text*, int, int, Rune*, int)
{
	if(et->col)
		colsort(et->col);
}

uint
seqof(Window *w, int isundo)
{
	/* if it's undo, see who changed with us */
	if(isundo)
		return w->body.file->seq;
	/* if it's redo, see who we'll be sync'ed up with */
	return fileredoseq(w->body.file);
}

void
undo(Text *et, Text*, Text*, int flag1, int, Rune*, int)
{
	int i, j;
	Column *c;
	Window *w;
	uint seq;

	if(et==nil || et->w== nil)
		return;
	seq = seqof(et->w, flag1);
	if(seq == 0){
		/* nothing to undo */
		return;
	}
	/*
	 * Undo the executing window first. Its display will update. other windows
	 * in the same file will not call show() and jump to a different location in the file.
	 * Simultaneous changes to other files will be chaotic, however.
	 */
	winundo(et->w, flag1);
	for(i=0; i<row.ncol; i++){
		c = row.col[i];
		for(j=0; j<c->nw; j++){
			w = c->w[j];
			if(w == et->w)
				continue;
			if(seqof(w, flag1) == seq)
				winundo(w, flag1);
		}
	}
}

char*
getname(Text *t, Text *argt, Rune *arg, int narg, int isput)
{
	char *s;
	Rune *r;
	int i, n, promote;
	Runestr dir;

	getarg(argt, FALSE, TRUE, &r, &n);
	promote = FALSE;
	if(r == nil)
		promote = TRUE;
	else if(isput){
		/* if are doing a Put, want to synthesize name even for non-existent file */
		/* best guess is that file name doesn't contain a slash */
		promote = TRUE;
		for(i=0; i<n; i++)
			if(r[i] == '/'){
				promote = FALSE;
				break;
			}
		if(promote){
			t = argt;
			arg = r;
			narg = n;
		}
	}
	if(promote){
		n = narg;
		if(n <= 0){
			s = runetobyte(t->file->name, t->file->nname);
			return s;
		}
		/* prefix with directory name if necessary */
		dir.r = nil;
		dir.nr = 0;
		if(n>0 && arg[0]!='/'){
			dir = dirname(t, nil, 0);
			if(n==1 && dir.r[0]=='.'){	/* sigh */
				free(dir.r);
				dir.r = nil;
				dir.nr = 0;
			}
		}
		if(dir.r){
			r = runemalloc(dir.nr+n+1);
			runemove(r, dir.r, dir.nr);
			free(dir.r);
			runemove(r+dir.nr, arg, n);
			n += dir.nr;
		}else{
			r = runemalloc(n+1);
			runemove(r, arg, n);
		}
	}
	s = runetobyte(r, n);
	free(r);
	if(strlen(s) == 0){
		free(s);
		s = nil;
	}
	return s;
}

void
zeroxx(Text *et, Text *t, Text*, int, int, Rune*, int)
{
	Window *nw;
	int c, locked;

	locked = FALSE;
	if(t!=nil && t->w!=nil && t->w!=et->w){
		locked = TRUE;
		c = 'M';
		if(et->w)
			c = et->w->owner;
		winlock(t->w, c);
	}
	if(t == nil)
		t = et;
	if(t==nil || t->w==nil)
		return;
	t = &t->w->body;
	if(t->w->isdir)
		warning(nil, "%.*S is a directory; Zerox illegal\n", t->file->nname, t->file->name);
	else{
		nw = coladd(t->w->col, nil, t->w, -1);
		/* ugly: fix locks so w->unlock works */
		winlock1(nw, t->w->owner);
	}
	if(locked)
		winunlock(t->w);
}

void
get(Text *et, Text *t, Text *argt, int flag1, int, Rune *arg, int narg)
{
	char *name;
	Rune *r;
	int i, n, dirty, samename;
	Window *w;
	Text *u;
	Dir d;

	if(flag1)
		if(et==nil || et->w==nil)
			return;
	if(!et->w->isdir && (et->w->body.file->nc>0 && !winclean(et->w, TRUE)))
		return;
	w = et->w;
	t = &w->body;
	name = getname(t, argt, arg, narg, FALSE);
	if(name == nil){
		warning(nil, "no file name\n");
		return;
	}
	if(t->file->ntext>1 && dirstat(name, &d)==0 && d.qid.path & CHDIR){
		warning(nil, "%s is a directory; can't read with multiple windows on it\n", name);
		return;
	}
	r = bytetorune(name, &n);
	for(i=0; i<t->file->ntext; i++){
		u = t->file->text[i];
		/* second and subsequent calls with zero an already empty buffer, but OK */
		textreset(u);
		windirfree(u->w);
	}
	samename = runeeq(r, n, t->file->name, t->file->nname);
	textload(t, 0, name, samename);
	if(samename){
		t->file->mod = FALSE;
		dirty = FALSE;
	}else{
		t->file->mod = TRUE;
		dirty = TRUE;
	}
	for(i=0; i<t->file->ntext; i++)
		t->file->text[i]->w->dirty = dirty;
	free(name);
	free(r);
	winsettag(w);
	t->file->unread = FALSE;
	for(i=0; i<t->file->ntext; i++){
		u = t->file->text[i];
		textsetselect(&u->w->tag, u->w->tag.file->nc, u->w->tag.file->nc);
		textscrdraw(u);
	}
}

void
putfile(File *f, int q0, int q1, Rune *namer, int nname)
{
	uint n, m;
	Rune *r;
	char *s, *name;
	int i, fd, q;
	Dir d;
	Window *w;

	w = f->curtext->w;
	name = runetobyte(namer, nname);
	if(runeeq(namer, nname, f->name, f->nname) && dirstat(name, &d)>=0){
		if(f->dev!=d.dev || f->qidpath!=d.qid.path || f->mtime<d.mtime){
			f->dev = d.dev;
			f->qidpath = d.qid.path;
			f->mtime = d.mtime;
			if(f->unread)
				warning(nil, "%s not written; file already exists\n", name);
			else
				warning(nil, "%s modified since last read\n", name);
			goto Rescue1;
		}
	}
	fd = create(name, OWRITE, 0666);
	if(fd < 0){
		warning(nil, "can't create file %s: %r\n", name);
		goto Rescue1;
	}
	r = fbufalloc();
	s = fbufalloc();
	if(dirfstat(fd, &d)>=0 && (d.mode&CHAPPEND) && d.length>0){
		warning(nil, "%s not written; file is append only\n", name);
		goto Rescue2;
	}

	for(q=q0; q<q1; q+=n){
		n = q1 - q;
		if(n > BUFSIZE/UTFmax)
			n = BUFSIZE/UTFmax;
		bufread(f, q, r, n);
		m = snprint(s, BUFSIZE+1, "%.*S", n, r);
		if(write(fd, s, m) != m){
			warning(nil, "can't write file %s: %r\n", name);
			goto Rescue2;
		}
	}
	if(runeeq(namer, nname, f->name, f->nname)){
		if(q0!=0 || q1!=f->nc){
			f->mod = TRUE;
			w->dirty = TRUE;
			f->unread = TRUE;
		}else{
			dirfstat(fd, &d);	/* ignore error; use old values if we failed */
			f->qidpath = d.qid.path;
			f->dev = d.dev;
			f->mtime = d.mtime;
			f->mod = FALSE;
			w->dirty = FALSE;
			f->unread = FALSE;
		}
		for(i=0; i<f->ntext; i++){
			f->text[i]->w->putseq = f->seq;
			f->text[i]->w->dirty = w->dirty;
		}
	}
	fbuffree(s);
	fbuffree(r);
	free(namer);
	free(name);
	close(fd);
	winsettag(w);
	return;

    Rescue2:
	fbuffree(s);
	fbuffree(r);
	close(fd);
	/* fall through */

    Rescue1:
	free(namer);
	free(name);
}

void
put(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
{
	int nname;
	Rune  *namer;
	Window *w;
	File *f;
	char *name;

	if(et==nil || et->w==nil || et->w->isdir)
		return;
	w = et->w;
	f = w->body.file;
	name = getname(&w->body, argt, arg, narg, TRUE);
	if(name == nil){
		warning(nil, "no file name\n");
		return;
	}
	namer = bytetorune(name, &nname);
	putfile(f, 0, f->nc, namer, nname);
	free(name);
}

void
dump(Text *, Text *, Text *argt, int isdump, int, Rune *arg, int narg)
{
	char *name;

	if(narg)
		name = runetobyte(arg, narg);
	else
		getbytearg(argt, FALSE, TRUE, &name);
	if(isdump)
		rowdump(&row, name);
	else
		rowload(&row, name, FALSE);
	free(name);
}


void
ignore(Text *, Text *, Text *argt, int, int, Rune *arg, int narg)
{
	char *exp;

	exp = nil;
	if(narg)
		exp = runetobyte(arg, narg);
	else
		getbytearg(argt, FALSE, TRUE, &exp);
	if (ignoreregx)
		free(ignoreregx);
	ignoreregx = (exp == nil) ? nil : regcomp(exp);
	free(exp);
}

void
cut(Text *et, Text *t, Text*, int dosnarf, int docut, Rune*, int)
{
	uint q0, q1, n, locked, c;
	Rune *r;

	/* use current window if snarfing and its selection is non-null */
	if(et!=t && dosnarf && et->w!=nil){
		if(et->w->body.q1>et->w->body.q0){
			t = &et->w->body;
			if(docut)
				filemark(t->file);	/* seq has been incremented by execute */
		}else if(et->w->tag.q1>et->w->tag.q0)
			t = &et->w->tag;
	}
	if(t == nil){
		/* can only happen if seltext == nil */
		return;
	}
	locked = FALSE;
	if(t->w!=nil && et->w!=t->w){
		locked = TRUE;
		c = 'M';
		if(et->w)
			c = et->w->owner;
		winlock(t->w, c);
	}
	if(t->q0 == t->q1){
		if(locked)
			winunlock(t->w);
		return;
	}
	if(dosnarf){
		q0 = t->q0;
		q1 = t->q1;
		bufdelete(&snarfbuf, 0, snarfbuf.nc);
		r = fbufalloc();
		while(q0 < q1){
			n = q1 - q0;
			if(n > RBUFSIZE)
				n = RBUFSIZE;
			bufread(t->file, q0, r, n);
			bufinsert(&snarfbuf, snarfbuf.nc, r, n);
			q0 += n;
		}
		fbuffree(r);
		putsnarf();
	}
	if(docut){
		textdelete(t, t->q0, t->q1, TRUE);
		textsetselect(t, t->q0, t->q0);
		if(t->w){
			textscrdraw(t);
			winsettag(t->w);
		}
	}else if(dosnarf)	/* Snarf command */
		argtext = t;
	if(locked)
		winunlock(t->w);
}

void
paste(Text *et, Text *t, Text*, int selectall, int tobody, Rune*, int)
{
	int c;
	uint q, q0, q1, n;
	Rune *r;

	getsnarf();
	if(snarfbuf.nc==0)
		return;

	/* if(tobody), use body of executing window  (Paste or Send command) */
	if(tobody && et!=nil && et->w!=nil){
		t = &et->w->body;
		filemark(t->file);	/* seq has been incremented by execute */
	}
	if(t == nil)
		return;

	if(t->w!=nil && et->w!=t->w){
		c = 'M';
		if(et->w)
			c = et->w->owner;
		winlock(t->w, c);
	}
	cut(t, t, nil, FALSE, TRUE, nil, 0);
	q = 0;
	q0 = t->q0;
	q1 = t->q0+snarfbuf.nc;
	r = fbufalloc();
	while(q0 < q1){
		n = q1 - q0;
		if(n > RBUFSIZE)
			n = RBUFSIZE;
		if(r == nil)
			r = runemalloc(n);
		bufread(&snarfbuf, q, r, n);
		textinsert(t, q0, r, n, TRUE);
		q += n;
		q0 += n;
	}
	fbuffree(r);
	if(selectall)
		textsetselect(t, t->q0, q1);
	else
		textsetselect(t, q1, q1);
	if(t->w){
		textscrdraw(t);
		winsettag(t->w);
	}
	if(t->w!=nil && et->w!=t->w)
		winunlock(t->w);
}

void
look(Text *et, Text *t, Text *argt, int, int, Rune *arg, int narg)
{
	Rune *r;
	int n;

	if(et && et->w){
		t = &et->w->body;
		if(narg > 0){
			search(t, arg, narg);
			return;
		}
		getarg(argt, FALSE, FALSE, &r, &n);
		if(r == nil){
			n = t->q1-t->q0;
			r = runemalloc(n);
			bufread(t->file, t->q0, r, n);
		}
		search(t, r, n);
		free(r);
	}
}

void
sendx(Text *et, Text *t, Text*, int, int, Rune*, int)
{
	if(et->w==nil)
		return;
	t = &et->w->body;
	if(t->q0 != t->q1)
		cut(t, t, nil, TRUE, FALSE, nil, 0);
	textsetselect(t, t->file->nc, t->file->nc);
	paste(t, t, nil, TRUE, TRUE, nil, 0);
	if(textreadc(t, t->file->nc-1) != '\n'){
		textinsert(t, t->file->nc, L"\n", 1, TRUE);
		textsetselect(t, t->file->nc, t->file->nc);
	}
}

void
edit(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
{
	Rune *r;
	int len;

	if(et == nil)
		return;
	getarg(argt, FALSE, TRUE, &r, &len);
	seq++;
	if(r != nil){
		editcmd(et, r, len);
		free(r);
	}else
		editcmd(et, arg, narg);
}

void
exit(Text*, Text*, Text*, int, int, Rune*, int)
{
	if(rowclean(&row)){
		sendul(cexit, 0);
		threadexits(nil);
	}
}

void
putall(Text*, Text*, Text*, int, int, Rune*, int)
{
	int i, j, e;
	Window *w;
	Column *c;
	char *a;

	for(i=0; i<row.ncol; i++){
		c = row.col[i];
		for(j=0; j<c->nw; j++){
			w = c->w[j];
			if(w->isscratch || w->isdir || w->body.file->nname==0)
				continue;
			if(w->nopen[QWevent] > 0)
				continue;
			a = runetobyte(w->body.file->name, w->body.file->nname);
			e = access(a, 0);
			if(w->body.file->mod || w->body.ncache)
				if(e < 0)
					warning(nil, "no auto-Put of %s: %r\n", a);
				else{
					wincommit(w, &w->body);
					put(&w->body, nil, nil, XXX, XXX, nil, 0);
				}
			free(a);
		}
	}
}


void
id(Text *et, Text*, Text*, int, int, Rune*, int)
{
	if(et && et->w)
		warning(nil, "/mnt/acme/%d/\n", et->w->id);
}

void
local(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
{
	char *a, *aa;
	Runestr dir;

	aa = getbytearg(argt, TRUE, TRUE, &a);

	dir = dirname(et, nil, 0);
	if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
		free(dir.r);
		dir.r = nil;
		dir.nr = 0;
	}
	run(nil, runetobyte(arg, narg), dir.r, dir.nr, FALSE, aa, a, FALSE);
}

void
kill(Text*, Text*, Text *argt, int, int, Rune *arg, int narg)
{
	Rune *a, *cmd, *r;
	int na;

	getarg(argt, FALSE, FALSE, &r, &na);
	if(r)
		kill(nil, nil, nil, 0, 0, r, na);
	/* loop condition: *arg is not a blank */
	for(;;){
		a = findbl(arg, narg, &na);
		if(a == arg)
			break;
		cmd = runemalloc(narg-na+1);
		runemove(cmd, arg, narg-na);
		sendp(ckill, cmd);
		arg = skipbl(a, na, &narg);
	}
}

void
fontx(Text *et, Text *t, Text *argt, int, int, Rune *arg, int narg)
{
	Rune *a, *r, *flag, *file;
	int na, nf;
	char *aa;
	Reffont *newfont;
	Dirlist *dp;
	int i, fix;

	if(et==nil || et->w==nil)
		return;
	t = &et->w->body;
	flag = nil;
	file = nil;
	/* loop condition: *arg is not a blank */
	nf = 0;
	for(;;){
		a = findbl(arg, narg, &na);
		if(a == arg)
			break;
		r = runemalloc(narg-na+1);
		runemove(r, arg, narg-na);
		if(runeeq(r, narg-na, L"fix", 3) || runeeq(r, narg-na, L"var", 3)){
			free(flag);
			flag = r;
		}else{
			free(file);
			file = r;
			nf = narg-na;
		}
		arg = skipbl(a, na, &narg);
	}
	getarg(argt, FALSE, TRUE, &r, &na);
	if(r)
		if(runeeq(r, na, L"fix", 3) || runeeq(r, na, L"var", 3)){
			free(flag);
			flag = r;
		}else{
			free(file);
			file = r;
			nf = na;
		}
	fix = 1;
	if(flag)
		fix = runeeq(flag, runestrlen(flag), L"fix", 3);
	else if(file == nil){
		newfont = rfget(FALSE, FALSE, FALSE, nil);
		if(newfont)
			fix = strcmp(newfont->f->name, t->font->name)==0;
	}
	if(file){
		aa = runetobyte(file, nf);
		newfont = rfget(fix, flag!=nil, FALSE, aa);
		free(aa);
	}else
		newfont = rfget(fix, FALSE, FALSE, nil);
	if(newfont){
		draw(screen, t->w->r, textcols[BACK], nil, ZP);
		rfclose(t->reffont);
		t->reffont = newfont;
		t->font = newfont->f;
		frinittick(t);
		if(t->w->isdir){
			t->all.min.x++;	/* force recolumnation; disgusting! */
			for(i=0; i<t->w->ndl; i++){
				dp = t->w->dlp[i];
				aa = runetobyte(dp->r, dp->nr);
				dp->wid = stringwidth(newfont->f, aa);
				free(aa);
			}
		}
		/* avoid shrinking of window due to quantization */
		colgrow(t->w->col, t->w, -1);
	}
	free(file);
	free(flag);
}

void
incl(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
{
	Rune *a, *r;
	Window *w;
	int na, n, len;

	if(et==nil || et->w==nil)
		return;
	w = et->w;
	n = 0;
	getarg(argt, FALSE, TRUE, &r, &len);
	if(r){
		n++;
		winaddincl(w, r, len);
	}
	/* loop condition: *arg is not a blank */
	for(;;){
		a = findbl(arg, narg, &na);
		if(a == arg)
			break;
		r = runemalloc(narg-na+1);
		runemove(r, arg, narg-na);
		n++;
		winaddincl(w, r, narg-na);
		arg = skipbl(a, na, &narg);
	}
	if(n==0 && w->nincl){
		for(n=w->nincl; --n>=0; )
			warning(nil, "%S ", w->incl[n]);
		warning(nil, "\n");
	}
}

void
tab(Text *et, Text*, Text *argt, int, int, Rune *arg, int narg)
{
	Rune *a, *r;
	Window *w;
	int na, len, tab;
	char *p;

	if(et==nil || et->w==nil)
		return;
	w = et->w;
	getarg(argt, FALSE, TRUE, &r, &len);
	tab = 0;
	if(r!=nil && len>0){
		p = runetobyte(r, len);
		if('0'<=p[0] && p[0]<='9')
			tab = atoi(p);
		free(p);
	}else{
		a = findbl(arg, narg, &na);
		if(a != arg){
			p = runetobyte(arg, narg-na);
			if('0'<=p[0] && p[0]<='9')
				tab = atoi(p);
			free(p);
		}
	}
	if(tab > 0){
		if(w->body.tabstop != tab){
			w->body.tabstop = tab;
			winresize(w, w->r, 1);
		}
	}else
		warning(nil, "%.*S: Tab %d\n", w->body.file->nname, w->body.file->name, w->body.tabstop);
}

void
runproc(void *argvp)
{
	/* args: */
		Window *win;
		char *s;
		Rune *rdir;
		int ndir;
		int newns;
		char *argaddr;
		char *arg;
		Command *c;
		Channel *cpid;
		int iseditcmd;
	/* end of args */
	char *e, *t, *name, *dir, **av, *news;
	Rune r, **incl;
	int ac, w, inarg, i, n, fd, nincl, winid;
	int pipechar;
	char buf[512];
	static void *parg[2];
	void **argv;

	argv = argvp;
	win = argv[0];
	s = argv[1];
	rdir = argv[2];
	ndir = (int)argv[3];
	newns = (int)argv[4];
	argaddr = argv[5];
	arg = argv[6];
	c = argv[7];
	cpid = argv[8];
	iseditcmd = (int)argv[9];
	free(argv);

	t = s;
	while(*t==' ' || *t=='\n' || *t=='\t')
		t++;
	for(e=t; *e; e++)
		if(*e==' ' || *e=='\n' || *e=='\t' )
			break;
	name = emalloc((e-t)+2);
	memmove(name, t, e-t);
	name[e-t] = 0;
	e = utfrrune(name, '/');
	if(e)
		strcpy(name, e+1);
	strcat(name, " ");	/* add blank here for ease in waittask */
	c->name = bytetorune(name, &c->nname);
	free(name);
	pipechar = 0;
	if(*t=='<' || *t=='|' || *t=='>')
		pipechar = *t++;
	c->iseditcmd = iseditcmd;
	c->text = s;
	if(rdir != nil){
		dir = runetobyte(rdir, ndir);
		chdir(dir);	/* ignore error: probably app. window */
		free(dir);
	}
	if(newns){
		nincl = 0;
		incl = nil;
		if(win){
			nincl = win->nincl;
			if(nincl > 0){
				incl = emalloc(nincl*sizeof(Rune*));
				for(i=0; i<nincl; i++){
					n = runestrlen(win->incl[i]);
					incl[i] = runemalloc(n+1);
					runemove(incl[i], win->incl[i], n);
				}
			}
			winid = win->id;
			winclose(win);
		}else{
			winid = 0;
			if(activewin)
				winid = activewin->id;
		}
		rfork(RFNAMEG|RFENVG|RFFDG|RFNOTEG);
		c->md = fsysmount(rdir, ndir, incl, nincl);
		if(c->md == nil){
			threadprint(2, "child: can't mount /dev/cons: %r\n");
			threadexits("mount");
		}
		close(0);
		if(winid>0 && (pipechar=='|' || pipechar=='>')){
			sprint(buf, "/mnt/acme/%d/rdsel", winid);
			open(buf, OREAD);
		}else
			open("/dev/null", OREAD);
		close(1);
		if((winid>0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
			if(iseditcmd){
				if(winid > 0)
					sprint(buf, "/mnt/acme/%d/editout", winid);
				else
					sprint(buf, "/mnt/acme/editout");
			}else
				sprint(buf, "/mnt/acme/%d/wrsel", winid);
			open(buf, OWRITE);
			close(2);
			open("/dev/cons", OWRITE);
		}else{
			open("/dev/cons", OWRITE);
			dup(1, 2);
		}
	}else{
		if(win)
			winclose(win);
		rfork(RFFDG|RFNOTEG);
		fsysclose();
		close(0);
		open("/dev/null", OREAD);
		close(1);
		open(acmeerrorfile, OWRITE);
		dup(1, 2);
	}

	if(argaddr)
		putenv("acmeaddr", argaddr);
	if(strlen(t) > sizeof buf-10)	/* may need to print into stack */
		goto Hard;
	inarg = FALSE;
	for(e=t; *e; e+=w){
		w = chartorune(&r, e);
		if(r==' ' || r=='\t')
			continue;
		if(r < ' ')
			goto Hard;
		if(utfrune("#;&|^$=`'{}()<>[]*?^~`", r))
			goto Hard;
		inarg = TRUE;
	}
	if(!inarg)
		goto Fail;

	ac = 0;
	av = nil;
	inarg = FALSE;
	for(e=t; *e; e+=w){
		w = chartorune(&r, e);
		if(r==' ' || r=='\t'){
			inarg = FALSE;
			*e = 0;
			continue;
		}
		if(!inarg){
			inarg = TRUE;
			av = realloc(av, (ac+1)*sizeof(char**));
			av[ac++] = e;
		}
	}
	av = realloc(av, (ac+2)*sizeof(char**));
	av[ac++] = arg;
	av[ac] = nil;
	c->av = av;
	procexec(cpid, av[0], av);
	e = av[0];
	if(e[0]=='/' || (e[0]=='.' && e[1]=='/'))
		goto Fail;
	if(cputype){
		sprint(buf, "%s/%s", cputype, av[0]);
		procexec(cpid, buf, av);
	}
	sprint(buf, "/bin/%s", av[0]);
	procexec(cpid, buf, av);
	goto Fail;

Hard:

	/*
	 * ugly: set path = (. $cputype /bin)
	 * should honor $path if unusual.
	 */
	if(cputype){
		n = 0;
		memmove(buf+n, ".", 2);
		n += 2;
		i = strlen(cputype)+1;
		memmove(buf+n, cputype, i);
		n += i;
		memmove(buf+n, "/bin", 5);
		n += 5;
		fd = create("/env/path", OWRITE, 0666);
		write(fd, buf, n);
		close(fd);
	}

	if(arg){
		news = emalloc(strlen(t) + 1 + 1 + strlen(arg) + 1 + 1);
		if(news){
			sprint(news, "%s '%s'", t, arg);	/* BUG: what if quote in arg? */
			free(s);
			t = news;
			c->text = news;
		}
	}
	procexecl(cpid, "/bin/rc", "rc", "-c", t, nil);

   Fail:
	/* procexec hasn't happened, so need to send our own pid */
	sendul(cpid, getpid());
	threadexits(nil);
}

void
runwaittask(void *v)
{
	Command *c;
	Channel *cpid;
	void **a;

	threadsetname("runwaittask");
	a = v;
	c = a[0];
	cpid = a[1];
	free(a);
	do
		c->pid = recvul(cpid);
	while(c->pid == ~0);
	if(c->pid != 0)	/* successful exec */
		sendp(ccommand, c);
	else{
		free(c->name);
		free(c);
	}
	chanfree(cpid);
}

void
run(Window *win, char *s, Rune *rdir, int ndir, int newns, char *argaddr, char *xarg, int iseditcmd)
{
	void **arg;
	Command *c;
	Channel *cpid;

	if(s == nil)
		return;

	arg = emalloc(10*sizeof(void*));
	c = emalloc(sizeof *c);
	cpid = chancreate(sizeof(ulong), 0);
	arg[0] = win;
	arg[1] = s;
	arg[2] = rdir;
	arg[3] = (void*)ndir;
	arg[4] = (void*)newns;
	arg[5] = argaddr;
	arg[6] = xarg;
	arg[7] = c;
	arg[8] = cpid;
	arg[9] = (void*)iseditcmd;
	proccreate(runproc, arg, STACK);
	/* mustn't block here because must be ready to answer mount() call in run() */
	arg = emalloc(2*sizeof(void*));
	arg[0] = c;
	arg[1] = cpid;
	threadcreate(runwaittask, arg, STACK);
}

[-- Attachment #4: rows.c --]
[-- Type: text/plain, Size: 15143 bytes --]

#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 <auth.h>
#include <fcall.h>
#include <bio.h>
#include <plumb.h>
#include "dat.h"
#include "fns.h"

void
rowinit(Row *row, Rectangle r)
{
	Rectangle r1;
	Text *t;
	char *c;
	Rune rc[100];
	int i;

	draw(screen, r, display->white, nil, ZP);
	row->r = r;
	row->col = nil;
	row->ncol = 0;
	r1 = r;
	r1.max.y = r1.min.y + font->height;
	t = &row->tag;
	textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols);
	t->what = Rowtag;
	t->row = row;
	t->w = nil;
	t->col = nil;
	r1.min.y = r1.max.y;
	r1.max.y += Border;
	draw(screen, r1, display->black, nil, ZP);
	if (c=getenv("acmetag")){
		for (i=0; *c && i<99; i++){
			c += chartorune(rc+i, c);
		}
		rc[i] = 0;
		textinsert(t, 0, rc, i, TRUE);
	}else
		textinsert(t, 0, L"Newcol Kill Putall Dump Exit Ignore ", 36, TRUE);
	textsetselect(t, t->file->nc, t->file->nc);
}

Column*
rowadd(Row *row, Column *c, int x)
{
	Rectangle r, r1;
	Column *d;
	int i;

	d = nil;
	r = row->r;
	r.min.y = row->tag.r.max.y+Border;
	if(x<r.min.x && row->ncol>0){	/*steal 40% of last column by default */
		d = row->col[row->ncol-1];
		x = d->r.min.x + 3*Dx(d->r)/5;
	}
	/* look for column we'll land on */
	for(i=0; i<row->ncol; i++){
		d = row->col[i];
		if(x < d->r.max.x)
			break;
	}
	if(row->ncol > 0){
		if(i < row->ncol)
			i++;	/* new column will go after d */
		r = d->r;
		if(Dx(r) < 100)
			return nil;
		draw(screen, r, display->white, nil, ZP);
		r1 = r;
		r1.max.x = min(x, r.max.x-50);
		if(Dx(r1) < 50)
			r1.max.x = r1.min.x+50;
		colresize(d, r1);
		r1.min.x = r1.max.x;
		r1.max.x = r1.min.x+Border;
		draw(screen, r1, display->black, nil, ZP);
		r.min.x = r1.max.x;
	}
	if(c == nil){
		c = emalloc(sizeof(Column));
		colinit(c, r);
		incref(&reffont);
	}else
		colresize(c, r);
	c->row = row;
	c->tag.row = row;
	row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*));
	memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*));
	row->col[i] = c;
	row->ncol++;
	clearmouse();
	return c;
}

void
rowresize(Row *row, Rectangle r)
{
	int i, dx, odx;
	Rectangle r1, r2;
	Column *c;

	dx = Dx(r);
	odx = Dx(row->r);
	row->r = r;
	r1 = r;
	r1.max.y = r1.min.y + font->height;
	textresize(&row->tag, r1);
	r1.min.y = r1.max.y;
	r1.max.y += Border;
	draw(screen, r1, display->black, nil, ZP);
	r.min.y = r1.max.y;
	r1 = r;
	r1.max.x = r1.min.x;
	for(i=0; i<row->ncol; i++){
		c = row->col[i];
		r1.min.x = r1.max.x;
		if(i == row->ncol-1)
			r1.max.x = r.max.x;
		else
			r1.max.x = r1.min.x+Dx(c->r)*dx/odx;
		if(i > 0){
			r2 = r1;
			r2.max.x = r2.min.x+Border;
			draw(screen, r2, display->black, nil, ZP);
			r1.min.x = r2.max.x;
		}
		colresize(c, r1);
	}
}

void
rowdragcol(Row *row, Column *c, int)
{
	Rectangle r;
	int i, b, x;
	Point p, op;
	Column *d;

	clearmouse();
	setcursor(mousectl, &boxcursor);
	b = mouse->buttons;
	op = mouse->xy;
	while(mouse->buttons == b)
		readmouse(mousectl);
	setcursor(mousectl, nil);
	if(mouse->buttons){
		while(mouse->buttons)
			readmouse(mousectl);
		return;
	}

	for(i=0; i<row->ncol; i++)
		if(row->col[i] == c)
			goto Found;
	error("can't find column");

  Found:
	if(i == 0)
		return;
	p = mouse->xy;
	if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
		return;
	if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){
		/* shuffle */
		x = c->r.min.x;
		rowclose(row, c, FALSE);
		if(rowadd(row, c, p.x) == nil)	/* whoops! */
		if(rowadd(row, c, x) == nil)		/* WHOOPS! */
		if(rowadd(row, c, -1)==nil){		/* shit! */
			rowclose(row, c, TRUE);
			return;
		}
		colmousebut(c);
		return;
	}
	d = row->col[i-1];
	if(p.x < d->r.min.x+80+Scrollwid)
		p.x = d->r.min.x+80+Scrollwid;
	if(p.x > c->r.max.x-80-Scrollwid)
		p.x = c->r.max.x-80-Scrollwid;
	r = d->r;
	r.max.x = c->r.max.x;
	draw(screen, r, display->white, nil, ZP);
	r.max.x = p.x;
	colresize(d, r);
	r = c->r;
	r.min.x = p.x;
	r.max.x = r.min.x;
	r.max.x += Border;
	draw(screen, r, display->black, nil, ZP);
	r.min.x = r.max.x;
	r.max.x = c->r.max.x;
	colresize(c, r);
	colmousebut(c);
}

void
rowclose(Row *row, Column *c, int dofree)
{
	Rectangle r;
	int i;

	for(i=0; i<row->ncol; i++)
		if(row->col[i] == c)
			goto Found;
	error("can't find column");
  Found:
	r = c->r;
	if(dofree)
		colcloseall(c);
	memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
	row->ncol--;
	row->col = realloc(row->col, row->ncol*sizeof(Column*));
	if(row->ncol == 0){
		draw(screen, r, display->white, nil, ZP);
		return;
	}
	if(i == row->ncol){		/* extend last column right */
		c = row->col[i-1];
		r.min.x = c->r.min.x;
		r.max.x = row->r.max.x;
	}else{			/* extend next window left */
		c = row->col[i];
		r.max.x = c->r.max.x;
	}
	draw(screen, r, display->white, nil, ZP);
	colresize(c, r);
}

Column*
rowwhichcol(Row *row, Point p)
{
	int i;
	Column *c;

	for(i=0; i<row->ncol; i++){
		c = row->col[i];
		if(ptinrect(p, c->r))
			return c;
	}
	return nil;
}

Text*
rowwhich(Row *row, Point p)
{
	Column *c;

	if(ptinrect(p, row->tag.all))
		return &row->tag;
	c = rowwhichcol(row, p);
	if(c)
		return colwhich(c, p);
	return nil;
}

Text*
rowtype(Row *row, Rune r, Point p)
{
	Window *w;
	Text *t;

	clearmouse();
	qlock(row);
	if(bartflag)
		t = barttext;
	else
		t = rowwhich(row, p);
	if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){
		w = t->w;
		if(w == nil)
			texttype(t, r);
		else{
			winlock(w, 'K');
			wintype(w, t, r);
			winunlock(w);
		}
	}
	qunlock(row);
	return t;
}

int
rowclean(Row *row)
{
	int clean;
	int i;

	clean = TRUE;
	for(i=0; i<row->ncol; i++)
		clean &= colclean(row->col[i]);
	return clean;
}

void
rowdump(Row *row, char *file)
{
	int i, j, fd, m, n, dumped;
	uint q0, q1;
	Biobuf *b;
	char *buf, *a, *fontname;
	Rune *r;
	Column *c;
	Window *w, *w1;
	Text *t;

	if(row->ncol == 0)
		return;
	buf = fbufalloc();
	if(file == nil){
		if(home == nil){
			warning(nil, "can't find file for dump: $home not defined\n");
			goto Rescue;
		}
		sprint(buf, "%s/acme.dump", home);
		file = buf;
	}
	fd = create(file, OWRITE, 0600);
	if(fd < 0){
		warning(nil, "can't open %s: %r\n", file);
		goto Rescue;
	}
	b = emalloc(sizeof(Biobuf));
	Binit(b, fd, OWRITE);
	r = fbufalloc();
	Bprint(b, "%s\n", wdir);
	Bprint(b, "%s\n", fontnames[0]);
	Bprint(b, "%s\n", fontnames[1]);
	for(i=0; i<row->ncol; i++){
		c = row->col[i];
		Bprint(b, "%11d", 100*(c->r.min.x-row->r.min.x)/Dx(row->r));
		if(i == row->ncol-1)
			Bputc(b, '\n');
		else
			Bputc(b, ' ');
	}
	for(i=0; i<row->ncol; i++){
		c = row->col[i];
		for(j=0; j<c->nw; j++)
			c->w[j]->body.file->dumpid = 0;
	}
	for(i=0; i<row->ncol; i++){
		c = row->col[i];
		for(j=0; j<c->nw; j++){
			w = c->w[j];
			wincommit(w, &w->tag);
			t = &w->body;
			/* windows owned by others get special treatment */
			if(w->nopen[QWevent] > 0)
				if(w->dumpstr == nil)
					continue;
			/* zeroxes of external windows are tossed */
			if(t->file->ntext > 1)
				for(n=0; n<t->file->ntext; n++){
					w1 = t->file->text[n]->w;
					if(w == w1)
						continue;
					if(w1->nopen[QWevent])
						goto Continue2;
				}
			fontname = "";
			if(t->reffont->f != font)
				fontname = t->reffont->f->name;
			if(t->file->nname)
				a = runetobyte(t->file->name, t->file->nname);
			else
				a = emalloc(1);
			if(t->file->dumpid){
				dumped = FALSE;
				Bprint(b, "x%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
					w->body.q0, w->body.q1,
					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
					fontname);
			}else if(w->dumpstr){
				dumped = FALSE;
				Bprint(b, "e%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
					0, 0,
					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
					fontname);
			}else if(strlen(a) == 0){	/* don't save unnamed windows */
				free(a);
				continue;
			}else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){
				dumped = FALSE;
				t->file->dumpid = w->id;
				Bprint(b, "f%11d %11d %11d %11d %11d %s\n", i, w->id,
					w->body.q0, w->body.q1,
					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
					fontname);
			}else{
				dumped = TRUE;
				t->file->dumpid = w->id;
				Bprint(b, "F%11d %11d %11d %11d %11d %11d %s\n", i, j,
					w->body.q0, w->body.q1,
					100*(w->r.min.y-c->r.min.y)/Dy(c->r),
					w->body.file->nc, fontname);
			}
			free(a);
			winctlprint(w, buf);
			Bwrite(b, buf, strlen(buf));
			m = min(RBUFSIZE, w->tag.file->nc);
			bufread(w->tag.file, 0, r, m);
			n = 0;
			while(n<m && r[n]!='\n')
				n++;
			r[n++] = '\n';
			Bprint(b, "%.*S", n, r);
			if(dumped){
				q0 = 0;
				q1 = t->file->nc;
				while(q0 < q1){
					n = q1 - q0;
					if(n > BUFSIZE/UTFmax)
						n = BUFSIZE/UTFmax;
					bufread(t->file, q0, r, n);
					Bprint(b, "%.*S", n, r);
					q0 += n;
				}
			}
			if(w->dumpstr){
				if(w->dumpdir)
					Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr);
				else
					Bprint(b, "\n%s\n", w->dumpstr);
			}
    Continue2:;
		}
	}
	Bterm(b);
	close(fd);
	free(b);
	fbuffree(r);

   Rescue:
	fbuffree(buf);
}

static
char*
rdline(Biobuf *b, int *linep)
{
	char *l;

	l = Brdline(b, '\n');
	if(l)
		(*linep)++;
	return l;
}

void
rowload(Row *row, char *file, int initing)
{
	int i, j, line, percent, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd;
	Biobuf *b, *bout;
	char *buf, *l, *t, *fontname;
	Rune *r, rune, *fontr;
	Column *c, *c1, *c2;
	uint q0, q1;
	Rectangle r1, r2;
	Window *w;

	buf = fbufalloc();
	if(file == nil){
		if(home == nil){
			warning(nil, "can't find file for load: $home not defined\n");
			goto Rescue1;
		}
		sprint(buf, "%s/acme.dump", home);
		file = buf;
	}
	b = Bopen(file, OREAD);
	if(b == nil){
		warning(nil, "can't open load file %s: %r\n", file);
		goto Rescue1;
	}
	/* current directory */
	line = 0;
	l = rdline(b, &line);
	if(l == nil)
		goto Rescue2;
	l[Blinelen(b)-1] = 0;
	if(chdir(l) < 0){
		warning(nil, "can't chdir %s\n", l);
		goto Rescue2;
	}
	/* global fonts */
	for(i=0; i<2; i++){
		l = rdline(b, &line);
		if(l == nil)
			goto Rescue2;
		l[Blinelen(b)-1] = 0;
		if(*l && strcmp(l, fontnames[i])!=0)
			rfget(i, TRUE, i==0 && initing, estrdup(l));
	}
	if(initing && row->ncol==0)
		rowinit(row, screen->clipr);
	l = rdline(b, &line);
	if(l == nil)
		goto Rescue2;
	j = Blinelen(b)/12;
	if(j<=0 || j>10)
		goto Rescue2;
	for(i=0; i<j; i++){
		percent = atoi(l+i*12);
		if(percent<0 || percent>=100)
			goto Rescue2;
		x = row->r.min.x+percent*Dx(row->r)/100;
		if(i < row->ncol){
			if(i == 0)
				continue;
			c1 = row->col[i-1];
			c2 = row->col[i];
			r1 = c1->r;
			r2 = c2->r;
			r1.max.x = x;
			r2.min.x = x+Border;
			if(Dx(r1) < 50 || Dx(r2) < 50)
				continue;
			draw(screen, Rpt(r1.min, r2.max), display->white, nil, ZP);
			colresize(c1, r1);
			colresize(c2, r2);
			r2.min.x = x;
			r2.max.x = x+Border;
			draw(screen, r2, display->black, nil, ZP);
		}
		if(i >= row->ncol)
			rowadd(row, nil, x);
	}
	for(;;){
		l = rdline(b, &line);
		if(l == nil)
			break;
		dumpid = 0;
		switch(l[0]){
		case 'e':
			if(Blinelen(b) < 1+5*12+1)
				goto Rescue2;
			l = rdline(b, &line);	/* ctl line; ignored */
			if(l == nil)
				goto Rescue2;
			l = rdline(b, &line);	/* directory */
			if(l == nil)
				goto Rescue2;
			l[Blinelen(b)-1] = 0;
			if(*l == '\0'){
				if(home == nil)
					r = bytetorune("./", &nr);
				else{
					t = emalloc(strlen(home)+1+1);
					sprint(t, "%s/", home);
					r = bytetorune(t, &nr);
					free(t);
				}
			}else
				r = bytetorune(l, &nr);
			l = rdline(b, &line);	/* command */
			if(l == nil)
				goto Rescue2;
			t = emalloc(Blinelen(b)+1);
			memmove(t, l, Blinelen(b));
			run(nil, t, r, nr, TRUE, nil, nil, FALSE);
			/* r is freed in run() */
			continue;
		case 'f':
			if(Blinelen(b) < 1+5*12+1)
				goto Rescue2;
			fontname = l+1+5*12;
			ndumped = -1;
			break;
		case 'F':
			if(Blinelen(b) < 1+6*12+1)
				goto Rescue2;
			fontname = l+1+6*12;
			ndumped = atoi(l+1+5*12+1);
			break;
		case 'x':
			if(Blinelen(b) < 1+5*12+1)
				goto Rescue2;
			fontname = l+1+5*12;
			ndumped = -1;
			dumpid = atoi(l+1+1*12);
			break;
		default:
			goto Rescue2;
		}
		l[Blinelen(b)-1] = 0;
		fontr = nil;
		nfontr = 0;
		if(*fontname)
			fontr = bytetorune(fontname, &nfontr);
		i = atoi(l+1+0*12);
		j = atoi(l+1+1*12);
		q0 = atoi(l+1+2*12);
		q1 = atoi(l+1+3*12);
		percent = atoi(l+1+4*12);
		if(i<0 || i>10)
			goto Rescue2;
		if(i > row->ncol)
			i = row->ncol;
		c = row->col[i];
		y = c->r.min.y+(percent*Dy(c->r))/100;
		if(y<c->r.min.y || y>=c->r.max.y)
			y = -1;
		if(dumpid == 0)
			w = coladd(c, nil, nil, y);
		else
			w = coladd(c, nil, lookid(dumpid, TRUE), y);
		if(w == nil)
			continue;
		w->dumpid = j;
		l = rdline(b, &line);
		if(l == nil)
			goto Rescue2;
		l[Blinelen(b)-1] = 0;
		r = bytetorune(l+5*12, &nr);
		ns = -1;
		for(n=0; n<nr; n++){
			if(r[n] == '/')
				ns = n;
			if(r[n] == ' ')
				break;
		}
		if(dumpid == 0)
			winsetname(w, r, n);
		for(; n<nr; n++)
			if(r[n] == '|')
				break;
		wincleartag(w);
		textinsert(&w->tag, w->tag.file->nc, r+n+1, nr-(n+1), TRUE);
		free(r);
		if(ndumped >= 0){
			/* simplest thing is to put it in a file and load that */
			sprint(buf, "/tmp/d%d.%.4sacme", getpid(), getuser());
			fd = create(buf, OWRITE|ORCLOSE, 0600);
			if(fd < 0){
				warning(nil, "can't create temp file: %r\n");
				goto Rescue2;
			}
			bout = emalloc(sizeof(Biobuf));
			Binit(bout, fd, OWRITE);
			for(n=0; n<ndumped; n++){
				rune = Bgetrune(b);
				if(rune == '\n')
					line++;
				if(rune == Beof){
					Bterm(bout);
					free(bout);
					close(fd);
					goto Rescue2;
				}
				Bputrune(bout, rune);
			}
			Bterm(bout);
			free(bout);
			textload(&w->body, 0, buf, 1);
			close(fd);
			w->body.file->mod = TRUE;
			for(n=0; n<w->body.file->ntext; n++)
				w->body.file->text[n]->w->dirty = TRUE;
			winsettag(w);
		}else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-')
			get(&w->body, nil, nil, FALSE, XXX, nil, 0);
		if(fontr){
			fontx(&w->body, nil, nil, 0, 0, fontr, nfontr);
			free(fontr);
		}
		if(q0>w->body.file->nc || q1>w->body.file->nc || q0>q1)
			q0 = q1 = 0;
		textshow(&w->body, q0, q1);
		w->maxlines = min(w->body.nlines, max(w->maxlines, w->body.maxlines));
	}
	Bterm(b);

Rescue1:
	fbuffree(buf);
	return;

Rescue2:
	warning(nil, "bad load file %s:%d\n", file, line);
	Bterm(b);
	goto Rescue1;
}

void
allwindows(void (*f)(Window*, void*), void *arg)
{
	int i, j;
	Column *c;

	for(i=0; i<row.ncol; i++){
		c = row.col[i];
		for(j=0; j<c->nw; j++)
			(*f)(c->w[j], arg);
	}
}

[-- Attachment #5: text.c --]
[-- Type: text/plain, Size: 24802 bytes --]

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <auth.h>
#include <fcall.h>
#include <plumb.h>
#include <regexp.h>
#include "dat.h"
#include "fns.h"

extern Reprog* ignoreregx;

Image	*tagcols[NCOL];
Image	*textcols[NCOL];

enum{
	TABDIR = 3	/* width of tabs in directory windows */
};

void
textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
{
	t->file = f;
	t->all = r;
	t->scrollr = r;
	t->scrollr.max.x = r.min.x+Scrollwid;
	t->lastsr = nullrect;
	r.min.x += Scrollwid+Scrollgap;
	t->eq0 = ~0;
	t->ncache = 0;
	t->reffont = rf;
	t->tabstop = maxtab;
	memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
	textredraw(t, r, rf->f, screen, -1);
}

void
textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
{
	int maxt;
	Rectangle rr;

	frinit(t, r, f, b, t->Frame.cols);
	rr = t->r;
	rr.min.x -= Scrollwid;	/* back fill to scroll bar */
	draw(t->b, rr, t->cols[BACK], nil, ZP);
	/* use no wider than 3-space tabs in a directory */
	maxt = maxtab;
	if(t->what == Body){
		if(t->w->isdir)
			maxt = min(TABDIR, maxtab);
		else
			maxt = t->tabstop;
	}
	t->maxtab = maxt*stringwidth(f, "0");
	if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
		if(t->maxlines > 0){
			textreset(t);
			textcolumnate(t, t->w->dlp,  t->w->ndl);
			textshow(t, 0, 0);
		}
	}else{
		textfill(t);
		textsetselect(t, t->q0, t->q1);
	}
}

int
textresize(Text *t, Rectangle r)
{
	int odx;

	if(Dy(r) > 0)
		r.max.y -= Dy(r)%t->font->height;
	else
		r.max.y = r.min.y;
	odx = Dx(t->all);
	t->all = r;
	t->scrollr = r;
	t->scrollr.max.x = r.min.x+Scrollwid;
	t->lastsr = nullrect;
	r.min.x += Scrollwid+Scrollgap;
	frclear(t, 0);
	textredraw(t, r, t->font, t->b, odx);
	return r.max.y;
}

void
textclose(Text *t)
{
	free(t->cache);
	frclear(t, 1);
	filedeltext(t->file, t);
	t->file = nil;
	rfclose(t->reffont);
	if(argtext == t)
		argtext = nil;
	if(typetext == t)
		typetext = nil;
	if(seltext == t)
		seltext = nil;
	if(mousetext == t)
		mousetext = nil;
	if(barttext == t)
		barttext = nil;
}

int
dircmp(void *a, void *b)
{
	Dirlist *da, *db;
	int i, n;

	da = *(Dirlist**)a;
	db = *(Dirlist**)b;
	n = min(da->nr, db->nr);
	i = memcmp(da->r, db->r, n*sizeof(Rune));
	if(i)
		return i;
	return da->nr - db->nr;
}

void
textcolumnate(Text *t, Dirlist **dlp, int ndl)
{
	int i, j, w, colw, mint, maxt, ncol, nrow;
	Dirlist *dl;
	uint q1;

	if(t->file->ntext > 1)
		return;
	mint = stringwidth(t->font, "0");
	/* go for narrower tabs if set more than 3 wide */
	t->maxtab = min(maxtab, TABDIR)*mint;
	maxt = t->maxtab;
	colw = 0;
	for(i=0; i<ndl; i++){
		dl = dlp[i];
		w = dl->wid;
		if(maxt-w%maxt < mint)
			w += mint;
		if(w % maxt)
			w += maxt-(w%maxt);
		if(w > colw)
			colw = w;
	}
	if(colw == 0)
		ncol = 1;
	else
		ncol = max(1, Dx(t->r)/colw);
	nrow = (ndl+ncol-1)/ncol;

	q1 = 0;
	for(i=0; i<nrow; i++){
		for(j=i; j<ndl; j+=nrow){
			dl = dlp[j];
			fileinsert(t->file, q1, dl->r, dl->nr);
			q1 += dl->nr;
			if(j+nrow >= ndl)
				break;
			w = dl->wid;
			if(maxt-w%maxt < mint){
				fileinsert(t->file, q1, L"\t", 1);
				q1++;
				w += mint;
			}
			do{
				fileinsert(t->file, q1, L"\t", 1);
				q1++;
				w += maxt-(w%maxt);
			}while(w < colw);
		}
		fileinsert(t->file, q1, L"\n", 1);
		q1++;
	}
}

uint
textload(Text *t, uint q0, char *file, int setqid)
{
	Rune *rp;
	Dirlist *dl, **dlp;
	int fd, i, n, ndl, nulls;
	uint q, q1;
	Dir d, *dbuf;
	char tmp[NAMELEN+1];
	Text *u;

	if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body || (t->w->isdir && t->file->nname==0))
		error("text.load");
	fd = open(file, OREAD);
	if(fd < 0){
		warning(nil, "can't open %s: %r\n", file);
		return 0;
	}
	if(dirfstat(fd, &d) < 0){
		warning(nil, "can't fstat %s: %r\n", file);
		goto Rescue;
	}
	nulls = FALSE;
	if(d.qid.path & CHDIR){
		/* this is checked in get() but it's possible the file changed underfoot */
		if(t->file->ntext > 1){
			warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
			goto Rescue;
		}
		t->w->isdir = TRUE;
		t->w->filemenu = FALSE;
		if(t->file->name[t->file->nname-1] != '/'){
			rp = runemalloc(t->file->nname+1);
			runemove(rp, t->file->name, t->file->nname);
			rp[t->file->nname] = '/';
			winsetname(t->w, rp, t->file->nname+1);
			free(rp);
		}
		dlp = nil;
		ndl = 0;
		dbuf = (Dir*)fbufalloc();
		while((n=dirread(fd, dbuf, BUFSIZE-(BUFSIZE)%sizeof(Dir))) > 0){
			n /= sizeof(Dir);
			for(i=0; i<n; i++){
				if (ignoreregx != nil)
					if (regexec(ignoreregx, dbuf[i].name, nil, 0))
						continue;
				dl = emalloc(sizeof(Dirlist));
				memmove(tmp, dbuf[i].name, NAMELEN);
				if(dbuf[i].mode & CHDIR)
					strcat(tmp, "/");
				dl->r = bytetorune(tmp, &dl->nr);
				dl->wid = stringwidth(t->font, tmp);
				ndl++;
				dlp = realloc(dlp, ndl*sizeof(Dirlist*));
				dlp[ndl-1] = dl;
			}
		}
		fbuffree(dbuf);
		qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
		t->w->dlp = dlp;
		t->w->ndl = ndl;
		textcolumnate(t, dlp, ndl);
		q1 = t->file->nc;
	}else{
		t->w->isdir = FALSE;
		t->w->filemenu = TRUE;
		q1 = q0 + fileload(t->file, q0, fd, &nulls);
	}
	if(setqid){
		t->file->dev = d.dev;
		t->file->mtime = d.mtime;
		t->file->qidpath = d.qid.path;
	}
	close(fd);
	rp = fbufalloc();
	for(q=q0; q<q1; q+=n){
		n = q1-q;
		if(n > RBUFSIZE)
			n = RBUFSIZE;
		bufread(t->file, q, rp, n);
		if(q < t->org)
			t->org += n;
		else if(q <= t->org+t->nchars)
			frinsert(t, rp, rp+n, q-t->org);
		if(t->lastlinefull)
			break;
	}
	fbuffree(rp);
	for(i=0; i<t->file->ntext; i++){
		u = t->file->text[i];
		if(u != t){
			if(u->org > u->file->nc)	/* will be 0 because of reset(), but safety first */
				u->org = 0;
			textresize(u, u->all);
			textbacknl(u, u->org, 0);	/* go to beginning of line */
		}
		textsetselect(u, q0, q0);
	}
	if(nulls)
		warning(nil, "%s: NUL bytes elided\n", file);
	return q1-q0;

    Rescue:
	close(fd);
	return 0;
}

uint
textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
{
	Rune *bp, *tp, *up;
	int i, initial;

	if(t->what == Tag){	/* can't happen but safety first: mustn't backspace over file name */
    Err:
		textinsert(t, q0, r, n, tofile);
		*nrp = n;
		return q0;
	}
	bp = r;
	for(i=0; i<n; i++)
		if(*bp++ == '\b'){
			--bp;
			initial = 0;
			tp = runemalloc(n);
			runemove(tp, r, i);
			up = tp+i;
			for(; i<n; i++){
				*up = *bp++;
				if(*up == '\b')
					if(up == tp)
						initial++;
					else
						--up;
				else
					up++;
			}
			if(initial){
				if(initial > q0)
					initial = q0;
				q0 -= initial;
				textdelete(t, q0, q0+initial, tofile);
			}
			n = up-tp;
			textinsert(t, q0, tp, n, tofile);
			free(tp);
			*nrp = n;
			return q0;
		}
	goto Err;
}

void
textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
{
	int c, i;
	Text *u;

	if(tofile && t->ncache != 0)
		error("text.insert");
	if(n == 0)
		return;
	if(tofile){
		fileinsert(t->file, q0, r, n);
		if(t->what == Body){
			t->w->dirty = TRUE;
			t->w->utflastqid = -1;
		}
		if(t->file->ntext > 1)
			for(i=0; i<t->file->ntext; i++){
				u = t->file->text[i];
				if(u != t){
					u->w->dirty = TRUE;	/* always a body */
					textinsert(u, q0, r, n, FALSE);
					textsetselect(u, u->q0, u->q1);
					textscrdraw(u);
				}
			}

	}
	if(q0 < t->q1)
		t->q1 += n;
	if(q0 < t->q0)
		t->q0 += n;
	if(q0 < t->org)
		t->org += n;
	else if(q0 <= t->org+t->nchars)
		frinsert(t, r, r+n, q0-t->org);
	if(t->w){
		c = 'i';
		if(t->what == Body)
			c = 'I';
		if(n <= EVENTSIZE)
			winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
		else
			winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
	}
}


void
textfill(Text *t)
{
	Rune *rp;
	int i, n, m, nl;

	if(t->lastlinefull || t->nofill)
		return;
	if(t->ncache > 0){
		if(t->w != nil)
			wincommit(t->w, t);
		else
			textcommit(t, TRUE);
	}
	rp = fbufalloc();
	do{
		n = t->file->nc-(t->org+t->nchars);
		if(n == 0)
			break;
		if(n > 2000)	/* educated guess at reasonable amount */
			n = 2000;
		bufread(t->file, t->org+t->nchars, rp, n);
		/*
		 * it's expensive to frinsert more than we need, so
		 * count newlines.
		 */
		nl = t->maxlines-t->nlines;
		m = 0;
		for(i=0; i<n; ){
			if(rp[i++] == '\n'){
				m++;
				if(m >= nl)
					break;
			}
		}
		frinsert(t, rp, rp+i, t->nchars);
	}while(t->lastlinefull == FALSE);
	fbuffree(rp);
}

void
textdelete(Text *t, uint q0, uint q1, int tofile)
{
	uint n, p0, p1;
	int i, c;
	Text *u;

	if(tofile && t->ncache != 0)
		error("text.delete");
	n = q1-q0;
	if(n == 0)
		return;
	if(tofile){
		filedelete(t->file, q0, q1);
		if(t->what == Body){
			t->w->dirty = TRUE;
			t->w->utflastqid = -1;
		}
		if(t->file->ntext > 1)
			for(i=0; i<t->file->ntext; i++){
				u = t->file->text[i];
				if(u != t){
					u->w->dirty = TRUE;	/* always a body */
					textdelete(u, q0, q1, FALSE);
					textsetselect(u, u->q0, u->q1);
					textscrdraw(u);
				}
			}
	}
	if(q0 < t->q0)
		t->q0 -= min(n, t->q0-q0);
	if(q0 < t->q1)
		t->q1 -= min(n, t->q1-q0);
	if(q1 <= t->org)
		t->org -= n;
	else if(q0 < t->org+t->nchars){
		p1 = q1 - t->org;
		if(p1 > t->nchars)
			p1 = t->nchars;
		if(q0 < t->org){
			t->org = q0;
			p0 = 0;
		}else
			p0 = q0 - t->org;
		frdelete(t, p0, p1);
		textfill(t);
	}
	if(t->w){
		c = 'd';
		if(t->what == Body)
			c = 'D';
		winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
	}
}

Rune
textreadc(Text *t, uint q)
{
	Rune r;

	if(t->cq0<=q && q<t->cq0+t->ncache)
		r = t->cache[q-t->cq0];
	else
		bufread(t->file, q, &r, 1);
	return r;
}

int
textbswidth(Text *t, Rune c)
{
	uint q, eq;
	Rune r;
	int skipping;

	/* there is known to be at least one character to erase */
	if(c == 0x08)	/* ^H: erase character */
		return 1;
	q = t->q0;
	skipping = TRUE;
	while(q > 0){
		r = textreadc(t, q-1);
		if(r == '\n'){		/* eat at most one more character */
			if(q == t->q0)	/* eat the newline */
				--q;
			break;
		}
		if(c == 0x17){
			eq = isalnum(r);
			if(eq && skipping)	/* found one; stop skipping */
				skipping = FALSE;
			else if(!eq && !skipping)
				break;
		}
		--q;
	}
	return t->q0-q;
}

void
texttype(Text *t, Rune r)
{
	uint q0, q1;
	int nnb, nb, n, i;
	Text *u;

	if(t->what!=Body && r=='\n')
		return;
	switch(r){
	case Kdown:
	case Kleft:
	case Kright:
		n = t->maxlines/2;
		q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
		textsetorigin(t, q0, FALSE);
		return;
	case Kup:
		n = t->maxlines/2;
		q0 = textbacknl(t, t->org, n);
		textsetorigin(t, q0, FALSE);
		return;
	}
	if(t->what == Body){
		seq++;
		filemark(t->file);
	}
	if(t->q1 > t->q0){
		if(t->ncache != 0)
			error("text.type");
		cut(t, t, nil, TRUE, TRUE, nil, 0);
		t->eq0 = ~0;
	}
	textshow(t, t->q0, t->q0);
	switch(r){
	case 0x1B:
		if(t->eq0 != ~0)
			textsetselect(t, t->eq0, t->q0);
		if(t->ncache > 0){
			if(t->w != nil)
				wincommit(t->w, t);
			else
				textcommit(t, TRUE);
		}
		return;
	case 0x08:	/* ^H: erase character */
	case 0x15:	/* ^U: erase line */
	case 0x17:	/* ^W: erase word */
		if(t->q0 == 0)	/* nothing to erase */
			return;
		nnb = textbswidth(t, r);
		q1 = t->q0;
		q0 = q1-nnb;
		/* if selection is at beginning of window, avoid deleting invisible text */
		if(q0 < t->org){
			q0 = t->org;
			nnb = q1-q0;
		}
		if(nnb <= 0)
			return;
		for(i=0; i<t->file->ntext; i++){
			u = t->file->text[i];
			u->nofill = TRUE;
			nb = nnb;
			n = u->ncache;
			if(n > 0){
				if(q1 != u->cq0+n)
					error("text.type backspace");
				if(n > nb)
					n = nb;
				u->ncache -= n;
				textdelete(u, q1-n, q1, FALSE);
				nb -= n;
			}
			if(u->eq0==q1 || u->eq0==~0)
				u->eq0 = q0;
			if(nb && u==t)
				textdelete(u, q0, q0+nb, TRUE);
			if(u != t)
				textsetselect(u, u->q0, u->q1);
			else
				textsetselect(t, q0, q0);
			u->nofill = FALSE;
		}
		for(i=0; i<t->file->ntext; i++)
			textfill(t->file->text[i]);
		return;
	}
	/* otherwise ordinary character; just insert, typically in caches of all texts */
	for(i=0; i<t->file->ntext; i++){
		u = t->file->text[i];
		if(u->eq0 == ~0)
			u->eq0 = t->q0;
		if(u->ncache == 0)
			u->cq0 = t->q0;
		else if(t->q0 != u->cq0+u->ncache)
			error("text.type cq1");
		textinsert(u, t->q0, &r, 1, FALSE);
		if(u != t)
			textsetselect(u, u->q0, u->q1);
		if(u->ncache == u->ncachealloc){
			u->ncachealloc += 10;
			u->cache = runerealloc(u->cache, u->ncachealloc);
		}
		u->cache[u->ncache++] = r;
	}
	textsetselect(t, t->q0+1, t->q0+1);
	if(r=='\n' && t->w!=nil)
		wincommit(t->w, t);
}

void
textcommit(Text *t, int tofile)
{
	if(t->ncache == 0)
		return;
	if(tofile)
		fileinsert(t->file, t->cq0, t->cache, t->ncache);
	if(t->what == Body){
		t->w->dirty = TRUE;
		t->w->utflastqid = -1;
	}
	t->ncache = 0;
}

static	Text	*clicktext;
static	uint	clickmsec;
static	Text	*selecttext;
static	uint	selectq;

/*
 * called from frame library
 */
void
framescroll(Frame *f, int dl)
{
	if(f != &selecttext->Frame)
		error("frameselect not right frame");
	textframescroll(selecttext, dl);
}

void
textframescroll(Text *t, int dl)
{
	uint q0;

	if(dl == 0){
		scrsleep(100);
		return;
	}
	if(dl < 0){
		q0 = textbacknl(t, t->org, -dl);
		if(selectq > t->org+t->p0)
			textsetselect(t, t->org+t->p0, selectq);
		else
			textsetselect(t, selectq, t->org+t->p0);
	}else{
		if(t->org+t->nchars == t->file->nc)
			return;
		q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
		if(selectq > t->org+t->p1)
			textsetselect(t, t->org+t->p1, selectq);
		else
			textsetselect(t, selectq, t->org+t->p1);
	}
	textsetorigin(t, q0, TRUE);
}


void
textselect(Text *t)
{
	uint q0, q1;
	int b, x, y;
	int state;

	selecttext = t;
	/*
	 * To have double-clicking and chording, we double-click
	 * immediately if it might make sense.
	 */
	b = mouse->buttons;
	q0 = t->q0;
	q1 = t->q1;
	selectq = t->org+frcharofpt(t, mouse->xy);
	if(clicktext==t && mouse->msec-clickmsec<500)
	if(q0==q1 && selectq==q0){
		textdoubleclick(t, &q0, &q1);
		textsetselect(t, q0, q1);
		flushimage(display, 1);
		x = mouse->xy.x;
		y = mouse->xy.y;
		/* stay here until something interesting happens */
		do
			readmouse(mousectl);
		while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
		mouse->xy.x = x;	/* in case we're calling frselect */
		mouse->xy.y = y;
		q0 = t->q0;	/* may have changed */
		q1 = t->q1;
		selectq = q0;
	}
	if(mouse->buttons == b){
		t->Frame.scroll = framescroll;
		frselect(t, mousectl);
		/* horrible botch: while asleep, may have lost selection altogether */
		if(selectq > t->file->nc)
			selectq = t->org + t->p0;
		t->Frame.scroll = nil;
		if(selectq < t->org)
			q0 = selectq;
		else
			q0 = t->org + t->p0;
		if(selectq > t->org+t->nchars)
			q1 = selectq;
		else
			q1 = t->org+t->p1;
	}
	if(q0 == q1){
		if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
			textdoubleclick(t, &q0, &q1);
			clicktext = nil;
		}else{
			clicktext = t;
			clickmsec = mouse->msec;
		}
	}else
		clicktext = nil;
	textsetselect(t, q0, q1);
	flushimage(display, 1);
	state = 0;	/* undo when possible; +1 for cut, -1 for paste */
	while(mouse->buttons){
		mouse->msec = 0;
		b = mouse->buttons;
		if(b & 6){
			if(state==0 && t->what==Body){
				seq++;
				filemark(t->w->body.file);
			}
			if(b & 2){
				if(state==-1 && t->what==Body){
					winundo(t->w, TRUE);
					textsetselect(t, q0, t->q0);
					state = 0;
				}else if(state != 1){
					cut(t, t, nil, TRUE, TRUE, nil, 0);
					state = 1;
				}
			}else{
				if(state==1 && t->what==Body){
					winundo(t->w, TRUE);
					textsetselect(t, q0, t->q1);
					state = 0;
				}else if(state != -1){
					paste(t, t, nil, TRUE, FALSE, nil, 0);
					state = -1;
				}
			}
			textscrdraw(t);
			clearmouse();
		}
		flushimage(display, 1);
		while(mouse->buttons == b)
			readmouse(mousectl);
		clicktext = nil;
	}
}

void
textshow(Text *t, uint q0, uint q1)
{
	int qe;
	int nl;
	uint q;

	if(t->what != Body)
		return;
	if(t->w!=nil && t->maxlines==0)
		colgrow(t->col, t->w, 1);
	textsetselect(t, q0, q1);
	qe = t->org+t->nchars;
	if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
		textscrdraw(t);
	else{
		if(t->w->nopen[QWevent] > 0)
			nl = 3*t->maxlines/4;
		else
			nl = t->maxlines/4;
		q = textbacknl(t, q0, nl);
		/* avoid going backwards if trying to go forwards - long lines! */
		if(!(q0>t->org && q<t->org))
			textsetorigin(t, q, TRUE);
		while(q0 > t->org+t->nchars)
			textsetorigin(t, t->org+1, FALSE);
	}
}

static
int
region(int a, int b)
{
	if(a < b)
		return -1;
	if(a == b)
		return 0;
	return 1;
}

void
selrestore(Frame *f, Point pt0, uint p0, uint p1)
{
	if(p1<=f->p0 || p0>=f->p1){
		/* no overlap */
		frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
		return;
	}
	if(p0>=f->p0 && p1<=f->p1){
		/* entirely inside */
		frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
		return;
	}

	/* they now are known to overlap */

	/* before selection */
	if(p0 < f->p0){
		frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
		p0 = f->p0;
		pt0 = frptofchar(f, p0);
	}
	/* after selection */
	if(p1 > f->p1){
		frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
		p1 = f->p1;
	}
	/* inside selection */
	frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
}

void
textsetselect(Text *t, uint q0, uint q1)
{
	int p0, p1;

	/* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
	t->q0 = q0;
	t->q1 = q1;
	/* compute desired p0,p1 from q0,q1 */
	p0 = q0-t->org;
	p1 = q1-t->org;
	if(p0 < 0)
		p0 = 0;
	if(p1 < 0)
		p1 = 0;
	if(p0 > t->nchars)
		p0 = t->nchars;
	if(p1 > t->nchars)
		p1 = t->nchars;
	if(p0==t->p0 && p1==t->p1)
		return;
	/* screen disagrees with desired selection */
	if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
		/* no overlap or too easy to bother trying */
		frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
		frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
		goto Return;
	}
	/* overlap; avoid unnecessary painting */
	if(p0 < t->p0){
		/* extend selection backwards */
		frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
	}else if(p0 > t->p0){
		/* trim first part of selection */
		frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
	}
	if(p1 > t->p1){
		/* extend selection forwards */
		frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
	}else if(p1 < t->p1){
		/* trim last part of selection */
		frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
	}

    Return:
	t->p0 = p0;
	t->p1 = p1;
}

uint;
xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)	/* when called, button is down */
{
	uint p0, p1, q, tmp;
	Point mp, pt0, pt1, qt;
	int reg, b;

	mp = mc->xy;
	b = mc->buttons;

	/* remove tick */
	if(f->p0 == f->p1)
		frtick(f, frptofchar(f, f->p0), 0);
	p0 = p1 = frcharofpt(f, mp);
	pt0 = frptofchar(f, p0);
	pt1 = frptofchar(f, p1);
	reg = 0;
	frtick(f, pt0, 1);
	do{
		q = frcharofpt(f, mc->xy);
		if(p1 != q){
			if(p0 == p1)
				frtick(f, pt0, 0);
			if(reg != region(q, p0)){	/* crossed starting point; reset */
				if(reg > 0)
					selrestore(f, pt0, p0, p1);
				else if(reg < 0)
					selrestore(f, pt1, p1, p0);
				p1 = p0;
				pt1 = pt0;
				reg = region(q, p0);
				if(reg == 0)
					frdrawsel0(f, pt0, p0, p1, col, display->white);
			}
			qt = frptofchar(f, q);
			if(reg > 0){
				if(q > p1)
					frdrawsel0(f, pt1, p1, q, col, display->white);

				else if(q < p1)
					selrestore(f, qt, q, p1);
			}else if(reg < 0){
				if(q > p1)
					selrestore(f, pt1, p1, q);
				else
					frdrawsel0(f, qt, q, p1, col, display->white);
			}
			p1 = q;
			pt1 = qt;
		}
		if(p0 == p1)
			frtick(f, pt0, 1);
		flushimage(f->display, 1);
		readmouse(mc);
	}while(mc->buttons == b);
	if(p1 < p0){
		tmp = p0;
		p0 = p1;
		p1 = tmp;
	}
	pt0 = frptofchar(f, p0);
	if(p0 == p1)
		frtick(f, pt0, 0);
	selrestore(f, pt0, p0, p1);
	/* restore tick */
	if(f->p0 == f->p1)
		frtick(f, frptofchar(f, f->p0), 1);
	flushimage(f->display, 1);
	*p1p = p1;
	return p0;
}

int
textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
{
	uint p0, p1;
	int buts;

	p0 = xselect(t, mousectl, high, &p1);
	buts = mousectl->buttons;
	if((buts & mask) == 0){
		*q0 = p0+t->org;
		*q1 = p1+t->org;
	}

	while(mousectl->buttons)
		readmouse(mousectl);
	return buts;
}

int
textselect2(Text *t, uint *q0, uint *q1, Text **tp)
{
	int buts;

	*tp = nil;
	buts = textselect23(t, q0, q1, but2col, 4);
	if(buts & 4)
		return 0;
	if(buts & 1){	/* pick up argument */
		*tp = argtext;
		return 1;
	}
	return 1;
}

int
textselect3(Text *t, uint *q0, uint *q1)
{
	int h;

	h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
	return h;
}

static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
static Rune left2[] =  { L'\n', 0 };
static Rune left3[] =  { L'\'', L'"', L'`', 0 };

static
Rune *left[] = {
	left1,
	left2,
	left3,
	nil
};
static
Rune *right[] = {
	right1,
	left2,
	left3,
	nil
};

void
textdoubleclick(Text *t, uint *q0, uint *q1)
{
	int c, i;
	Rune *r, *l, *p;
	uint q;

	for(i=0; left[i]!=nil; i++){
		q = *q0;
		l = left[i];
		r = right[i];
		/* try matching character to left, looking right */
		if(q == 0)
			c = '\n';
		else
			c = textreadc(t, q-1);
		p = runestrchr(l, c);
		if(p != nil){
			if(textclickmatch(t, c, r[p-l], 1, &q))
				*q1 = q-(c!='\n');
			return;
		}
		/* try matching character to right, looking left */
		if(q == t->file->nc)
			c = '\n';
		else
			c = textreadc(t, q);
		p = runestrchr(r, c);
		if(p != nil){
			if(textclickmatch(t, c, l[p-r], -1, &q)){
				*q1 = *q0+(*q0<t->file->nc && c=='\n');
				*q0 = q;
				if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
					(*q0)++;
			}
			return;
		}
	}
	/* try filling out word to right */
	while(*q1<t->file->nc && isalnum(textreadc(t, *q1)))
		(*q1)++;
	/* try filling out word to left */
	while(*q0>0 && isalnum(textreadc(t, *q0-1)))
		(*q0)--;
}

int
textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
{
	Rune c;
	int nest;

	nest = 1;
	for(;;){
		if(dir > 0){
			if(*q == t->file->nc)
				break;
			c = textreadc(t, *q);
			(*q)++;
		}else{
			if(*q == 0)
				break;
			(*q)--;
			c = textreadc(t, *q);
		}
		if(c == cr){
			if(--nest==0)
				return 1;
		}else if(c == cl)
			nest++;
	}
	return cl=='\n' && nest==1;
}

uint
textbacknl(Text *t, uint p, uint n)
{
	int i, j;

	/* look for start of this line if n==0 */
	if(n==0 && p>0 && textreadc(t, p-1)!='\n')
		n = 1;
	i = n;
	while(i-->0 && p>0){
		--p;	/* it's at a newline now; back over it */
		if(p == 0)
			break;
		/* at 128 chars, call it a line anyway */
		for(j=128; --j>0 && p>0; p--)
			if(textreadc(t, p-1)=='\n')
				break;
	}
	return p;
}

void
textsetorigin(Text *t, uint org, int exact)
{
	int i, a, fixup;
	Rune *r;
	uint n;

	if(org>0 && !exact){
		/* org is an estimate of the char posn; find a newline */
		/* don't try harder than 256 chars */
		for(i=0; i<256 && org<t->file->nc; i++){
			if(textreadc(t, org) == '\n'){
				org++;
				break;
			}
			org++;
		}
	}
	a = org-t->org;
	fixup = 0;
	if(a>=0 && a<t->nchars){
		frdelete(t, 0, a);
		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
	}
	else if(a<0 && -a<t->nchars){
		n = t->org - org;
		r = runemalloc(n);
		bufread(t->file, org, r, n);
		frinsert(t, r, r+n, 0);
		free(r);
	}else
		frdelete(t, 0, t->nchars);
	t->org = org;
	textfill(t);
	textscrdraw(t);
	textsetselect(t, t->q0, t->q1);
	if(fixup && t->p1 > t->p0)
		frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
}

void
textreset(Text *t)
{
	t->file->seq = 0;
	t->eq0 = ~0;
	/* do t->delete(0, t->nc, TRUE) without building backup stuff */
	textsetselect(t, t->org, t->org);
	frdelete(t, 0, t->nchars);
	t->org = 0;
	t->q0 = 0;
	t->q1 = 0;
	filereset(t->file);
	bufreset(t->file);
}

             reply	other threads:[~2001-11-05  8:07 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2001-11-05  8:07 Fco.J.Ballesteros [this message]
2001-11-08  4:51 rob pike
2001-11-08  7:49 Fco.J.Ballesteros

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20011105080740.64503199B5@mail.cse.psu.edu \
    --to=nemo@plan9.escet.urjc.es \
    --cc=9fans@cse.psu.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).