xref: /inferno-os/appl/acme/edit.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Edit;
2
3include "common.m";
4
5sys: Sys;
6dat: Dat;
7utils: Utils;
8textm: Textm;
9windowm: Windowm;
10rowm: Rowm;
11scroll: Scroll;
12editlog: Editlog;
13editcomd: Editcmd;
14
15sprint, print: import sys;
16FALSE, TRUE, BUFSIZE, Null, Empty, Inactive: import Dat;
17warning, error, strchr: import utils;
18Text: import textm;
19File: import Filem;
20Window: import windowm;
21allwindows: import rowm;
22scrdraw: import scroll;
23elogterm, elogapply: import editlog;
24cmdexec, resetxec: import editcomd;
25
26init(mods : ref Dat->Mods)
27{
28	sys = mods.sys;
29	dat = mods.dat;
30	utils = mods.utils;
31	textm = mods.textm;
32	windowm = mods.windowm;
33	rowm = mods.rowm;
34	scroll = mods.scroll;
35	editlog = mods.editlog;
36	editcomd = mods.editcmd;
37	editing = Inactive;
38}
39
40linex: con "\n";
41wordx: con "\t\n";
42
43cmdtab = array[28] of {
44#		cmdc	text	regexp	addr	defcmd	defaddr	count	token	 fn
45	Cmdt ( '\n',	0,	0,	0,	0,	aDot,	0,	nil,		C_nl ),
46	Cmdt ( 'a',		1,	0,	0,	0,	aDot,	0,	nil,		C_a ),
47	Cmdt ( 'b',		0,	0,	0,	0,	aNo,		0,	linex,	C_b ),
48	Cmdt ( 'c',		1,	0,	0,	0,	aDot,	0,	nil,		C_c ),
49	Cmdt ( 'd',		0,	0,	0,	0,	aDot,	0,	nil,		C_d ),
50	Cmdt ( 'e',		0,	0,	0,	0,	aNo,		0,	wordx,	C_e ),
51	Cmdt ( 'f',		0,	0,	0,	0,	aNo,		0,	wordx,	C_f ),
52	Cmdt ( 'g',		0,	1,	0,	'p',	aDot,	0,	nil,		C_g ),
53	Cmdt ( 'i',		1,	0,	0,	0,	aDot,	0,	nil,		C_i ),
54	Cmdt ( 'm',	0,	0,	1,	0,	aDot,	0,	nil,		C_m ),
55	Cmdt ( 'p',		0,	0,	0,	0,	aDot,	0,	nil,		C_p ),
56	Cmdt ( 'r',		0,	0,	0,	0,	aDot,	0,	wordx,	C_e ),
57	Cmdt ( 's',		0,	1,	0,	0,	aDot,	1,	nil,		C_s ),
58	Cmdt ( 't',		0,	0,	1,	0,	aDot,	0,	nil,		C_m ),
59	Cmdt ( 'u',		0,	0,	0,	0,	aNo,		2,	nil,		C_u ),
60	Cmdt ( 'v',		0,	1,	0,	'p',	aDot,	0,	nil,		C_g ),
61	Cmdt ( 'w',	0,	0,	0,	0,	aAll,		0,	wordx,	C_w ),
62	Cmdt ( 'x',		0,	1,	0,	'p',	aDot,	0,	nil,		C_x ),
63	Cmdt ( 'y',		0,	1,	0,	'p',	aDot,	0,	nil,		C_x ),
64	Cmdt ( '=',		0,	0,	0,	0,	aDot,	0,	linex,	C_eq ),
65	Cmdt ( 'B',		0,	0,	0,	0,	aNo,		0,	linex,	C_B ),
66	Cmdt ( 'D',	0,	0,	0,	0,	aNo,		0,	linex,	C_D ),
67	Cmdt ( 'X',		0,	1,	0,	'f',	aNo,		0,	nil,		C_X ),
68	Cmdt ( 'Y',		0,	1,	0,	'f',	aNo,		0,	nil,		C_X ),
69	Cmdt ( '<',		0,	0,	0,	0,	aDot,	0,	linex,	C_pipe ),
70	Cmdt ( '|',		0,	0,	0,	0,	aDot,	0,	linex,	C_pipe ),
71	Cmdt ( '>',		0,	0,	0,	0,	aDot,	0,	linex,	C_pipe ),
72	# deliberately unimplemented
73	# Cmdt ( 'k',	0,	0,	0,	0,	aDot,	0,	nil,		C_k ),
74	# Cmdt ( 'n',	0,	0,	0,	0,	aNo,		0,	nil,		C_n ),
75	# Cmdt ( 'q',	0,	0,	0,	0,	aNo,		0,	nil,		C_q ),
76	# Cmdt ( '!',	0,	0,	0,	0,	aNo,		0,	linex,	C_plan9 ),
77	Cmdt (0,		0,	0,	0,	0,	0,		0,	nil,		-1 )
78};
79
80cmdstartp: string;
81cmdendp: int;
82cmdp: int;
83editerrc: chan of string;
84
85lastpat : ref String;
86patset: int;
87
88# cmdlist: ref List;
89# addrlist: ref List;
90# stringlist: ref List;
91
92editwaitproc(pid : int, sync: chan of int)
93{
94	fd : ref Sys->FD;
95	n : int;
96
97	sys->pctl(Sys->FORKFD, nil);
98	w := sprint("#p/%d/wait", pid);
99	fd = sys->open(w, Sys->OREAD);
100	if (fd == nil)
101		error("fd == nil in editwaitproc");
102	sync <-= sys->pctl(0, nil);
103	buf := array[Sys->WAITLEN] of byte;
104	status := "";
105	for(;;){
106		if ((n = sys->read(fd, buf, len buf))<0)
107			error("bad read in editwaitproc");
108		status = string buf[0:n];
109		dat->cwait <-= status;
110	}
111}
112
113editthread()
114{
115	cmdp: ref Cmd;
116
117	mypid := sys->pctl(0, nil);
118	sync := chan of int;
119	spawn editwaitproc(mypid, sync);
120	yourpid := <- sync;
121	while((cmdp=parsecmd(0)) != nil){
122#		ocurfile = curfile;
123#		loaded = curfile && !curfile->unread;
124		if(cmdexec(curtext, cmdp) == 0)
125			break;
126		freecmd();
127	}
128	editerrc <-= nil;
129	utils->postnote(Utils->PNPROC, mypid, yourpid, "kill");
130}
131
132allelogterm(w: ref Window)
133{
134	elogterm(w.body.file);
135}
136
137alleditinit(w: ref Window)
138{
139	w.tag.commit(TRUE);
140	w.body.commit(TRUE);
141	w.body.file.editclean = FALSE;
142}
143
144allupdate(w: ref Window)
145{
146	t: ref Text;
147	i: int;
148	f: ref File;
149
150	t = w.body;
151	f = t.file;
152	if(f.curtext != t)		# do curtext only
153		return;
154	if(f.elog.typex == Null)
155		elogterm(f);
156	else if(f.elog.typex != Empty){
157		elogapply(f);
158		if(f.editclean){
159			f.mod = FALSE;
160			for(i=0; i<f.ntext; i++)
161				f.text[i].w.dirty = FALSE;
162		}
163		t.setselect(t.q0, t.q1);
164		scrdraw(t);
165		w.settag();
166	}
167}
168
169editerror(s: string)
170{
171	# print("%s", s);
172	freecmd();
173	allwindows(ALLELOGTERM, nil);	# truncate the edit logs
174	editerrc <-= s;
175	exit;
176}
177
178editcmd(ct: ref Text, r: string, n: int)
179{
180	err: string;
181
182	if(n == 0)
183		return;
184	if(2*n > BUFSIZE){
185		warning(nil, "string too long\n");
186		return;
187	}
188
189	allwindows(ALLEDITINIT, nil);
190	cmdstartp = r[0:n];
191	if(r[n-1] != '\n')
192		cmdstartp[n++] = '\n';
193	cmdendp = n;
194	cmdp = 0;
195	if(ct.w == nil)
196		curtext = nil;
197	else
198		curtext = ct.w.body;
199	resetxec();
200	if(editerrc == nil){
201		editerrc = chan of string;
202		lastpat = allocstring(0);
203	}
204	spawn editthread();
205	err = <- editerrc;
206	editing = Inactive;
207	if(err != nil)
208		warning(nil, sprint("Edit: %s\n", err));
209
210	# update everyone whose edit log has data
211	allwindows(ALLUPDATE, nil);
212}
213
214getch(): int
215{
216	if(cmdp == cmdendp)
217		return -1;
218	return cmdstartp[cmdp++];
219}
220
221nextc(): int
222{
223	if(cmdp == cmdendp)
224		return -1;
225	return cmdstartp[cmdp];
226}
227
228ungetch()
229{
230	if(--cmdp < 0)
231		error("ungetch");
232}
233
234getnum(signok: int): int
235{
236	n: int;
237	c, sign: int;
238
239	n = 0;
240	sign = 1;
241	if(signok>1 && nextc()=='-'){
242		sign = -1;
243		getch();
244	}
245	if((c=nextc())<'0' || '9'<c)	# no number defaults to 1
246		return sign;
247	while('0'<=(c=getch()) && c<='9')
248		n = n*10 + (c-'0');
249	ungetch();
250	return sign*n;
251}
252
253cmdskipbl(): int
254{
255	c: int;
256	do
257		c = getch();
258	while(c==' ' || c=='\t');
259	if(c >= 0)
260		ungetch();
261	return c;
262}
263
264# Check that list has room for one more element.
265# growlist(l: ref List)
266# {
267#	if(l.elems == nil || l.nalloc==0){
268#		l.nalloc = INCR;
269#		l.elems = array[INCR] of Listelement;
270#		l.nused = 0;
271#	}else if(l.nused == l.nalloc){
272#		old := l.elems;
273#		l.elems = array[l.nalloc+INCR] of Listelement;
274#		l.elems[0:] = old[0:l.nalloc];
275#		l.nalloc += INCR;
276#	}
277# }
278
279# Remove the ith element from the list
280# dellist(l: ref List, i: int)
281# {
282#	l.elems[i:] = l.elems[i+1:l.nused];
283#	l.nused--;
284# }
285
286# Add a new element, whose position is i, to the list
287# inslist(l: ref List, i: int, val: int)
288# {
289#	growlist(l);
290#	l.elems[i+1:] = l.elems[i:l.nused];
291#	l.elems[i] = val;
292#	l.nused++;
293# }
294
295# listfree(l: ref List)
296# {
297#	l.elems = nil;
298# }
299
300allocstring(n: int): ref String
301{
302	s: ref String;
303
304	s = ref String;
305	s.n = n;
306	s.r = string array[s.n] of { * => byte '\0' };
307	return s;
308}
309
310freestring(s: ref String)
311{
312	s.r = nil;
313}
314
315newcmd(): ref Cmd
316{
317	p: ref Cmd;
318
319	p = ref Cmd;
320	# inslist(cmdlist, cmdlist.nused, p);
321	return p;
322}
323
324newstring(n: int): ref String
325{
326	p: ref String;
327
328	p = allocstring(n);
329	# inslist(stringlist, stringlist.nused, p);
330	return p;
331}
332
333newaddr(): ref Addr
334{
335	p: ref Addr;
336
337	p = ref Addr;
338	# inslist(addrlist, addrlist.nused, p);
339	return p;
340}
341
342freecmd()
343{
344	# i: int;
345
346	# cmdlist.elems = nil;
347	# addrlist.elems = nil;
348	# stringlist.elems = nil;
349	# cmdlist.nused = addrlist.nused = stringlist.nused = 0;
350}
351
352okdelim(c: int)
353{
354	if(c=='\\' || ('a'<=c && c<='z')
355	|| ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
356		editerror(sprint("bad delimiter %c\n", c));
357}
358
359atnl()
360{
361	c: int;
362
363	cmdskipbl();
364	c = getch();
365	if(c != '\n')
366		editerror(sprint("newline expected (saw %c)", c));
367}
368
369Straddc(s: ref String, c: int)
370{
371	s.r[s.n++] = c;
372}
373
374getrhs(s: ref String, delim: int, cmd: int)
375{
376	c: int;
377
378	while((c = getch())>0 && c!=delim && c!='\n'){
379		if(c == '\\'){
380			if((c=getch()) <= 0)
381				error("bad right hand side");
382			if(c == '\n'){
383				ungetch();
384				c='\\';
385			}else if(c == 'n')
386				c='\n';
387			else if(c!=delim && (cmd=='s' || c!='\\'))	# s does its own
388				Straddc(s, '\\');
389		}
390		Straddc(s, c);
391	}
392	ungetch();	# let client read whether delimiter, '\n' or whatever
393}
394
395collecttoken(end: string): ref String
396{
397	c: int;
398
399	s := newstring(0);
400
401	while((c=nextc())==' ' || c=='\t')
402		Straddc(s, getch()); # blanks significant for getname()
403	while((c=getch())>0 && strchr(end, c)<0)
404		Straddc(s, c);
405	if(c != '\n')
406		atnl();
407	return s;
408}
409
410collecttext(): ref String
411{
412	s: ref String;
413	begline, i, c, delim: int;
414
415	s = newstring(0);
416	if(cmdskipbl()=='\n'){
417		getch();
418		i = 0;
419		do{
420			begline = i;
421			while((c = getch())>0 && c!='\n'){
422				i++;
423				Straddc(s, c);
424			}
425			i++;
426			Straddc(s, '\n');
427			if(c < 0)
428				return s;
429		}while(s.r[begline]!='.' || s.r[begline+1]!='\n');
430		s.r[s.n-2] = '\0';
431	}else{
432		okdelim(delim = getch());
433		getrhs(s, delim, 'a');
434		if(nextc()==delim)
435			getch();
436		atnl();
437	}
438	return s;
439}
440
441cmdlookup(c: int): int
442{
443	i: int;
444
445	for(i=0; cmdtab[i].cmdc; i++)
446		if(cmdtab[i].cmdc == c)
447			return i;
448	return -1;
449}
450
451parsecmd(nest: int): ref Cmd
452{
453	i, c: int;
454	cp, ncp: ref Cmd;
455	cmd: ref Cmd;
456
457	cmd = ref Cmd;
458	cmd.next = cmd.cmd = nil;
459	cmd.re = nil;
460	cmd.flag = cmd.num = 0;
461	cmd.addr = compoundaddr();
462	if(cmdskipbl() == -1)
463		return nil;
464	if((c=getch())==-1)
465		return nil;
466	cmd.cmdc = c;
467	if(cmd.cmdc=='c' && nextc()=='d'){	# sleazy two-character case
468		getch();		# the 'd'
469		cmd.cmdc='c'|16r100;
470	}
471	i = cmdlookup(cmd.cmdc);
472	if(i >= 0){
473		if(cmd.cmdc == '\n'){
474			cp = newcmd();
475			*cp = *cmd;
476			return cp;
477			# let nl_cmd work it all out
478		}
479		ct := cmdtab[i];
480		if(ct.defaddr==aNo && cmd.addr != nil)
481			editerror("command takes no address");
482		if(ct.count)
483			cmd.num = getnum(ct.count);
484		if(ct.regexp){
485			# x without pattern -> .*\n, indicated by cmd.re==0
486			# X without pattern is all files
487			if((ct.cmdc!='x' && ct.cmdc!='X') ||
488			   ((c = nextc())!=' ' && c!='\t' && c!='\n')){
489				cmdskipbl();
490				if((c = getch())=='\n' || c<0)
491					editerror("no address");
492				okdelim(c);
493				cmd.re = getregexp(c);
494				if(ct.cmdc == 's'){
495					cmd.text = newstring(0);
496					getrhs(cmd.text, c, 's');
497					if(nextc() == c){
498						getch();
499						if(nextc() == 'g')
500							cmd.flag = getch();
501					}
502
503				}
504			}
505		}
506		if(ct.addr && (cmd.mtaddr=simpleaddr())==nil)
507			editerror("bad address");
508		if(ct.defcmd){
509			if(cmdskipbl() == '\n'){
510				getch();
511				cmd.cmd = newcmd();
512				cmd.cmd.cmdc = ct.defcmd;
513			}else if((cmd.cmd = parsecmd(nest))==nil)
514				error("defcmd");
515		}else if(ct.text)
516			cmd.text = collecttext();
517		else if(ct.token != nil)
518			cmd.text = collecttoken(ct.token);
519		else
520			atnl();
521	}else
522		case(cmd.cmdc){
523		'{' =>
524			cp = nil;
525			do{
526				if(cmdskipbl()=='\n')
527					getch();
528				ncp = parsecmd(nest+1);
529				if(cp != nil)
530					cp.next = ncp;
531				else
532					cmd.cmd = ncp;
533			}while((cp = ncp) != nil);
534			break;
535		'}' =>
536			atnl();
537			if(nest==0)
538				editerror("right brace with no left brace");
539			return nil;
540		'c'|16r100 =>
541			editerror("unimplemented command cd");
542		* =>
543			editerror(sprint("unknown command %c", cmd.cmdc));
544		}
545	cp = newcmd();
546	*cp = *cmd;
547	return cp;
548}
549
550getregexp(delim: int): ref String
551{
552	buf, r: ref String;
553	i, c: int;
554
555	buf = allocstring(0);
556	for(i=0; ; i++){
557		if((c = getch())=='\\'){
558			if(nextc()==delim)
559				c = getch();
560			else if(nextc()=='\\'){
561				Straddc(buf, c);
562				c = getch();
563			}
564		}else if(c==delim || c=='\n')
565			break;
566		if(i >= BUFSIZE)
567			editerror("regular expression too long");
568		Straddc(buf, c);
569	}
570	if(c!=delim && c)
571		ungetch();
572	if(buf.n > 0){
573		patset = TRUE;
574		freestring(lastpat);
575		lastpat = buf;
576	}else
577		freestring(buf);
578	if(lastpat.n == 0)
579		editerror("no regular expression defined");
580	r = newstring(lastpat.n);
581	k := lastpat.n;
582	for(j := 0; j < k; j++)
583		r.r[j] = lastpat.r[j];	# newstring put \0 at end
584	return r;
585}
586
587simpleaddr(): ref Addr
588{
589	addr: Addr;
590	ap, nap: ref Addr;
591
592	addr.next = nil;
593	addr.left = nil;
594	case(cmdskipbl()){
595	'#' =>
596		addr.typex = getch();
597		addr.num = getnum(1);
598		break;
599	'0' to '9' =>
600		addr.num = getnum(1);
601		addr.typex='l';
602		break;
603	'/' or '?' or '"' =>
604		addr.re = getregexp(addr.typex = getch());
605		break;
606	'.' or
607	'$' or
608	'+' or
609	'-' or
610	'\'' =>
611		addr.typex = getch();
612		break;
613	* =>
614		return nil;
615	}
616	if((addr.next = simpleaddr()) != nil)
617		case(addr.next.typex){
618		'.' or
619		'$' or
620		'\'' =>
621			if(addr.typex!='"')
622				editerror("bad address syntax");
623			break;
624		'"' =>
625			editerror("bad address syntax");
626			break;
627		'l' or
628		'#' =>
629			if(addr.typex=='"')
630				break;
631			if(addr.typex!='+' && addr.typex!='-'){
632				# insert the missing '+'
633				nap = newaddr();
634				nap.typex='+';
635				nap.next = addr.next;
636				addr.next = nap;
637			}
638			break;
639		'/' or
640		'?' =>
641			if(addr.typex!='+' && addr.typex!='-'){
642				# insert the missing '+'
643				nap = newaddr();
644				nap.typex='+';
645				nap.next = addr.next;
646				addr.next = nap;
647			}
648			break;
649		'+' or
650		'-' =>
651			break;
652		* =>
653			error("simpleaddr");
654		}
655	ap = newaddr();
656	*ap = addr;
657	return ap;
658}
659
660compoundaddr(): ref Addr
661{
662	addr: Addr;
663	ap, next: ref Addr;
664
665	addr.left = simpleaddr();
666	if((addr.typex = cmdskipbl())!=',' && addr.typex!=';')
667		return addr.left;
668	getch();
669	next = addr.next = compoundaddr();
670	if(next != nil && (next.typex==',' || next.typex==';') && next.left==nil)
671		editerror("bad address syntax");
672	ap = newaddr();
673	*ap = addr;
674	return ap;
675}
676
677