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