Anonymous avatar Anonymous committed 0df6bbc

first commit

Comments (0)

Files changed (5)

+enum
+{
+	Qdir,
+	Qacme,
+	Qcons,
+	Qconsctl,
+	Qdraw,
+	Qeditout,
+	Qindex,
+	Qlabel,
+	Qnew,
+
+	QWaddr,
+	QWbody,
+	QWctl,
+	QWdata,
+	QWeditout,
+	QWerrors,
+	QWevent,
+	QWrdsel,
+	QWwrsel,
+	QWtag,
+	QWxdata,
+	QMAX
+};
+
+enum
+{
+	Blockincr =	256,
+	Maxblock = 	8*1024,
+	NRange =		10,
+	Infinity = 		0x7FFFFFFF	/* huge value for regexp address */
+};
+
+#define Buffer AcmeBuffer
+typedef	struct	Block Block;
+typedef	struct	Buffer Buffer;
+typedef	struct	Command Command;
+typedef	struct	Column Column;
+typedef	struct	Dirlist Dirlist;
+typedef	struct	Dirtab Dirtab;
+typedef	struct	Disk Disk;
+typedef	struct	Expand Expand;
+typedef	struct	Fid Fid;
+typedef	struct	File File;
+typedef	struct	Elog Elog;
+typedef	struct	Mntdir Mntdir;
+typedef	struct	Range Range;
+typedef	struct	Rangeset Rangeset;
+typedef	struct	Reffont Reffont;
+typedef	struct	Row Row;
+typedef	struct	Runestr Runestr;
+typedef	struct	Text Text;
+typedef	struct	Timer Timer;
+typedef	struct	Window Window;
+typedef	struct	Xfid Xfid;
+
+struct Runestr
+{
+	Rune	*r;
+	int	nr;
+};
+
+struct Range
+{
+	int	q0;
+	int	q1;
+};
+
+struct Block
+{
+	uint		addr;	/* disk address in bytes */
+	union
+	{
+		uint	n;		/* number of used runes in block */
+		Block	*next;	/* pointer to next in free list */
+	} u;
+};
+
+struct Disk
+{
+	int		fd;
+	uint		addr;	/* length of temp file */
+	Block	*free[Maxblock/Blockincr+1];
+};
+
+Disk*	diskinit(void);
+Block*	disknewblock(Disk*, uint);
+void		diskrelease(Disk*, Block*);
+void		diskread(Disk*, Block*, Rune*, uint);
+void		diskwrite(Disk*, Block**, Rune*, uint);
+
+struct Buffer
+{
+	uint	nc;
+	Rune	*c;			/* cache */
+	uint	cnc;			/* bytes in cache */
+	uint	cmax;		/* size of allocated cache */
+	uint	cq;			/* position of cache */
+	int		cdirty;	/* cache needs to be written */
+	uint	cbi;			/* index of cache Block */
+	Block	**bl;		/* array of blocks */
+	uint	nbl;			/* number of blocks */
+};
+void		bufinsert(Buffer*, uint, Rune*, uint);
+void		bufdelete(Buffer*, uint, uint);
+uint		bufload(Buffer*, uint, int, int*);
+void		bufread(Buffer*, uint, Rune*, uint);
+void		bufclose(Buffer*);
+void		bufreset(Buffer*);
+
+struct Elog
+{
+	short	type;		/* Delete, Insert, Filename */
+	uint		q0;		/* location of change (unused in f) */
+	uint		nd;		/* number of deleted characters */
+	uint		nr;		/* # runes in string or file name */
+	Rune		*r;
+};
+void	elogterm(File*);
+void	elogclose(File*);
+void	eloginsert(File*, int, Rune*, int);
+void	elogdelete(File*, int, int);
+void	elogreplace(File*, int, int, Rune*, int);
+void	elogapply(File*);
+
+struct File
+{
+	Buffer	b;			/* the data */
+	Buffer	delta;	/* transcript of changes */
+	Buffer	epsilon;	/* inversion of delta for redo */
+	Buffer	*elogbuf;	/* log of pending editor changes */
+	Elog		elog;		/* current pending change */
+	Rune		*name;	/* name of associated file */
+	int		nname;	/* size of name */
+	uvlong	qidpath;	/* of file when read */
+	ulong		mtime;	/* of file when read */
+	int		dev;		/* of file when read */
+	int		unread;	/* file has not been read from disk */
+	int		editclean;	/* mark clean after edit command */
+
+	int		seq;		/* if seq==0, File acts like Buffer */
+	int		mod;
+	Text		*curtext;	/* most recently used associated text */
+	Text		**text;	/* list of associated texts */
+	int		ntext;
+	int		dumpid;	/* used in dumping zeroxed windows */
+};
+File*		fileaddtext(File*, Text*);
+void		fileclose(File*);
+void		filedelete(File*, uint, uint);
+void		filedeltext(File*, Text*);
+void		fileinsert(File*, uint, Rune*, uint);
+uint		fileload(File*, uint, int, int*);
+void		filemark(File*);
+void		filereset(File*);
+void		filesetname(File*, Rune*, int);
+void		fileundelete(File*, Buffer*, uint, uint);
+void		fileuninsert(File*, Buffer*, uint, uint);
+void		fileunsetname(File*, Buffer*);
+void		fileundo(File*, int, uint*, uint*);
+uint		fileredoseq(File*);
+
+enum	/* Text.what */
+{
+	Columntag,
+	Rowtag,
+	Tag,
+	Body
+};
+
+struct Text
+{
+	File		*file;
+	Frame	fr;
+	Reffont	*reffont;
+	uint	org;
+	uint	q0;
+	uint	q1;
+	int	what;
+	int	tabstop;
+	Window	*w;
+	Rectangle scrollr;
+	Rectangle lastsr;
+	Rectangle all;
+	Row		*row;
+	Column	*col;
+
+	uint	eq0;	/* start of typing for ESC */
+	uint	cq0;	/* cache position */
+	int		ncache;	/* storage for insert */
+	int		ncachealloc;
+	Rune	*cache;
+	int	nofill;
+	int	needundo;
+};
+
+uint		textbacknl(Text*, uint, uint);
+uint		textbsinsert(Text*, uint, Rune*, uint, int, int*);
+int		textbswidth(Text*, Rune);
+int		textclickhtmlmatch(Text*, uint*, uint*);
+int		textclickmatch(Text*, int, int, int, uint*);
+void		textclose(Text*);
+void		textcolumnate(Text*, Dirlist**, int);
+void		textcommit(Text*, int);
+void		textconstrain(Text*, uint, uint, uint*, uint*);
+void		textdelete(Text*, uint, uint, int);
+void		textdoubleclick(Text*, uint*, uint*);
+void		textfill(Text*);
+void		textframescroll(Text*, int);
+void		textinit(Text*, File*, Rectangle, Reffont*, Image**);
+void		textinsert(Text*, uint, Rune*, uint, int);
+int		textload(Text*, uint, char*, int);
+Rune		textreadc(Text*, uint);
+void		textredraw(Text*, Rectangle, Font*, Image*, int);
+void		textreset(Text*);
+int		textresize(Text*, Rectangle, int);
+void		textscrdraw(Text*);
+void		textscroll(Text*, int);
+void		textselect(Text*);
+int		textselect2(Text*, uint*, uint*, Text**);
+int		textselect23(Text*, uint*, uint*, Image*, int);
+int		textselect3(Text*, uint*, uint*);
+void		textsetorigin(Text*, uint, int);
+void		textsetselect(Text*, uint, uint);
+void		textshow(Text*, uint, uint, int);
+void		texttype(Text*, Rune);
+
+struct Window
+{
+	QLock	lk;
+	Ref	ref;
+	Text		tag;
+	Text		body;
+	Rectangle	r;
+	uchar	isdir;
+	uchar	isscratch;
+	uchar	filemenu;
+	uchar	dirty;
+	uchar	autoindent;
+	int		id;
+	Range	addr;
+	Range	limit;
+	uchar	nopen[QMAX];
+	uchar	nomark;
+	Range	wrselrange;
+	int		rdselfd;
+	Column	*col;
+	Xfid		*eventx;
+	char		*events;
+	int		nevents;
+	int		owner;
+	int		maxlines;
+	Dirlist	**dlp;
+	int		ndl;
+	int		putseq;
+	int		nincl;
+	Rune		**incl;
+	Reffont	*reffont;
+	QLock	ctllock;
+	uint		ctlfid;
+	char		*dumpstr;
+	char		*dumpdir;
+	int		dumpid;
+	int		utflastqid;
+	int		utflastboff;
+	int		utflastq;
+	int		tagsafe;		/* taglines is correct */
+	int		tagexpand;
+	int		taglines;
+	Rectangle	tagtop;
+	QLock	editoutlk;
+	int		backwards;
+};
+
+void	wininit(Window*, Window*, Rectangle);
+void	winlock(Window*, int);
+void	winlock1(Window*, int);
+void	winunlock(Window*);
+void	wintype(Window*, Text*, Rune);
+void	winundo(Window*, int);
+void	winsetname(Window*, Rune*, int);
+void	winsettag(Window*);
+void	winsettag1(Window*);
+void	wincommit(Window*, Text*);
+int	winresize(Window*, Rectangle, int, int);
+void	winclose(Window*);
+void	windelete(Window*);
+int	winclean(Window*, int);
+void	windirfree(Window*);
+void	winevent(Window*, char*, ...);
+void	winmousebut(Window*);
+void	winaddincl(Window*, Rune*, int);
+void	wincleartag(Window*);
+char	*winctlprint(Window*, char*, int);
+
+struct Column
+{
+	Rectangle r;
+	Text	tag;
+	Row		*row;
+	Window	**w;
+	int		nw;
+	int		safe;
+};
+
+void		colinit(Column*, Rectangle);
+Window*	coladd(Column*, Window*, Window*, int);
+void		colclose(Column*, Window*, int);
+void		colcloseall(Column*);
+void		colresize(Column*, Rectangle);
+Text*	colwhich(Column*, Point);
+void		coldragwin(Column*, Window*, int);
+void		colgrow(Column*, Window*, int);
+int		colclean(Column*);
+void		colsort(Column*);
+void		colmousebut(Column*);
+
+struct Row
+{
+	QLock	lk;
+	Rectangle r;
+	Text	tag;
+	Column	**col;
+	int		ncol;
+
+};
+
+void		rowinit(Row*, Rectangle);
+Column*	rowadd(Row*, Column *c, int);
+void		rowclose(Row*, Column*, int);
+Text*	rowwhich(Row*, Point);
+Column*	rowwhichcol(Row*, Point);
+void		rowresize(Row*, Rectangle);
+Text*	rowtype(Row*, Rune, Point);
+void		rowdragcol(Row*, Column*, int but);
+int		rowclean(Row*);
+void		rowdump(Row*, char*);
+int		rowload(Row*, char*, int);
+void		rowloadfonts(char*);
+
+struct Timer
+{
+	int		dt;
+	int		cancel;
+	Channel	*c;	/* chan(int) */
+	Timer	*next;
+};
+
+struct Command
+{
+	int		pid;
+	Rune		*name;
+	int		nname;
+	char		*text;
+	char		**av;
+	int		iseditcmd;
+	Mntdir	*md;
+	Command	*next;
+};
+
+struct Dirtab
+{
+	char	*name;
+	uchar	type;
+	uint	qid;
+	uint	perm;
+};
+
+struct Mntdir
+{
+	int		id;
+	int		ref;
+	Rune		*dir;
+	int		ndir;
+	Mntdir	*next;
+	int		nincl;
+	Rune		**incl;
+};
+
+struct Fid
+{
+	int		fid;
+	int		busy;
+	int		open;
+	Qid		qid;
+	Window	*w;
+	Dirtab	*dir;
+	Fid		*next;
+	Mntdir	*mntdir;
+	int		nrpart;
+	uchar	rpart[UTFmax];
+};
+
+
+struct Xfid
+{
+	void		*arg;	/* args to xfidinit */
+	Fcall	fcall;
+	Xfid	*next;
+	Channel	*c;		/* chan(void(*)(Xfid*)) */
+	Fid	*f;
+	uchar	*buf;
+	int	flushed;
+
+};
+
+void		xfidctl(void *);
+void		xfidflush(Xfid*);
+void		xfidopen(Xfid*);
+void		xfidclose(Xfid*);
+void		xfidread(Xfid*);
+void		xfidwrite(Xfid*);
+void		xfidctlwrite(Xfid*, Window*);
+void		xfideventread(Xfid*, Window*);
+void		xfideventwrite(Xfid*, Window*);
+void		xfidindexread(Xfid*);
+void		xfidutfread(Xfid*, Text*, uint, int);
+int		xfidruneread(Xfid*, Text*, uint, uint);
+
+struct Reffont
+{
+	Ref	ref;
+	Font	*f;
+
+};
+Reffont	*rfget(int, int, int, char*);
+void		rfclose(Reffont*);
+
+struct Rangeset
+{
+	Range	r[NRange];
+};
+
+struct Dirlist
+{
+	Rune	*r;
+	int		nr;
+	int		wid;
+};
+
+struct Expand
+{
+	uint	q0;
+	uint	q1;
+	Rune	*name;
+	int	nname;
+	char	*bname;
+	int	jump;
+	union{
+		Text	*at;
+		Rune	*ar;
+	} u;
+	int	(*agetc)(void*, uint);
+	int	a0;
+	int	a1;
+};
+
+enum
+{
+	/* fbufalloc() guarantees room off end of BUFSIZE */
+	BUFSIZE = Maxblock+IOHDRSZ,	/* size from fbufalloc() */
+	RBUFSIZE = BUFSIZE/sizeof(Rune),
+	EVENTSIZE = 256,
+	Scrollwid = 12,	/* width of scroll bar */
+	Scrollgap = 4,	/* gap right of scroll bar */
+	Margin = 4,	/* margin around text */
+	Border = 2	/* line between rows, cols, windows */
+};
+
+#define	QID(w,q)	((w<<8)|(q))
+#define	WIN(q)	((((ulong)(q).path)>>8) & 0xFFFFFF)
+#define	FILE(q)	((q).path & 0xFF)
+
+#undef FALSE
+#undef TRUE
+
+enum
+{
+	FALSE,
+	TRUE,
+	XXX
+};
+
+enum
+{
+	Empty	= 0,
+	Null		= '-',
+	Delete	= 'd',
+	Insert	= 'i',
+	Replace	= 'r',
+	Filename	= 'f'
+};
+
+enum	/* editing */
+{
+	Inactive	= 0,
+	Inserting,
+	Collecting
+};
+
+uint		globalincref;
+uint		seq;
+uint		maxtab;	/* size of a tab, in units of the '0' character */
+
+Display		*display;
+Image		*screen;
+Font			*font;
+Mouse		*mouse;
+Mousectl		*mousectl;
+Keyboardctl	*keyboardctl;
+Reffont		reffont;
+Image		*modbutton;
+Image		*colbutton;
+Image		*button;
+Image		*but2col;
+Image		*but3col;
+Cursor		boxcursor;
+Row			row;
+int			timerpid;
+Disk			*disk;
+Text			*seltext;
+Text			*argtext;
+Text			*mousetext;	/* global because Text.close needs to clear it */
+Text			*typetext;		/* global because Text.close needs to clear it */
+Text			*barttext;		/* shared between mousetask and keyboardthread */
+int			bartflag;
+int			swapscrollbuttons;
+Window		*activewin;
+Column		*activecol;
+Buffer		snarfbuf;
+Rectangle		nullrect;
+int			fsyspid;
+char			*cputype;
+char			*objtype;
+char			*home;
+char			*fontnames[2];
+Image		*tagcols[NCOL];
+Image		*textcols[NCOL];
+extern char		wdir[]; /* must use extern because no dimension given */
+int			editing;
+int			erroutfd;
+int			messagesize;		/* negotiated in 9P version setup */
+int			globalautoindent;
+int			dodollarsigns;
+char*		mtpt;
+
+enum
+{
+	Kscrolloneup		= KF|0x20,
+	Kscrollonedown	= KF|0x21
+};
+
+Channel	*cplumb;		/* chan(Plumbmsg*) */
+Channel	*cwait;		/* chan(Waitmsg) */
+Channel	*ccommand;	/* chan(Command*) */
+Channel	*ckill;		/* chan(Rune*) */
+Channel	*cxfidalloc;	/* chan(Xfid*) */
+Channel	*cxfidfree;	/* chan(Xfid*) */
+Channel	*cnewwindow;	/* chan(Channel*) */
+Channel	*mouseexit0;	/* chan(int) */
+Channel	*mouseexit1;	/* chan(int) */
+Channel	*cexit;		/* chan(int) */
+Channel	*cerr;		/* chan(char*) */
+Channel	*cedit;		/* chan(int) */
+Channel	*cwarn;		/* chan(void*)[1] (really chan(unit)[1]) */
+
+QLock	editoutlk;
+
+#define	STACK	65536
+#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 <fcall.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include "dat.h"
+#include "fns.h"
+
+Buffer	snarfbuf;
+
+/*
+ * These functions get called as:
+ *
+ *	fn(et, t, argt, flag1, flag1, flag2, s, n);
+ *
+ * Where the arguments are:
+ *
+ *	et: the Text* in which the executing event (click) occurred
+ *	t: the Text* containing the current selection (Edit, Cut, Snarf, Paste)
+ *	argt: the Text* containing the argument for a 2-1 click.
+ *	e->flag1: from Exectab entry
+ * 	e->flag2: from Exectab entry
+ *	s: the command line remainder (e.g., "x" if executing "Dump x")
+ *	n: length of s  (s is *not* NUL-terminated)
+ */
+
+void doabort(Text*, Text*, Text*, int, int, Rune*, int);
+void	del(Text*, Text*, Text*, int, int, Rune*, int);
+void	delcol(Text*, Text*, Text*, int, int, Rune*, int);
+void	dotfiles(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	xexit(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	incl(Text*, Text*, Text*, int, int, Rune*, int);
+void	indent(Text*, Text*, Text*, int, int, Rune*, int);
+void	xkill(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	rev(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);
+
+typedef struct Exectab Exectab;
+struct Exectab
+{
+	Rune	*name;
+	void	(*fn)(Text*, Text*, Text*, int, int, Rune*, int);
+	int		mark;
+	int		flag1;
+	int		flag2;
+};
+
+static Rune LAbort[] = { 'A', 'b', 'o', 'r', 't', 0 };
+static Rune LCut[] = { 'C', 'u', 't', 0 };
+static Rune LDel[] = { 'D', 'e', 'l', 0 };
+static Rune LDelcol[] = { 'D', 'e', 'l', 'c', 'o', 'l', 0 };
+static Rune LDelete[] = { 'D', 'e', 'l', 'e', 't', 'e', 0 };
+static Rune LDump[] = { 'D', 'u', 'm', 'p', 0 };
+static Rune LEdit[] = { 'E', 'd', 'i', 't', 0 };
+static Rune LExit[] = { 'E', 'x', 'i', 't', 0 };
+static Rune LFont[] = { 'F', 'o', 'n', 't', 0 };
+static Rune LGet[] = { 'G', 'e', 't', 0 };
+static Rune LID[] = { 'I', 'D', 0 };
+static Rune LIncl[] = { 'I', 'n', 'c', 'l', 0 };
+static Rune LIndent[] = { 'I', 'n', 'd', 'e', 'n', 't', 0 };
+static Rune LKill[] = { 'K', 'i', 'l', 'l', 0 };
+static Rune LLoad[] = { 'L', 'o', 'a', 'd', 0 };
+static Rune LLocal[] = { 'L', 'o', 'c', 'a', 'l', 0 };
+static Rune LLook[] = { 'L', 'o', 'o', 'k', 0 };
+static Rune LNew[] = { 'N', 'e', 'w', 0 };
+static Rune LNewcol[] = { 'N', 'e', 'w', 'c', 'o', 'l', 0 };
+static Rune LPaste[] = { 'P', 'a', 's', 't', 'e', 0 };
+static Rune LPut[] = { 'P', 'u', 't', 0 };
+static Rune LPutall[] = { 'P', 'u', 't', 'a', 'l', 'l', 0 };
+static Rune LRedo[] = { 'R', 'e', 'd', 'o', 0 };
+static Rune LRev[] = { 'R', 'e', 'v', 0 };
+static Rune LSend[] = { 'S', 'e', 'n', 'd', 0 };
+static Rune LSnarf[] = { 'S', 'n', 'a', 'r', 'f', 0 };
+static Rune LSort[] = { 'S', 'o', 'r', 't', 0 };
+static Rune LTab[] = { 'T', 'a', 'b', 0 };
+static Rune LUndo[] = { 'U', 'n', 'd', 'o', 0 };
+static Rune LZerox[] = { 'Z', 'e', 'r', 'o', 'x', 0 };
+
+Exectab exectab[] = {
+	{ LAbort,		doabort,	FALSE,	XXX,		XXX,		},
+	{ LCut,		cut,		TRUE,	TRUE,	TRUE	},
+	{ LDel,		del,		FALSE,	FALSE,	XXX		},
+	{ LDelcol,		delcol,	FALSE,	XXX,		XXX		},
+	{ LDelete,		del,		FALSE,	TRUE,	XXX		},
+	{ LDump,		dump,	FALSE,	TRUE,	XXX		},
+	{ LEdit,		edit,		FALSE,	XXX,		XXX		},
+	{ LExit,		xexit,	FALSE,	XXX,		XXX		},
+	{ LFont,		fontx,	FALSE,	XXX,		XXX		},
+	{ LGet,		get,		FALSE,	TRUE,	XXX		},
+	{ LID,		id,		FALSE,	XXX,		XXX		},
+	{ LIncl,		incl,		FALSE,	XXX,		XXX		},
+	{ LIndent,		indent,	FALSE,	XXX,		XXX		},
+	{ LKill,		xkill,		FALSE,	XXX,		XXX		},
+	{ LLoad,		dump,	FALSE,	FALSE,	XXX		},
+	{ LLocal,		local,	FALSE,	XXX,		XXX		},
+	{ LLook,		look,		FALSE,	XXX,		XXX		},
+	{ LNew,		new,		FALSE,	XXX,		XXX		},
+	{ LNewcol,	newcol,	FALSE,	XXX,		XXX		},
+	{ LPaste,		paste,	TRUE,	TRUE,	XXX		},
+	{ LPut,		put,		FALSE,	XXX,		XXX		},
+	{ LPutall,		putall,	FALSE,	XXX,		XXX		},
+	{ LRedo,		undo,	FALSE,	FALSE,	XXX		},
+	{ LRev,		rev,	FALSE,	FALSE,	XXX		},
+	{ LSend,		sendx,	TRUE,	XXX,		XXX		},
+	{ LSnarf,		cut,		FALSE,	TRUE,	FALSE	},
+	{ LSort,		sort,		FALSE,	XXX,		XXX		},
+	{ LTab,		tab,		FALSE,	XXX,		XXX		},
+	{ LUndo,		undo,	FALSE,	TRUE,	XXX		},
+	{ LZerox,		zeroxx,	FALSE,	XXX,		XXX		},
+	{ nil, 			0,		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->b.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->b, 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->b, 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->b, 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->ref);
+	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->b, 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
+doabort(Text *__0, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	static int n;
+
+	USED(__0);
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	if(n++ == 0)
+		warning(nil, "executing Abort again will call abort()\n");
+	else
+		abort();
+}
+
+void
+newcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	Column *c;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	c = rowadd(et->row, nil, -1);
+	if(c)
+		winsettag(coladd(c, nil, nil, -1));
+}
+
+void
+delcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	int i;
+	Column *c;
+	Window *w;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	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]+w->nopen[QWxdata] > 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 *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
+{
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+
+	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
+rev(Text *et, Text *_1, Text *_2, int _3, int _4, Rune *_5, int _6)
+{
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_4);
+	USED(_6);
+
+	if (et->w->backwards) 
+		et->w->backwards = 0;
+	else 
+		et->w->backwards = 1;
+}
+
+void
+sort(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	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 *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
+{
+	int i, j;
+	Column *c;
+	Window *w;
+	uint seq;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+
+	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(dir.nr==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);
+			if(dir.nr>0 && r[dir.nr]!='/' && n>0 && arg[0]!='/')
+				r[dir.nr++] = '/';
+			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 *_1, int _2, int _3, Rune *_4, int _5)
+{
+	Window *nw;
+	int c, locked;
+
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	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 _0, Rune *arg, int narg)
+{
+	char *name;
+	Rune *r;
+	int i, n, dirty, samename, isdir;
+	Window *w;
+	Text *u;
+	Dir *d;
+
+	USED(_0);
+
+	if(flag1)
+		if(et==nil || et->w==nil)
+			return;
+	if(!et->w->isdir && (et->w->body.file->b.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){
+		d = dirstat(name);
+		isdir = (d!=nil && (d->qid.type & QTDIR));
+		free(d);
+		if(isdir){
+			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->b.nc, u->w->tag.file->b.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, *d1;
+	Window *w;
+	int isapp;
+
+	w = f->curtext->w;
+	name = runetobyte(namer, nname);
+	d = dirstat(name);
+	if(d!=nil && runeeq(namer, nname, f->name, f->nname)){
+		/* f->mtime+1 because when talking over NFS it's often off by a second */
+		if(f->dev!=d->dev || f->qidpath!=d->qid.path || abs(f->mtime-d->mtime) > 1){
+			if(f->unread)
+				warning(nil, "%s not written; file already exists\n", name);
+			else
+				warning(nil, "%s modified%s%s since last read\n\twas %t; now %t\n", name, d->muid[0]?" by ":"", d->muid, f->mtime, d->mtime);
+			f->dev = d->dev;
+			f->qidpath = d->qid.path;
+			f->mtime = d->mtime;
+			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();
+	free(d);
+	d = dirfstat(fd);
+	isapp = (d!=nil && d->length>0 && (d->qid.type&QTAPPEND));
+	if(isapp){
+		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->b, 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->b.nc){
+			f->mod = TRUE;
+			w->dirty = TRUE;
+			f->unread = TRUE;
+		}else{
+			// In case the file is on NFS, reopen the fd
+			// before dirfstat to cause the attribute cache
+			// to be updated (otherwise the mtime in the
+			// dirfstat below will be stale and not match
+			// what NFS sees).  The file is already written,
+			// so this should be a no-op when not on NFS.
+			// Opening for OWRITE (but no truncation)
+			// in case we don't have read permission.
+			// (The create above worked, so we probably
+			// still have write permission.)
+			close(fd);
+			fd = open(name, OWRITE);
+
+			d1 = dirfstat(fd);
+			if(d1 != nil){
+				free(d);
+				d = d1;
+			}
+			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(d);
+	free(namer);
+	free(name);
+	close(fd);
+	winsettag(w);
+	return;
+
+    Rescue2:
+	fbuffree(s);
+	fbuffree(r);
+	close(fd);
+	/* fall through */
+
+    Rescue1:
+	free(d);
+	free(namer);
+	free(name);
+}
+
+void
+put(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	int nname;
+	Rune  *namer;
+	Window *w;
+	File *f;
+	char *name;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	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->b.nc, namer, nname);
+	free(name);
+}
+
+void
+dump(Text *_0, Text *_1, Text *argt, int isdump, int _2, Rune *arg, int narg)
+{
+	char *name;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	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
+cut(Text *et, Text *t, Text *_0, int dosnarf, int docut, Rune *_2, int _3)
+{
+	uint q0, q1, n, locked, c;
+	Rune *r;
+
+	USED(_0);
+	USED(_2);
+	USED(_3);
+
+	/*
+	 * if not executing a mouse chord (et != t) and snarfing (dosnarf)
+	 * and executed Cut or Snarf in window tag (et->w != nil),
+	 * then use the window body selection or the tag selection
+	 * or do nothing at all.
+	 */
+	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;
+		else
+			t = nil;
+	}
+	if(t == nil)	/* no selection */
+		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->b, q0, r, n);
+			bufinsert(&snarfbuf, snarfbuf.nc, r, n);
+			q0 += n;
+		}
+		fbuffree(r);
+		acmeputsnarf();
+	}
+	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 *_0, int selectall, int tobody, Rune *_1, int _2)
+{
+	int c;
+	uint q, q0, q1, n;
+	Rune *r;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	/* 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;
+
+	acmegetsnarf();
+	if(t==nil || snarfbuf.nc==0)
+		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 _0, int _1, Rune *arg, int narg)
+{
+	Rune *r;
+	int n;
+
+	USED(_0);
+	USED(_1);
+
+	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->b, t->q0, r, n);
+		}
+		search(t, r, n);
+		free(r);
+	}
+}
+
+static Rune Lnl[] = { '\n', 0 };
+
+void
+sendx(Text *et, Text *t, Text *_0, int _1, int _2, Rune *_3, int _4)
+{
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+
+	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->b.nc, t->file->b.nc);
+	paste(t, t, nil, TRUE, TRUE, nil, 0);
+	if(textreadc(t, t->file->b.nc-1) != '\n'){
+		textinsert(t, t->file->b.nc, Lnl, 1, TRUE);
+		textsetselect(t, t->file->b.nc, t->file->b.nc);
+		textshow(t, t->q1, t->q1, 1);
+	}
+}
+
+void
+edit(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *r;
+	int len;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	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
+xexit(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	USED(et);
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	if(rowclean(&row)){
+		sendul(cexit, 0);
+		threadexits(nil);
+	}
+}
+
+void
+putall(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	int i, j, e;
+	Window *w;
+	Column *c;
+	char *a;
+
+	USED(et);
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	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 *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+	USED(_4);
+	USED(_5);
+
+	if(et && et->w)
+		warning(nil, "/mnt/acme/%d/\n", et->w->id);
+}
+
+void
+local(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	char *a, *aa;
+	Runestr dir;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	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
+xkill(Text *_0, Text *_1, Text *argt, int _2, int _3, Rune *arg, int narg)
+{
+	Rune *a, *cmd, *r;
+	int na;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+	USED(_3);
+
+	getarg(argt, FALSE, FALSE, &r, &na);
+	if(r)
+		xkill(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);
+	}
+}
+
+static Rune Lfix[] = { 'f', 'i', 'x', 0 };
+static Rune Lvar[] = { 'v', 'a', 'r', 0 };
+
+void
+fontx(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
+{
+	Rune *a, *r, *flag, *file;
+	int na, nf;
+	char *aa;
+	Reffont *newfont;
+	Dirlist *dp;
+	int i, fix;
+
+	USED(_0);
+	USED(_1);
+
+	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, Lfix, 3) || runeeq(r, narg-na, Lvar, 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, Lfix, 3) || runeeq(r, na, Lvar, 3)){
+			free(flag);
+			flag = r;
+		}else{
+			free(file);
+			file = r;
+			nf = na;
+		}
+	fix = 1;
+	if(flag)
+		fix = runeeq(flag, runestrlen(flag), Lfix, 3);
+	else if(file == nil){
+		newfont = rfget(FALSE, FALSE, FALSE, nil);
+		if(newfont)
+			fix = strcmp(newfont->f->name, t->fr.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->fr.font = newfont->f;
+		frinittick(&t->fr);
+		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 *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *a, *r;
+	Window *w;
+	int na, n, len;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	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");
+	}
+}
+
+static Rune LON[] = { 'O', 'N', 0 };
+static Rune LOFF[] = { 'O', 'F', 'F', 0 };
+static Rune Lon[] = { 'o', 'n', 0 };
+
+enum {
+	IGlobal = -2,
+	IError = -1,
+	Ion = 0,
+	Ioff = 1
+};
+
+static int
+indentval(Rune *s, int n)
+{
+	if(n < 2)
+		return IError;
+	if(runestrncmp(s, LON, n) == 0){
+		globalautoindent = TRUE;
+		warning(nil, "Indent ON\n");
+		return IGlobal;
+	}
+	if(runestrncmp(s, LOFF, n) == 0){
+		globalautoindent = FALSE;
+		warning(nil, "Indent OFF\n");
+		return IGlobal;
+	}
+	return runestrncmp(s, Lon, n) == 0;
+}
+
+static void
+fixindent(Window *w, void *arg)
+{
+	USED(arg);
+	w->autoindent = globalautoindent;
+}
+
+void
+indent(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *a, *r;
+	Window *w;
+	int na, len, autoindent;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	w = nil;
+	if(et!=nil && et->w!=nil)
+		w = et->w;
+	autoindent = IError;
+	getarg(argt, FALSE, TRUE, &r, &len);
+	if(r!=nil && len>0)
+		autoindent = indentval(r, len);
+	else{
+		a = findbl(arg, narg, &na);
+		if(a != arg)
+			autoindent = indentval(arg, narg-na);
+	}
+	if(autoindent == IGlobal)
+		allwindows(fixindent, nil);
+	else if(w != nil && autoindent >= 0)
+		w->autoindent = autoindent;
+}
+
+void
+tab(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+	Rune *a, *r;
+	Window *w;
+	int na, len, tab;
+	char *p;
+
+	USED(_0);
+	USED(_1);
+	USED(_2);
+
+	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, FALSE, TRUE);
+		}
+	}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, *filename, *dir, **av, *news;
+	Rune r, **incl;
+	int ac, w, inarg, i, n, fd, nincl, winid;
+	int sfd[3];
+	int pipechar;
+	char buf[512];
+	int olddir;
+	int ret;
+	/*static void *parg[2]; */
+	char *rcarg[4];
+	void **argv;
+	CFsys *fs;
+
+	threadsetname("runproc");
+
+	argv = argvp;
+	win = argv[0];
+	s = argv[1];
+	rdir = argv[2];
+	ndir = (uintptr)argv[3];
+	newns = (uintptr)argv[4];
+	argaddr = argv[5];
+	arg = argv[6];
+	c = argv[7];
+	cpid = argv[8];
+	iseditcmd = (uintptr)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)
+		memmove(name, e+1, strlen(e+1)+1);	/* strcpy but overlaps */
+	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(newns){
+		nincl = 0;
+		incl = nil;
+		if(win){
+			filename = smprint("%.*S", win->body.file->nname, win->body.file->name);
+			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;
+		}else{
+			filename = nil;
+			winid = 0;
+			if(activewin)
+				winid = activewin->id;
+		}
+		rfork(RFNAMEG|RFENVG|RFFDG|RFNOTEG);
+		sprint(buf, "%d", winid);
+		putenv("winid", buf);
+
+		if(filename){
+			putenv("%", filename);
+			free(filename);
+		}
+		c->md = fsysmount(rdir, ndir, incl, nincl);
+		if(c->md == nil){
+			fprint(2, "child: can't allocate mntdir: %r\n");
+			threadexits("fsysmount");
+		}
+		sprint(buf, "%d", c->md->id);
+		if((fs = nsmount("acme", buf)) == nil){
+			fprint(2, "child: can't mount acme: %r\n");
+			fsysdelid(c->md);
+			c->md = nil;
+			threadexits("nsmount");
+		}
+		if(winid>0 && (pipechar=='|' || pipechar=='>')){
+			sprint(buf, "%d/rdsel", winid);
+			sfd[0] = fsopenfd(fs, buf, OREAD);
+		}else
+			sfd[0] = open("/dev/null", OREAD);
+		if((winid>0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
+			if(iseditcmd){
+				if(winid > 0)
+					sprint(buf, "%d/editout", winid);
+				else
+					sprint(buf, "editout");
+			}else
+				sprint(buf, "%d/wrsel", winid);
+			sfd[1] = fsopenfd(fs, buf, OWRITE);
+			sfd[2] = fsopenfd(fs, "cons", OWRITE);
+		}else{
+			sfd[1] = fsopenfd(fs, "cons", OWRITE);
+			sfd[2] = sfd[1];
+		}
+		fsunmount(fs);
+	}else{
+		rfork(RFFDG|RFNOTEG);
+		fsysclose();
+		sfd[0] = open("/dev/null", OREAD);
+		sfd[1] = open("/dev/null", OWRITE);
+		sfd[2] = dup(erroutfd, -1);
+	}
+	if(win)
+		winclose(win);
+
+	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;
+
+	/*
+	 * clumsy -- we're not running in a separate thread
+	 * so we have to save the current directory and put
+	 * it back when we're done.  if this gets to be a regular
+	 * thing we could change threadexec to take a directory too.
+	 */
+	olddir = -1;
+	if(rdir != nil){
+		olddir = open(".", OREAD);
+		dir = runetobyte(rdir, ndir);
+		chdir(dir);	/* ignore error: probably app. window */
+		free(dir);
+	}
+	ret = threadspawn(sfd, av[0], av);
+	if(olddir >= 0){
+		fchdir(olddir);
+		close(olddir);
+	}
+	if(ret >= 0){
+		if(cpid)
+			sendul(cpid, ret);
+		threadexits("");
+	}
+/* libthread uses execvp so no need to do this */
+#if 0
+	e = av[0];
+	if(e[0]=='/' || (e[0]=='.' && e[1]=='/'))
+		goto Fail;
+	if(cputype){
+		sprint(buf, "%s/%s", cputype, av[0]);
+		procexec(cpid, sfd, buf, av);
+	}
+	sprint(buf, "/bin/%s", av[0]);
+	procexec(cpid, sfd, buf, av);
+#endif
+	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;
+		}
+	}
+	olddir = -1;
+	if(rdir != nil){
+		olddir = open(".", OREAD);
+		dir = runetobyte(rdir, ndir);
+		chdir(dir);	/* ignore error: probably app. window */
+		free(dir);
+	}
+	rcarg[0] = "rc";
+	rcarg[1] = "-c";
+	rcarg[2] = t;
+	rcarg[3] = nil;
+	ret = threadspawn(sfd, rcarg[0], rcarg);
+	if(olddir >= 0){
+		fchdir(olddir);
+		close(olddir);
+	}
+	if(ret >= 0){
+		if(cpid)
+			sendul(cpid, ret);
+		threadexits(nil);
+	}
+	warning(nil, "exec rc: %r\n");
+
+   Fail:
+	/* threadexec hasn't happened, so send a zero */
+	close(sfd[0]);
+	close(sfd[1]);
+	if(sfd[2] != sfd[1])
+		close(sfd[2]);
+	sendul(cpid, 0);
+	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);
+	free(c->av);
+	if(c->pid != 0)	/* successful exec */
+		sendp(ccommand, c);
+	else{
+		if(c->iseditcmd)
+			sendul(cedit, 0);
+		free(c->name);
+		free(c->text);
+		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);
+	chansetname(cpid, "cpid %s", s);
+	arg[0] = win;
+	arg[1] = s;
+	arg[2] = rdir;
+	arg[3] = (void*)(uintptr)ndir;
+	arg[4] = (void*)(uintptr)newns;
+	arg[5] = argaddr;
+	arg[6] = xarg;
+	arg[7] = c;
+	arg[8] = cpid;
+	arg[9] = (void*)(uintptr)iseditcmd;
+	threadcreate(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);
+}
+/*
+#pragma	varargck	argpos	warning	2
+#pragma	varargck	argpos	warningew	2
+*/
+
+void	warning(Mntdir*, char*, ...);
+void	warningew(Window*, Mntdir*, char*, ...);
+
+#define	fbufalloc()	emalloc(BUFSIZE)
+#define	fbuffree(x)	free(x)
+
+void	plumblook(Plumbmsg *m);
+void	plumbshow(Plumbmsg*m);
+void	acmeputsnarf(void);
+void	acmegetsnarf(void);
+int	tempfile(void);
+void	scrlresize(void);
+Font*	getfont(int, int, char*);
+char*	getarg(Text*, int, int, Rune**, int*);
+char*	getbytearg(Text*, int, int, char**);
+void	new(Text*, Text*, Text*, int, int, Rune*, int);
+void	undo(Text*, Text*, Text*, int, int, Rune*, int);
+void	scrsleep(uint);
+void	savemouse(Window*);
+void	restoremouse(Window*);
+void	clearmouse(void);
+void	allwindows(void(*)(Window*, void*), void*);
+uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*);
+
+Window*	errorwin(Mntdir*, int);
+Window*	errorwinforwin(Window*);
+Runestr cleanrname(Runestr);
+void	run(Window*, char*, Rune*, int, int, char*, char*, int);
+void fsysclose(void);
+void	setcurtext(Text*, int);
+int	isfilec(Rune);
+void	rxinit(void);
+int rxnull(void);
+Runestr	dirname(Text*, Rune*, int);
+void	error(char*);
+void	cvttorunes(char*, int, Rune*, int*, int*, int*);
+void*	tmalloc(uint);
+void	tfree(void);
+void	killprocs(void);
+void	killtasks(void);
+int	runeeq(Rune*, uint, Rune*, uint);
+int	ALEF_tid(void);
+void	iconinit(void);
+Timer*	timerstart(int);
+void	timerstop(Timer*);
+void	timercancel(Timer*);
+void	timerinit(void);
+void	cut(Text*, Text*, Text*, int, int, Rune*, int);
+void	paste(Text*, Text*, Text*, int, int, Rune*, int);
+void	get(Text*, Text*, Text*, int, int, Rune*, int);
+void	put(Text*, Text*, Text*, int, int, Rune*, int);
+void	putfile(File*, int, int, Rune*, int);
+void	fontx(Text*, Text*, Text*, int, int, Rune*, int);
+#undef isalnum
+#define isalnum acmeisalnum
+int	isalnum(Rune);
+void	execute(Text*, uint, uint, int, Text*);
+int	search(Text*, Rune*, uint);
+void	look3(Text*, uint, uint, int);
+void	editcmd(Text*, Rune*, uint);
+uint	min(uint, uint);
+uint	max(uint, uint);
+Window*	lookfile(Rune*, int);
+Window*	lookid(int, int);
+char*	runetobyte(Rune*, int);
+Rune*	bytetorune(char*, int*);
+void	fsysinit(void);
+Mntdir*	fsysmount(Rune*, int, Rune**, int);
+void		fsysdelid(Mntdir*);
+void		fsysincid(Mntdir*);
+Xfid*		respond(Xfid*, Fcall*, char*);
+int		rxcompile(Rune*);
+int		rgetc(void*, uint);
+int		tgetc(void*, uint);
+int		isaddrc(int);
+int		isregexc(int);
+void *emalloc(uint);
+void *erealloc(void*, uint);
+char	*estrdup(char*);
+Range		address(uint, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint),  int*, uint*);
+Range	regexp(uint showerr, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp);
+int		rxexecute(Text*, Rune*, uint, uint, Rangeset*);
+int		rxbexecute(Text*, uint, Rangeset*);
+Window*	makenewwindow(Text *t);
+int	expand(Text*, uint, uint, Expand*);
+Rune*	skipbl(Rune*, int, int*);
+Rune*	findbl(Rune*, int, int*);
+char*	edittext(Window*, int, Rune*, int);
+void		flushwarnings(void);
+void		startplumbing(void);
+
+Runestr	runestr(Rune*, uint);
+Range range(int, int);
+
+#define	runemalloc(a)		(Rune*)emalloc((a)*sizeof(Rune))
+#define	runerealloc(a, b)	(Rune*)erealloc((a), (b)*sizeof(Rune))
+#define	runemove(a, b, c)	memmove((a), (b), (c)*sizeof(Rune))
+
+int	ismtpt(char*);
+#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 <fcall.h>
+#include <regexp.h>
+#include <9pclient.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+CFid *plumbsendfid;
+CFid *plumbeditfid;
+
+Window*	openfile(Text*, Expand*);
+
+int	nuntitled;
+
+void
+plumbthread(void *v)
+{
+	CFid *fid;
+	Plumbmsg *m;
+	Timer *t;
+
+	USED(v);
+	threadsetname("plumbproc");
+	
+	/*
+	 * Loop so that if plumber is restarted, acme need not be.
+	 */
+	for(;;){
+		/*
+		 * Connect to plumber.
+		 */
+		plumbunmount();
+		while((fid = plumbopenfid("edit", OREAD|OCEXEC)) == nil){
+			t = timerstart(2000);
+			recv(t->c, nil);
+			timerstop(t);
+		}
+		plumbeditfid = fid;
+		plumbsendfid = plumbopenfid("send", OWRITE|OCEXEC);
+	
+		/*
+		 * Relay messages.
+		 */
+		for(;;){
+			m = plumbrecvfid(plumbeditfid);
+			if(m == nil)
+				break;
+			sendp(cplumb, m);
+		}
+
+		/*
+		 * Lost connection.
+		 */
+		fid = plumbsendfid;
+		plumbsendfid = nil;
+		fsclose(fid);
+
+		fid = plumbeditfid;
+		plumbeditfid = nil;
+		fsclose(fid);
+	}
+}
+
+void
+startplumbing(void)
+{
+	cplumb = chancreate(sizeof(Plumbmsg*), 0);
+	chansetname(cplumb, "cplumb");
+	threadcreate(plumbthread, nil, STACK);
+}
+
+
+void
+look3(Text *t, uint q0, uint q1, int external)
+{
+	int n, c, f, expanded;
+	Text *ct;
+	Expand e;
+	Rune *r;
+	uint p;
+	Plumbmsg *m;
+	Runestr dir;
+	char buf[32];
+	// for backwards search
+	int eval;
+	Range ra;
+	Rune *pat;
+
+	ct = seltext;
+	if(ct == nil)
+		seltext = t;
+	expanded = expand(t, q0, q1, &e);
+	if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
+		/* send alphanumeric expansion to external client */
+		if(expanded == FALSE)
+			return;
+		f = 0;
+		if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil))
+			f = 1;		/* acme can do it without loading a file */
+		if(q0!=e.q0 || q1!=e.q1)
+			f |= 2;	/* second (post-expand) message follows */
+		if(e.nname)
+			f |= 4;	/* it's a file name */
+		c = 'l';
+		if(t->what == Body)
+			c = 'L';
+		n = q1-q0;
+		if(n <= EVENTSIZE){
+			r = runemalloc(n);
+			bufread(&t->file->b, q0, r, n);
+			winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r);
+			free(r);
+		}else
+			winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n);
+		if(q0==e.q0 && q1==e.q1)
+			return;
+		if(e.nname){
+			n = e.nname;
+			if(e.a1 > e.a0)
+				n += 1+(e.a1-e.a0);
+			r = runemalloc(n);
+			runemove(r, e.name, e.nname);
+			if(e.a1 > e.a0){
+				r[e.nname] = ':';
+				bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0);
+			}
+		}else{
+			n = e.q1 - e.q0;
+			r = runemalloc(n);
+			bufread(&t->file->b, e.q0, r, n);
+		}
+		f &= ~2;
+		if(n <= EVENTSIZE)
+			winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r);
+		else
+			winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n);
+		free(r);
+		goto Return;
+	}
+	if(plumbsendfid != nil){
+		/* send whitespace-delimited word to plumber */
+		m = emalloc(sizeof(Plumbmsg));
+		m->src = estrdup("acme");
+		m->dst = nil;
+		dir = dirname(t, nil, 0);
+		if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
+			free(dir.r);
+			dir.r = nil;
+			dir.nr = 0;
+		}
+		if(dir.nr == 0)
+			m->wdir = estrdup(wdir);
+		else
+			m->wdir = runetobyte(dir.r, dir.nr);
+		free(dir.r);
+		m->type = estrdup("text");
+		m->attr = nil;
+		buf[0] = '\0';
+		if(q1 == q0){
+			if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
+				q0 = t->q0;
+				q1 = t->q1;
+			}else{
+				p = q0;
+				while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n')
+					q0--;
+				while(q1<t->file->b.nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n')
+					q1++;
+				if(q1 == q0){