9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
* Re: [9fans] acme: hiding certain files in dir windows
@ 2001-11-08  4:51 rob pike
  0 siblings, 0 replies; 3+ messages in thread
From: rob pike @ 2001-11-08  4:51 UTC (permalink / raw)
  To: 9fans

Don't you want the regexp to be inherited when you descend
the hierarchy?

> By the way, why doesn't acme use regexp(2)?

Regexp(2) uses longjmps, which are a bad idea in a threaded
world.  The original version of acme was in Alef, which doesn't
even have longjmp, so its regexp library used a threading
trick to manage errors.  It's actually a pretty trick that I was
happy to keep in the C version of acme; in fact, I used it in
several places.

-rob



^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [9fans] acme: hiding certain files in dir windows
@ 2001-11-08  7:49 Fco.J.Ballesteros
  0 siblings, 0 replies; 3+ messages in thread
From: Fco.J.Ballesteros @ 2001-11-08  7:49 UTC (permalink / raw)
  To: 9fans

The ignored regexp is global, so I don't need to inherit
it while desdencing the hierarchy.
Of course it would be more powerful to be able to ingore different
patterns on different windows but did not feel the need.

Another comment I have is that perhaps it would be more useful to
have a single `-e' (execute) option for acme instead of the -l and the
-i I have added in my local version for Ignore.

Instead of saying   -l mydump -i myregexp
we could just say   -e 'Load mydump' -e 'Ignore myregxp',
which would let us execute other various things upon startup.
For example; I'd love this one: acme -e 'acd /dev/sdC1'

I get some spare time for this I may try the experiment; just wanted
to hear comments about it.


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [9fans] acme: hiding certain files in dir windows
@ 2001-11-05  8:07 Fco.J.Ballesteros
  0 siblings, 0 replies; 3+ messages in thread
From: Fco.J.Ballesteros @ 2001-11-05  8:07 UTC (permalink / raw)
  To: 9fans

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

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2001-11-08  7:49 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2001-11-08  4:51 [9fans] acme: hiding certain files in dir windows rob pike
  -- strict thread matches above, loose matches on Subject: below --
2001-11-08  7:49 Fco.J.Ballesteros
2001-11-05  8:07 Fco.J.Ballesteros

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).