xref: /inferno-os/appl/acme/exec.b (revision 043f83732c06a092cd12b5ad4f92264dee44c61a)
1implement Exec;
2
3include "common.m";
4
5sys : Sys;
6dat : Dat;
7acme : Acme;
8utils : Utils;
9graph : Graph;
10gui : Gui;
11lookx : Look;
12bufferm : Bufferm;
13textm : Textm;
14scrl : Scroll;
15filem : Filem;
16windowm : Windowm;
17rowm : Rowm;
18columnm : Columnm;
19fsys : Fsys;
20editm: Edit;
21
22Dir, OREAD, OWRITE : import Sys;
23EVENTSIZE, QWaddr, QWdata, QWevent, Astring : import dat;
24Lock, Reffont, Ref, seltext, seq, row : import dat;
25warning, error, skipbl, findbl, stralloc, strfree, exec : import utils;
26dirname : import lookx;
27Body, Text : import textm;
28File : import filem;
29sprint : import sys;
30TRUE, FALSE, XXX, BUFSIZE : import Dat;
31Buffer : import bufferm;
32Row : import rowm;
33Column : import columnm;
34Window : import windowm;
35setalphabet: import textm;
36
37# snarfbuf : ref Buffer;
38
39init(mods : ref Dat->Mods)
40{
41	sys = mods.sys;
42	dat = mods.dat;
43	acme = mods.acme;
44	utils = mods.utils;
45	graph = mods.graph;
46	gui = mods.gui;
47	lookx = mods.look;
48	bufferm = mods.bufferm;
49	textm = mods.textm;
50	scrl = mods.scroll;
51	filem = mods.filem;
52	rowm = mods.rowm;
53	windowm = mods.windowm;
54	columnm = mods.columnm;
55	fsys = mods.fsys;
56	editm = mods.edit;
57
58	snarfbuf = bufferm->newbuffer();
59}
60
61Exectab : adt {
62	name : string;
63	fun : int;
64	mark : int;
65	flag1 : int;
66	flag2 : int;
67};
68
69F_ALPHABET, F_CUT, F_DEL, F_DELCOL, F_DUMP, F_EDIT, F_EXITX, F_FONTX, F_GET, F_ID, F_INCL, F_KILL, F_LIMBO, F_LINENO, F_LOCAL, F_LOOK, F_NEW, F_NEWCOL, F_PASTE, F_PUT, F_PUTALL, F_UNDO, F_SEND, F_SORT, F_TAB, F_ZEROX : con iota;
70
71exectab := array[] of {
72	Exectab ( "Alphabet",	F_ALPHABET,	FALSE,	XXX,		XXX		),
73	Exectab ( "Cut",		F_CUT,		TRUE,	TRUE,	TRUE	),
74	Exectab ( "Del",			F_DEL,		FALSE,	FALSE,	XXX		),
75	Exectab ( "Delcol",		F_DELCOL,	FALSE,	XXX,		XXX		),
76	Exectab ( "Delete",		F_DEL,		FALSE,	TRUE,	XXX		),
77	Exectab ( "Dump",		F_DUMP,		FALSE,	TRUE,	XXX		),
78	Exectab ( "Edit",		F_EDIT,		FALSE,	XXX,		XXX		),
79	Exectab ( "Exit",		F_EXITX,		FALSE,	XXX,		XXX		),
80	Exectab ( "Font",		F_FONTX,		FALSE,	XXX,		XXX		),
81	Exectab ( "Get",			F_GET,		FALSE,	TRUE,	XXX		),
82	Exectab ( "ID",			F_ID,		FALSE,	XXX,		XXX		),
83	Exectab ( "Incl",		F_INCL,		FALSE,	XXX,		XXX		),
84	Exectab ( "Kill",			F_KILL,		FALSE,	XXX,		XXX		),
85	Exectab ( "Limbo",		F_LIMBO,		FALSE,	XXX,		XXX   	),
86	Exectab ( "Lineno",		F_LINENO,	FALSE,	XXX,		XXX		),
87	Exectab ( "Load",		F_DUMP,		FALSE,	FALSE,	XXX		),
88	Exectab ( "Local",		F_LOCAL,		FALSE,	XXX,		XXX		),
89	Exectab ( "Look",		F_LOOK,		FALSE,	XXX,		XXX		),
90	Exectab ( "New",		F_NEW,		FALSE,	XXX,		XXX		),
91	Exectab ( "Newcol",		F_NEWCOL,	FALSE,	XXX,		XXX		),
92	Exectab ( "Paste",		F_PASTE,		TRUE,	TRUE,	XXX		),
93	Exectab ( "Put",			F_PUT,		FALSE,	XXX,		XXX		),
94	Exectab ( "Putall",		F_PUTALL,	FALSE,	XXX,		XXX		),
95	Exectab ( "Redo",		F_UNDO,		FALSE,	FALSE,	XXX		),
96	Exectab ( "Send",		F_SEND,		TRUE,	XXX,		XXX		),
97	Exectab ( "Snarf",		F_CUT,		FALSE,	TRUE,	FALSE	),
98	Exectab ( "Sort",		F_SORT,		FALSE,	XXX,		XXX		),
99	Exectab ( "Tab",		F_TAB,		FALSE,	XXX,		XXX		),
100	Exectab ( "Undo",		F_UNDO,		FALSE,	TRUE,	XXX		),
101	Exectab ( "Zerox",		F_ZEROX,		FALSE,	XXX,		XXX		),
102	Exectab ( nil, 			0,			0,		0,		0		),
103};
104
105runfun(fun : int, et, t, argt : ref Text, flag1, flag2 : int, arg : string, narg : int)
106{
107	case (fun) {
108		F_ALPHABET	=> alphabet(et, argt, arg, narg);
109		F_CUT 	 	=> cut(et, t, flag1, flag2);
110		F_DEL 		=> del(et, flag1);
111		F_DELCOL	=> delcol(et);
112		F_DUMP 		=> dump(argt, flag1, arg, narg);
113		F_EDIT		=> edit(et, argt, arg, narg);
114		F_EXITX		=> exitx();
115		F_FONTX		=> fontx(et, t, argt, arg, narg);
116		F_GET 		=> get(et, t, argt, flag1, arg, narg);
117		F_ID 		=> id(et);
118		F_INCL 		=> incl(et, argt, arg, narg);
119		F_KILL 		=> kill(argt, arg, narg);
120		F_LIMBO		=> limbo(et);
121		F_LINENO		=> lineno(et);
122		F_LOCAL 		=> local(et, argt, arg);
123		F_LOOK 		=> look(et, t, argt);
124		F_NEW 		=> lookx->new(et, t, argt, flag1, flag2, arg, narg);
125		F_NEWCOL	=> newcol(et);
126		F_PASTE		=> paste(et, t, flag1, flag2);
127		F_PUT		=> put(et, argt, arg, narg);
128		F_PUTALL 	=> putall();
129		F_UNDO 		=> undo(et, flag1);
130		F_SEND		=> send(et, t);
131		F_SORT		=> sort(et);
132		F_TAB		=> tab(et, argt, arg, narg);
133		F_ZEROX		=> zerox(et, t);
134		*			=> error("bad case in runfun()");
135	}
136}
137
138lookup(r : string, n : int) : int
139{
140	nr : int;
141
142	(r, n) = skipbl(r, n);
143	if(n == 0)
144		return -1;
145	(nil, nr) = findbl(r, n);
146	nr = n-nr;
147	for(i := 0; exectab[i].name != nil; i++)
148		if (r[0:nr] == exectab[i].name)
149			return i;
150	return -1;
151}
152
153isexecc(c : int) : int
154{
155	if(lookx->isfilec(c))
156		return 1;
157	return c=='<' || c=='|' || c=='>';
158}
159
160execute(t : ref Text, aq0 : int, aq1 : int, external : int, argt : ref Text)
161{
162	q0, q1 : int;
163	r : ref Astring;
164	s, dir, aa, a : string;
165	e : int;
166	c, n, f : int;
167
168	q0 = aq0;
169	q1 = aq1;
170	if(q1 == q0){	# expand to find word (actually file name)
171		# if in selection, choose selection
172		if(t.q1>t.q0 && t.q0<=q0 && q0<=t.q1){
173			q0 = t.q0;
174			q1 = t.q1;
175		}else{
176			while(q1<t.file.buf.nc && isexecc(c=t.readc(q1)) && c!=':')
177				q1++;
178			while(q0>0 && isexecc(c=t.readc(q0-1)) && c!=':')
179				q0--;
180			if(q1 == q0)
181				return;
182		}
183	}
184	r = stralloc(q1-q0);
185	t.file.buf.read(q0, r, 0, q1-q0);
186	e = lookup(r.s, q1-q0);
187	if(!external && t.w!=nil && t.w.nopen[QWevent]>byte 0){
188		f = 0;
189		if(e >= 0)
190			f |= 1;
191		if(q0!=aq0 || q1!=aq1){
192			t.file.buf.read(aq0, r, 0, aq1-aq0);
193			f |= 2;
194		}
195		(aa, a) = getbytearg(argt, TRUE, TRUE);
196		if(a != nil){
197			if(len a > EVENTSIZE){	# too big; too bad
198				aa = a = nil;
199				warning(nil, "`argument string too long\n");
200				return;
201			}
202			f |= 8;
203		}
204		c = 'x';
205		if(t.what == Body)
206			c = 'X';
207		n = aq1-aq0;
208		if(n <= EVENTSIZE)
209			t.w.event(sprint("%c%d %d %d %d %s\n", c, aq0, aq1, f, n, r.s[0:n]));
210		else
211			t.w.event(sprint("%c%d %d %d 0 \n", c, aq0, aq1, f));
212		if(q0!=aq0 || q1!=aq1){
213			n = q1-q0;
214			t.file.buf.read(q0, r, 0, n);
215			if(n <= EVENTSIZE)
216				t.w.event(sprint("%c%d %d 0 %d %s\n", c, q0, q1, n, r.s[0:n]));
217			else
218				t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q1));
219		}
220		if(a != nil){
221			t.w.event(sprint("%c0 0 0 %d %s\n", c, len a, a));
222			if(aa != nil)
223				t.w.event(sprint("%c0 0 0 %d %s\n", c, len aa, aa));
224			else
225				t.w.event(sprint("%c0 0 0 0 \n", c));
226		}
227		strfree(r);
228		r = nil;
229		a = aa = nil;
230		return;
231	}
232	if(e >= 0){
233		if(exectab[e].mark && seltext!=nil)
234		if(seltext.what == Body){
235			seq++;
236			seltext.w.body.file.mark();
237		}
238		(s, n) = skipbl(r.s, q1-q0);
239		(s, n) = findbl(s, n);
240		(s, n) = skipbl(s, n);
241		runfun(exectab[e].fun, t, seltext, argt, exectab[e].flag1, exectab[e].flag2, s, n);
242		strfree(r);
243		r = nil;
244		return;
245	}
246
247	(dir, n) = dirname(t, nil, 0);
248	if(n==1 && dir[0]=='.'){	# sigh
249		dir = nil;
250		n = 0;
251	}
252	(aa, a) = getbytearg(argt, TRUE, TRUE);
253	if(t.w != nil)
254		t.w.refx.inc();
255	spawn run(t.w, r.s, dir, n, TRUE, aa, a, FALSE);
256}
257
258printarg(argt : ref Text, q0 : int, q1 : int) : string
259{
260	buf : string;
261
262	if(argt.what!=Body || argt.file.name==nil)
263		return nil;
264	if(q0 == q1)
265		buf = sprint("%s:#%d", argt.file.name, q0);
266	else
267		buf = sprint("%s:#%d,#%d", argt.file.name, q0, q1);
268	return buf;
269}
270
271getarg(argt : ref Text, doaddr : int, dofile : int) : (string, string, int)
272{
273	r : ref Astring;
274	n : int;
275	e : Dat->Expand;
276	a : string;
277	ok : int;
278
279	if(argt == nil)
280		return (nil, nil, 0);
281	a = nil;
282	argt.commit(TRUE);
283	(ok, e) = lookx->expand(argt, argt.q0, argt.q1);
284	if (ok) {
285		e.bname = nil;
286		if(len e.name && dofile){
287			if(doaddr)
288				a = printarg(argt, e.q0, e.q1);
289			return (a, e.name, len e.name);
290		}
291		e.name = nil;
292	}else{
293		e.q0 = argt.q0;
294		e.q1 = argt.q1;
295	}
296	n = e.q1 - e.q0;
297	r = stralloc(n);
298	argt.file.buf.read(e.q0, r, 0, n);
299	if(doaddr)
300		a = printarg(argt, e.q0, e.q1);
301	return(a, r.s, n);
302}
303
304getbytearg(argt : ref Text, doaddr : int, dofile : int) : (string, string)
305{
306	r : string;
307	n : int;
308	aa : string;
309
310	(aa, r, n) = getarg(argt, doaddr, dofile);
311	if(r == nil)
312		return (nil, nil);
313	return (aa, r);
314}
315
316newcol(et : ref Text)
317{
318	c : ref Column;
319
320	c = et.row.add(nil, -1);
321	if(c != nil)
322		c.add(nil, nil, -1).settag();
323}
324
325delcol(et : ref Text)
326{
327	c := et.col;
328	if(c==nil || !c.clean(FALSE))
329		return;
330	for(i:=0; i<c.nw; i++){
331		w := c.w[i];
332		if(int w.nopen[QWevent]+int w.nopen[QWaddr]+int w.nopen[QWdata] > 0){
333			warning(nil, sys->sprint("can't delete column; %s is running an external command\n", w.body.file.name));
334			return;
335		}
336	}
337	c.row.close(c, TRUE);
338}
339
340del(et : ref Text, flag1 : int)
341{
342	if(et.col==nil || et.w == nil)
343		return;
344	if(flag1 || et.w.body.file.ntext>1 || et.w.clean(FALSE, FALSE))
345		et.col.close(et.w, TRUE);
346}
347
348sort(et : ref Text)
349{
350	if(et.col != nil)
351		et.col.sort();
352}
353
354seqof(w: ref Window, isundo: int): int
355{
356	# if it's undo, see who changed with us
357	if(isundo)
358		return w.body.file.seq;
359	# if it's redo, see who we'll be sync'ed up with
360	return w.body.file.redoseq();
361}
362
363undo(et : ref Text, flag1 : int)
364{
365	i, j: int;
366	c: ref Column;
367	w: ref Window;
368	seq: int;
369
370	if(et==nil || et.w== nil)
371		return;
372	seq = seqof(et.w, flag1);
373	for(i=0; i<row.ncol; i++){
374		c = row.col[i];
375		for(j=0; j<c.nw; j++){
376			w = c.w[j];
377			if(seqof(w, flag1) == seq)
378				w.undo(flag1);
379		}
380	}
381	# et.w.undo(flag1);
382}
383
384getname(t : ref Text, argt : ref Text, arg : string, narg : int, isput : int) : string
385{
386	r, dir : string;
387	i, n, ndir, promote : int;
388
389	(nil, r, n) = getarg(argt, FALSE, TRUE);
390	promote = FALSE;
391	if(r == nil)
392		promote = TRUE;
393	else if(isput){
394		# if are doing a Put, want to synthesize name even for non-existent file
395		# best guess is that file name doesn't contain a slash
396		promote = TRUE;
397		for(i=0; i<n; i++)
398			if(r[i] == '/'){
399				promote = FALSE;
400				break;
401			}
402		if(promote){
403			t = argt;
404			arg = r;
405			narg = n;
406		}
407	}
408	if(promote){
409		n = narg;
410		if(n <= 0)
411			return t.file.name;
412		# prefix with directory name if necessary
413		dir = nil;
414		ndir = 0;
415		if(n>0 && arg[0]!='/'){
416			(dir, ndir) = dirname(t, nil, 0);
417			if(n==1 && dir[0]=='.'){	# sigh
418				dir = nil;
419				ndir = 0;
420			}
421		}
422		if(dir != nil){
423			r = dir[0:ndir] + arg[0:n];
424			dir = nil;
425			n += ndir;
426		}else
427			r = arg[0:n];
428	}
429	return r;
430}
431
432zerox(et : ref Text, t : ref Text)
433{
434	nw : ref Window;
435	c, locked : int;
436
437	locked = FALSE;
438	if(t!=nil && t.w!=nil && t.w!=et.w){
439		locked = TRUE;
440		c = 'M';
441		if(et.w != nil)
442			c = et.w.owner;
443		t.w.lock(c);
444	}
445	if(t == nil)
446		t = et;
447	if(t==nil || t.w==nil)
448		return;
449	t = t.w.body;
450	if(t.w.isdir)
451		warning(nil, sprint("%s is a directory; Zerox illegal\n", t.file.name));
452	else{
453		nw = t.w.col.add(nil, t.w, -1);
454		# ugly: fix locks so w.unlock works
455		nw.lock1(t.w.owner);
456	}
457	if(locked)
458		t.w.unlock();
459}
460
461get(et : ref Text, t : ref Text, argt : ref Text, flag1 : int, arg : string, narg : int)
462{
463	name : string;
464	r : string;
465	i, n, dirty : int;
466	w : ref Window;
467	u : ref Text;
468	d : Dir;
469	ok : int;
470
471	if(flag1)
472		if(et==nil || et.w==nil)
473			return;
474	if(!et.w.isdir && (et.w.body.file.buf.nc>0 && !et.w.clean(TRUE, FALSE)))
475		return;
476	w = et.w;
477	t = w.body;
478	name = getname(t, argt, arg, narg, FALSE);
479	if(name == nil){
480		warning(nil, "no file name\n");
481		return;
482	}
483	if(t.file.ntext>1){
484		(ok, d) = sys->stat(name);
485		if (ok == 0 && d.qid.qtype & Sys->QTDIR) {
486			warning(nil, sprint("%s is a directory; can't read with multiple windows on it\n", name));
487			return;
488		}
489	}
490	r = name;
491	n = len name;
492	for(i=0; i<t.file.ntext; i++){
493		u = t.file.text[i];
494		# second and subsequent calls with zero an already empty buffer, but OK
495		u.reset();
496		u.w.dirfree();
497	}
498	samename := r[0:n] == t.file.name;
499	t.loadx(0, name, samename);
500	if(samename){
501		t.file.mod = FALSE;
502		dirty = FALSE;
503	}else{
504		t.file.mod = TRUE;
505		dirty = TRUE;
506	}
507	for(i=0; i<t.file.ntext; i++)
508		t.file.text[i].w.dirty = dirty;
509	name = nil;
510	r = nil;
511	w.settag();
512	t.file.unread = FALSE;
513	for(i=0; i<t.file.ntext; i++){
514		u = t.file.text[i];
515		u.w.tag.setselect(u.w.tag.file.buf.nc, u.w.tag.file.buf.nc);
516		scrl->scrdraw(u);
517	}
518}
519
520putfile(f: ref File, q0: int, q1: int, name: string)
521{
522	n : int;
523	r, s : ref Astring;
524	w : ref Window;
525	i, q : int;
526	fd : ref Sys->FD;
527	d : Dir;
528	ok : int;
529
530	w = f.curtext.w;
531
532	{
533		if(name == f.name){
534			(ok, d) = sys->stat(name);
535			if(ok >= 0 && (f.dev!=d.dev || f.qidpath!=d.qid.path || f.mtime<d.mtime)){
536				f.dev = d.dev;
537				f.qidpath = d.qid.path;
538				f.mtime = d.mtime;
539				if(f.unread)
540					warning(nil, sys->sprint("%s not written; file already exists\n", name));
541				else
542					warning(nil, sys->sprint("%s modified since last read\n", name));
543				raise "e";
544			}
545		}
546		fd = sys->create(name, OWRITE, 8r664);	# was 666
547		if(fd == nil){
548			warning(nil, sprint("can't create file %s: %r\n", name));
549			raise "e";
550		}
551		r = stralloc(BUFSIZE);
552		s = stralloc(BUFSIZE);
553
554		{
555			(ok, d) = sys->fstat(fd);
556			if(ok>=0 && (d.mode&Sys->DMAPPEND) && d.length>big 0){
557				warning(nil, sprint("%s not written; file is append only\n", name));
558				raise "e";
559			}
560			for(q = q0; q < q1; q += n){
561				n = q1 - q;
562				if(n > BUFSIZE)
563					n = BUFSIZE;
564				f.buf.read(q, r, 0, n);
565				ab := array of byte r.s[0:n];
566				if(sys->write(fd, ab, len ab) != len ab){
567					ab = nil;
568					warning(nil, sprint("can't write file %s: %r\n", name));
569					raise "e";
570				}
571				ab = nil;
572			}
573			if(name == f.name){
574				d0 : Dir;
575
576				if(q0 != 0 || q1 != f.buf.nc){
577					f.mod = TRUE;
578					w.dirty = TRUE;
579					f.unread = TRUE;
580				}
581				else{
582					(ok, d0) = sys->fstat(fd);	# use old values if we failed
583					if (ok >= 0)
584						d = d0;
585					f.qidpath = d.qid.path;
586					f.dev = d.dev;
587					f.mtime = d.mtime;
588					f.mod = FALSE;
589					w.dirty = FALSE;
590					f.unread = FALSE;
591				}
592				for(i=0; i<f.ntext; i++){
593					f.text[i].w.putseq = f.seq;
594					f.text[i].w.dirty = w.dirty;
595				}
596			}
597			strfree(s);
598			strfree(r);
599			s = r = nil;
600			name = nil;
601			fd = nil;
602			w.settag();
603		}
604		exception{
605			* =>
606				strfree(s);
607				strfree(r);
608				s = r = nil;
609				fd = nil;
610				raise "e";
611		}
612	}
613	exception{
614		* =>
615			name = nil;
616			return;
617	}
618}
619
620put(et : ref Text, argt : ref Text, arg : string, narg : int)
621{
622	namer : string;
623	name : string;
624	w : ref Window;
625
626	if(et==nil || et.w==nil || et.w.isdir)
627		return;
628	w = et.w;
629	f := w.body.file;
630
631	name = getname(w.body, argt, arg, narg, TRUE);
632	if(name == nil){
633		warning(nil, "no file name\n");
634		return;
635	}
636	namer = name;
637	putfile(f, 0, f.buf.nc, namer);
638	name = nil;
639}
640
641dump(argt : ref Text, isdump : int, arg : string, narg : int)
642{
643	name : string;
644
645	if(narg)
646		name = arg;
647	else
648		(nil, name) = getbytearg(argt, FALSE, TRUE);
649	if(isdump)
650		row.dump(name);
651	else {
652		if (!row.qlock.locked())
653			error("row not locked in dump()");
654		row.loadx(name, FALSE);
655	}
656	name = nil;
657}
658
659cut(et : ref Text, t : ref Text, dosnarf : int, docut : int)
660{
661	q0, q1, n, locked, c : int;
662	r : ref Astring;
663
664	# use current window if snarfing and its selection is non-null
665	if(et!=t && dosnarf && et.w!=nil){
666		if(et.w.body.q1>et.w.body.q0){
667			t = et.w.body;
668			t.file.mark();	# seq has been incremented by execute
669		}
670		else if(et.w.tag.q1>et.w.tag.q0)
671			t = et.w.tag;
672	}
673	if(t == nil)
674		return;
675	locked = FALSE;
676	if(t.w!=nil && et.w!=t.w){
677		locked = TRUE;
678		c = 'M';
679		if(et.w != nil)
680			c = et.w.owner;
681		t.w.lock(c);
682	}
683	if(t.q0 == t.q1){
684		if(locked)
685			t.w.unlock();
686		return;
687	}
688	if(dosnarf){
689		q0 = t.q0;
690		q1 = t.q1;
691		snarfbuf.delete(0, snarfbuf.nc);
692		r = stralloc(BUFSIZE);
693		while(q0 < q1){
694			n = q1 - q0;
695			if(n > BUFSIZE)
696				n = BUFSIZE;
697			t.file.buf.read(q0, r, 0, n);
698			snarfbuf.insert(snarfbuf.nc, r.s, n);
699			q0 += n;
700		}
701		strfree(r);
702		r = nil;
703		acme->putsnarf();
704	}
705	if(docut){
706		t.delete(t.q0, t.q1, TRUE);
707		t.setselect(t.q0, t.q0);
708		if(t.w != nil){
709			scrl->scrdraw(t);
710			t.w.settag();
711		}
712	}else if(dosnarf)	# Snarf command
713		dat->argtext = t;
714	if(locked)
715		t.w.unlock();
716}
717
718paste(et : ref Text, t : ref Text, selectall : int, tobody: int)
719{
720	c : int;
721	q, q0, q1, n : int;
722	r : ref Astring;
723
724	# if(tobody), use body of executing window  (Paste or Send command)
725	if(tobody && et!=nil && et.w!=nil){
726		t = et.w.body;
727		t.file.mark();	# seq has been incremented by execute
728	}
729	if(t == nil)
730		return;
731
732	acme->getsnarf();
733	if(t==nil || snarfbuf.nc==0)
734		return;
735	if(t.w!=nil && et.w!=t.w){
736		c = 'M';
737		if(et.w != nil)
738			c = et.w.owner;
739		t.w.lock(c);
740	}
741	cut(t, t, FALSE, TRUE);
742	q = 0;
743	q0 = t.q0;
744	q1 = t.q0+snarfbuf.nc;
745	r = stralloc(BUFSIZE);
746	while(q0 < q1){
747		n = q1 - q0;
748		if(n > BUFSIZE)
749			n = BUFSIZE;
750		if(r == nil)
751			r = stralloc(n);
752		snarfbuf.read(q, r, 0, n);
753		t.insert(q0, r.s, n, TRUE, 0);
754		q += n;
755		q0 += n;
756	}
757	strfree(r);
758	r = nil;
759	if(selectall)
760		t.setselect(t.q0, q1);
761	else
762		t.setselect(q1, q1);
763	if(t.w != nil){
764		scrl->scrdraw(t);
765		t.w.settag();
766	}
767	if(t.w!=nil && et.w!=t.w)
768		t.w.unlock();
769}
770
771look(et : ref Text, t : ref Text, argt : ref Text)
772{
773	r : string;
774	s : ref Astring;
775	n : int;
776
777	if(et != nil && et.w != nil){
778		t = et.w.body;
779		(nil, r, n) = getarg(argt, FALSE, FALSE);
780		if(r == nil){
781			n = t.q1-t.q0;
782			s = stralloc(n);
783			t.file.buf.read(t.q0, s, 0, n);
784			r = s.s;
785		}
786		lookx->search(t, r, n);
787		r = nil;
788	}
789}
790
791send(et : ref Text, t : ref Text)
792{
793	if(et.w==nil)
794		return;
795	t = et.w.body;
796	if(t.q0 != t.q1)
797		cut(t, t, TRUE, FALSE);
798	t.setselect(t.file.buf.nc, t.file.buf.nc);
799	paste(t, t, TRUE, TRUE);
800	if(t.readc(t.file.buf.nc-1) != '\n'){
801		t.insert(t.file.buf.nc, "\n", 1, TRUE, 0);
802		t.setselect(t.file.buf.nc, t.file.buf.nc);
803	}
804}
805
806edit(et: ref Text, argt: ref Text, arg: string, narg: int)
807{
808	r: string;
809	leng: int;
810
811	if(et == nil)
812		return;
813	(nil, r, leng) = getarg(argt, FALSE, TRUE);
814	seq++;
815	if(r != nil){
816		editm->editcmd(et, r, leng);
817		r = nil;
818	}else
819		editm->editcmd(et, arg, narg);
820}
821
822exitx()
823{
824	if(row.clean(TRUE))
825		acme->acmeexit(nil);
826}
827
828putall()
829{
830	i, j, e : int;
831	w : ref Window;
832	c : ref Column;
833	a : string;
834
835	for(i=0; i<row.ncol; i++){
836		c = row.col[i];
837		for(j=0; j<c.nw; j++){
838			w = c.w[j];
839			if(w.isscratch || w.isdir || len w.body.file.name==0)
840				continue;
841			if(w.nopen[QWevent] > byte 0)
842				continue;
843			a = w.body.file.name;
844			e = utils->access(a);
845			if(w.body.file.mod || w.body.ncache)
846				if(e < 0)
847					warning(nil, sprint("no auto-Put of %s: %r\n", a));
848				else{
849					w.commit(w.body);
850					put(w.body, nil, nil, 0);
851				}
852			a = nil;
853		}
854	}
855}
856
857id(et : ref Text)
858{
859	if(et != nil && et.w != nil)
860		warning(nil, sprint("/mnt/acme/%d/\n", et.w.id));
861}
862
863limbo(et: ref Text)
864{
865	s := getname(et.w.body, nil, nil, 0, 0);
866	if(s == nil)
867		return;
868	for(l := len s; l > 0 && s[--l] != '/'; )
869		;
870	if(s[l] == '/')
871		s = s[l+1: ];
872	s = "limbo -gw " + s;
873	(dir, n) := dirname(et, nil, 0);
874	if(n==1 && dir[0]=='.'){	# sigh
875		dir = nil;
876		n = 0;
877	}
878	spawn run(nil, s, dir, n, TRUE, nil, nil, FALSE);
879}
880
881local(et : ref Text, argt : ref Text, arg : string)
882{
883	a, aa : string;
884	dir : string;
885	n : int;
886
887	(aa, a) = getbytearg(argt, TRUE, TRUE);
888
889	(dir, n) = dirname(et, nil, 0);
890	if(n==1 && dir[0]=='.'){	# sigh
891		dir = nil;
892		n = 0;
893	}
894	spawn run(nil, arg, dir, n, FALSE, aa, a, FALSE);
895}
896
897kill(argt : ref Text, arg : string, narg : int)
898{
899	a, cmd, r : string;
900	na : int;
901
902	(nil, r, na) = getarg(argt, FALSE, FALSE);
903	if(r != nil)
904		kill(nil, r, na);
905	# loop condition: *arg is not a blank
906	for(;;){
907		(a, na) = findbl(arg, narg);
908		if(a == arg)
909			break;
910		cmd = arg[0:narg-na];
911		dat->ckill <-= cmd;
912		(arg, narg) = skipbl(a, na);
913	}
914}
915
916lineno(et : ref Text)
917{
918	n : int;
919
920	if (et == nil || et.w == nil || (et = et.w.body) == nil)
921		return;
922	q0 := et.q0;
923	q1 := et.q1;
924	if (q0 < 0 || q1 < 0 || q0 > q1)
925		return;
926	ln0 := 1;
927	ln1 := 1;
928	rp := stralloc(BUFSIZE);
929	nc := et.file.buf.nc;
930	if (q0 >= nc)
931		q0 = nc-1;
932	if (q1 >= nc)
933		q1 = nc-1;
934	for (q := 0; q < q1; ) {
935		if (q+BUFSIZE > nc)
936			n = nc-q;
937		else
938			n = BUFSIZE;
939		et.file.buf.read(q, rp, 0, n);
940		for (i := 0; i < n && q < q1; i++) {
941			if (rp.s[i] == '\n') {
942				if (q < q0)
943					ln0++;
944				if (q < q1-1)
945					ln1++;
946			}
947			q++;
948		}
949	}
950	rp = nil;
951	if (et.file.name != nil)
952		file := et.file.name + ":";
953	else
954		file = nil;
955	if (ln0 == ln1)
956		warning(nil, sprint("%s%d\n", file, ln0));
957	else
958		warning(nil, sprint("%s%d,%d\n", file, ln0, ln1));
959}
960
961fontx(et : ref Text, t : ref Text, argt : ref Text, arg : string, narg : int)
962{
963	a, r, flag, file : string;
964	na, nf : int;
965	aa : string;
966	newfont : ref Reffont;
967	dp : ref Dat->Dirlist;
968	i, fix : int;
969
970	if(et==nil || et.w==nil)
971		return;
972	t = et.w.body;
973	flag = nil;
974	file = nil;
975	# loop condition: *arg is not a blank
976	nf = 0;
977	for(;;){
978		(a, na) = findbl(arg, narg);
979		if(a == arg)
980			break;
981		r = arg[0:narg-na];
982		if(r == "fix" || r == "var"){
983			flag = nil;
984			flag = r;
985		}else{
986			file = r;
987			nf = narg-na;
988		}
989		(arg, narg) = skipbl(a, na);
990	}
991	(nil, r, na) = getarg(argt, FALSE, TRUE);
992	if(r != nil)
993		if(r == "fix" || r == "var"){
994			flag = nil;
995			flag = r;
996		}else{
997			file = r;
998			nf = na;
999		}
1000	fix = 1;
1001	if(flag != nil)
1002		fix = flag == "fix";
1003	else if(file == nil){
1004		newfont = Reffont.get(FALSE, FALSE, FALSE, nil);
1005		if(newfont != nil)
1006			fix = newfont.f.name == t.frame.font.name;
1007	}
1008	if(file != nil){
1009		aa = file[0:nf];
1010		newfont = Reffont.get(fix, flag!=nil, FALSE, aa);
1011		aa = nil;
1012	}else
1013		newfont = Reffont.get(fix, FALSE, FALSE, nil);
1014	if(newfont != nil){
1015		graph->draw(gui->mainwin, t.w.r, acme->textcols[Framem->BACK], nil, (0, 0));
1016		t.reffont.close();
1017		t.reffont = newfont;
1018		t.frame.font = newfont.f;
1019		if(t.w.isdir){
1020			t.all.min.x++;	# force recolumnation; disgusting!
1021			for(i=0; i<t.w.ndl; i++){
1022				dp = t.w.dlp[i];
1023				aa = dp.r;
1024				dp.wid = graph->strwidth(newfont.f, aa);
1025				aa = nil;
1026			}
1027		}
1028		# avoid shrinking of window due to quantization
1029		t.w.col.grow(t.w, -1, 1);
1030	}
1031	file = nil;
1032	flag = nil;
1033}
1034
1035incl(et : ref Text, argt : ref Text, arg : string, narg : int)
1036{
1037	a, r : string;
1038	w : ref Window;
1039	na, n, leng : int;
1040
1041	if(et==nil || et.w==nil)
1042		return;
1043	w = et.w;
1044	n = 0;
1045	(nil, r, leng) = getarg(argt, FALSE, TRUE);
1046	if(r != nil){
1047		n++;
1048		w.addincl(r, leng);
1049	}
1050	# loop condition: *arg is not a blank
1051	for(;;){
1052		(a, na) = findbl(arg, narg);
1053		if(a == arg)
1054			break;
1055		r = arg[0:narg-na];
1056		n++;
1057		w.addincl(r, narg-na);
1058		(arg, narg) = skipbl(a, na);
1059	}
1060	if(n==0 && w.nincl){
1061		for(n=w.nincl; --n>=0; )
1062			warning(nil, sprint("%s ", w.incl[n]));
1063		warning(nil, "\n");
1064	}
1065}
1066
1067tab(et : ref Text, argt : ref Text, arg : string, narg : int)
1068{
1069	a, r, p : string;
1070	w : ref Window;
1071	na, leng, tab : int;
1072
1073	if(et==nil || et.w==nil)
1074		return;
1075	w = et.w;
1076	(nil, r, leng) = getarg(argt, FALSE, TRUE);
1077	tab = 0;
1078	if(r!=nil && leng>0){
1079		p = r[0:leng];
1080		if('0'<=p[0] && p[0]<='9')
1081			tab = int p;
1082		p = nil;
1083	}else{
1084		(a, na) = findbl(arg, narg);
1085		if(a != arg){
1086			p = arg[0:narg-na];
1087			if('0'<=p[0] && p[0]<='9')
1088				tab = int p;
1089			p = nil;
1090		}
1091	}
1092	if(tab > 0){
1093		if(w.body.tabstop != tab){
1094			w.body.tabstop = tab;
1095			w.reshape(w.r, 1);
1096		}
1097	}else
1098		warning(nil, sys->sprint("%s: Tab %d\n", w.body.file.name, w.body.tabstop));
1099}
1100
1101alphabet(et: ref Text, argt: ref Text, arg: string, narg: int)
1102{
1103	r: string;
1104	leng: int;
1105
1106	if(et == nil)
1107		return;
1108	(nil, r, leng) = getarg(argt, FALSE, FALSE);
1109	if(r != nil)
1110		setalphabet(r[0:leng]);
1111	else
1112		setalphabet(arg[0:narg]);
1113}
1114
1115runfeed(p : array of ref Sys->FD, c : chan of int)
1116{
1117	n : int;
1118	buf : array of byte;
1119	s : string;
1120
1121	sys->pctl(Sys->FORKFD, nil);
1122	c <-= 1;
1123	# p[1] = nil;
1124	buf = array[256] of byte;
1125	for(;;){
1126		if((n = sys->read(p[0], buf, 256)) <= 0)
1127			break;
1128		s = string buf[0:n];
1129		dat->cerr <-= s;
1130		s = nil;
1131	}
1132	buf = nil;
1133	exit;
1134}
1135
1136run(win : ref Window, s : string, rdir : string, ndir : int, newns : int, argaddr : string, arg : string, iseditcmd: int)
1137{
1138	c : ref Dat->Command;
1139	name, dir : string;
1140	e, t : int;
1141	av : list of string;
1142	r : int;
1143	incl : array of string;
1144	inarg, i, nincl : int;
1145	tfd : ref Sys->FD;
1146	p : array of ref Sys->FD;
1147	pc : chan of int;
1148	winid : int;
1149
1150	c = ref Dat->Command;
1151	t = 0;
1152	while(t < len s && (s[t]==' ' || s[t]=='\n' || s[t]=='\t'))
1153		t++;
1154	for(e=t; e < len s; e++)
1155		if(s[e]==' ' || s[e]=='\n' || s[e]=='\t' )
1156			break;
1157	name = s[t:e];
1158	e = utils->strrchr(name, '/');
1159	if(e >= 0)
1160		name = name[e+1:];
1161	name += " ";	# add blank here for ease in waittask
1162	c.name = name;
1163	name = nil;
1164	pipechar := 0;
1165	if (t < len s && (s[t] == '<' || s[t] == '|' || s[t] == '>')){
1166		pipechar = s[t++];
1167		s = s[t:];
1168	}
1169	c.pid = sys->pctl(0, nil);
1170	c.iseditcmd = iseditcmd;
1171	c.text = s;
1172	dat->ccommand <-= c;
1173	#
1174	# must pctl() after communication because rendezvous name
1175	# space is part of RFNAMEG.
1176	#
1177
1178	if(newns){
1179		wids : string = "";
1180		filename: string;
1181
1182		if(win != nil){
1183			filename = win.body.file.name;
1184			wids = string win.id;
1185			nincl = win.nincl;
1186			incl = array[nincl] of string;
1187			for(i=0; i<nincl; i++)
1188				incl[i] = win.incl[i];
1189			winid = win.id;
1190			win.close();
1191		}else{
1192			winid = 0;
1193			nincl = 0;
1194			incl = nil;
1195			if(dat->activewin != nil)
1196				winid = (dat->activewin).id;
1197		}
1198		# sys->pctl(Sys->FORKNS|Sys->FORKFD|Sys->NEWPGRP, nil);
1199		sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->FORKENV|Sys->NEWPGRP, 0::1::2::fsys->fsyscfd()::nil);
1200		if(rdir != nil){
1201			dir = rdir[0:ndir];
1202			sys->chdir(dir);	# ignore error: probably app. window
1203			dir = nil;
1204		}
1205		if(filename != nil)
1206			utils->setenv("%", filename);
1207		c.md = fsys->fsysmount(rdir, ndir, incl, nincl);
1208		if(c.md == nil){
1209			# error("child: can't mount /mnt/acme");
1210			warning(nil, "can't mount /mnt/acme");
1211			exit;
1212		}
1213		if(winid > 0 && (pipechar=='|' || pipechar=='>')){
1214			buf := sys->sprint("/mnt/acme/%d/rdsel", winid);
1215			tfd = sys->open(buf, OREAD);
1216		}
1217		else
1218			tfd = sys->open("/dev/null", OREAD);
1219		sys->dup(tfd.fd, 0);
1220		tfd = nil;
1221		if((winid > 0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
1222			buf: string;
1223
1224			if(iseditcmd){
1225				if(winid > 0)
1226					buf = sprint("/mnt/acme/%d/editout", winid);
1227				else
1228 					buf = sprint("/mnt/acme/editout");
1229			}
1230			else
1231				buf = sys->sprint("/mnt/acme/%d/wrsel", winid);
1232			tfd = sys->open(buf, OWRITE);
1233		}
1234		else
1235			tfd = sys->open("/dev/cons", OWRITE);
1236		sys->dup(tfd.fd, 1);
1237		tfd = nil;
1238		if(winid > 0 && (pipechar=='|' || pipechar=='<')){
1239			tfd = sys->open("/dev/cons", OWRITE);
1240			sys->dup(tfd.fd, 2);
1241		}
1242		else
1243			sys->dup(1, 2);
1244		tfd = nil;
1245		utils->setenv("acmewin", wids);
1246	}else{
1247		if(win != nil)
1248			win.close();
1249		sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
1250		if(rdir != nil){
1251			dir = rdir[0:ndir];
1252			sys->chdir(dir);	# ignore error: probably app. window
1253			dir = nil;
1254		}
1255		p = array[2] of ref Sys->FD;
1256		if(sys->pipe(p) < 0){
1257			error("child: can't pipe");
1258			exit;
1259		}
1260		pc = chan of int;
1261		spawn runfeed(p, pc);
1262		<-pc;
1263		pc = nil;
1264		fsys->fsysclose();
1265		tfd = sys->open("/dev/null", OREAD);
1266		sys->dup(tfd.fd, 0);
1267		tfd = nil;
1268		sys->dup(p[1].fd, 1);
1269		sys->dup(1, 2);
1270		p[0] = p[1] = nil;
1271	}
1272
1273	if(argaddr != nil)
1274		utils->setenv("acmeaddr", argaddr);
1275	hard := 0;
1276	if(len s > 512-10)	# may need to print into stack
1277		hard = 1;
1278	else {
1279		inarg = FALSE;
1280		for(e=0; e < len s; e++){
1281			r = s[e];
1282			if(r==' ' || r=='\t')
1283				continue;
1284			if(r < ' ') {
1285				hard = 1;
1286				break;
1287			}
1288			if(utils->strchr("#;&|^$=`'{}()<>[]*?^~`", r) >= 0) {
1289				hard = 1;
1290				break;
1291			}
1292			inarg = TRUE;
1293		}
1294		if (!hard) {
1295			if(!inarg)
1296				exit;
1297			av = nil;
1298			sa := -1;
1299			for(e=0; e < len s; e++){
1300				r = s[e];
1301				if(r==' ' || r=='\t'){
1302					if (sa >= 0) {
1303						av = s[sa:e] :: av;
1304						sa = -1;
1305					}
1306					continue;
1307				}
1308				if (sa < 0)
1309					sa = e;
1310			}
1311			if (sa >= 0)
1312				av = s[sa:e] :: av;
1313			if (arg != nil)
1314				av = arg :: av;
1315			av = utils->reverse(av);
1316			c.av = av;
1317			exec(hd av, av);
1318			dat->cwait <-= string c.pid + " \"Exec\":";
1319			exit;
1320		}
1321	}
1322
1323	if(arg != nil){
1324		s = sprint("%s '%s'", s, arg);	# BUG: what if quote in arg?
1325		c.text = s;
1326	}
1327	av = nil;
1328	av = s :: av;
1329	av = "-c" :: av;
1330	av = "/dis/sh" :: av;
1331	exec(hd av, av);
1332	dat->cwait <-= string c.pid + " \"Exec\":";
1333	exit;
1334}
1335
1336# Nasty bug causes
1337# Edit ,|nonexistentcommand
1338# (or ,> or ,<) to lock up acme.  Easy fix.  Add these two lines
1339# to the failure case of runwaittask():
1340#
1341# /sys/src/cmd/acme/exec.c:1287 a exec.c:1288,1289
1342# else{
1343# if(c->iseditcmd)
1344# sendul(cedit, 0);
1345# free(c->name);
1346# free(c->text);
1347# free(c);
1348# }
1349
1350
1351