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