xref: /plan9-contrib/sys/src/cmd/acme/edit.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
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
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
94 allelogterm(Window *w, void*)
95 {
96 	elogterm(w->body.file);
97 }
98 
99 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
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
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
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
193 getch(void)
194 {
195 	if(*cmdp == *cmdendp)
196 		return -1;
197 	return *cmdp++;
198 }
199 
200 int
201 nextc(void)
202 {
203 	if(*cmdp == *cmdendp)
204 		return -1;
205 	return *cmdp;
206 }
207 
208 void
209 ungetch(void)
210 {
211 	if(--cmdp < cmdstartp)
212 		error("ungetch");
213 }
214 
215 long
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
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
251 growlist(List *l)
252 {
253 	if(l->listptr==0 || l->nalloc==0){
254 		l->nalloc = INCR;
255 		l->listptr = emalloc(INCR*sizeof(long));
256 		l->nused = 0;
257 	}else if(l->nused == l->nalloc){
258 		l->listptr = erealloc(l->listptr, (l->nalloc+INCR)*sizeof(long));
259 		memset((void*)(l->longptr+l->nalloc), 0, INCR*sizeof(long));
260 		l->nalloc += INCR;
261 	}
262 }
263 
264 /*
265  * Remove the ith element from the list
266  */
267 void
268 dellist(List *l, int i)
269 {
270 	memmove(&l->longptr[i], &l->longptr[i+1], (l->nused-(i+1))*sizeof(long));
271 	l->nused--;
272 }
273 
274 /*
275  * Add a new element, whose position is i, to the list
276  */
277 void
278 inslist(List *l, int i, long val)
279 {
280 	growlist(l);
281 	memmove(&l->longptr[i+1], &l->longptr[i], (l->nused-i)*sizeof(long));
282 	l->longptr[i] = val;
283 	l->nused++;
284 }
285 
286 void
287 listfree(List *l)
288 {
289 	free(l->listptr);
290 	free(l);
291 }
292 
293 String*
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
307 freestring(String *s)
308 {
309 	free(s->r);
310 	free(s);
311 }
312 
313 Cmd*
314 newcmd(void){
315 	Cmd *p;
316 
317 	p = emalloc(sizeof(Cmd));
318 	inslist(&cmdlist, cmdlist.nused, (long)p);
319 	return p;
320 }
321 
322 String*
323 newstring(int n)
324 {
325 	String *p;
326 
327 	p = allocstring(n);
328 	inslist(&stringlist, stringlist.nused, (long)p);
329 	return p;
330 }
331 
332 Addr*
333 newaddr(void)
334 {
335 	Addr *p;
336 
337 	p = emalloc(sizeof(Addr));
338 	inslist(&addrlist, addrlist.nused, (long)p);
339 	return p;
340 }
341 
342 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
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
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
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
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 *
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 *
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 	}else{
444 		okdelim(delim = getch());
445 		getrhs(s, delim, 'a');
446 		if(nextc()==delim)
447 			getch();
448 		atnl();
449 	}
450     Return:
451 	return s;
452 }
453 
454 int
455 cmdlookup(int c)
456 {
457 	int i;
458 
459 	for(i=0; cmdtab[i].cmdc; i++)
460 		if(cmdtab[i].cmdc == c)
461 			return i;
462 	return -1;
463 }
464 
465 Cmd*
466 parsecmd(int nest)
467 {
468 	int i, c;
469 	struct cmdtab *ct;
470 	Cmd *cp, *ncp;
471 	Cmd cmd;
472 
473 	cmd.next = cmd.cmd = 0;
474 	cmd.re = 0;
475 	cmd.flag = cmd.num = 0;
476 	cmd.addr = compoundaddr();
477 	if(cmdskipbl() == -1)
478 		return 0;
479 	if((c=getch())==-1)
480 		return 0;
481 	cmd.cmdc = c;
482 	if(cmd.cmdc=='c' && nextc()=='d'){	/* sleazy two-character case */
483 		getch();		/* the 'd' */
484 		cmd.cmdc='c'|0x100;
485 	}
486 	i = cmdlookup(cmd.cmdc);
487 	if(i >= 0){
488 		if(cmd.cmdc == '\n')
489 			goto Return;	/* let nl_cmd work it all out */
490 		ct = &cmdtab[i];
491 		if(ct->defaddr==aNo && cmd.addr)
492 			editerror("command takes no address");
493 		if(ct->count)
494 			cmd.num = getnum(ct->count);
495 		if(ct->regexp){
496 			/* x without pattern -> .*\n, indicated by cmd.re==0 */
497 			/* X without pattern is all files */
498 			if((ct->cmdc!='x' && ct->cmdc!='X') ||
499 			   ((c = nextc())!=' ' && c!='\t' && c!='\n')){
500 				cmdskipbl();
501 				if((c = getch())=='\n' || c<0)
502 					editerror("no address");
503 				okdelim(c);
504 				cmd.re = getregexp(c);
505 				if(ct->cmdc == 's'){
506 					cmd.text = newstring(0);
507 					getrhs(cmd.text, c, 's');
508 					if(nextc() == c){
509 						getch();
510 						if(nextc() == 'g')
511 							cmd.flag = getch();
512 					}
513 
514 				}
515 			}
516 		}
517 		if(ct->addr && (cmd.mtaddr=simpleaddr())==0)
518 			editerror("bad address");
519 		if(ct->defcmd){
520 			if(cmdskipbl() == '\n'){
521 				getch();
522 				cmd.cmd = newcmd();
523 				cmd.cmd->cmdc = ct->defcmd;
524 			}else if((cmd.cmd = parsecmd(nest))==0)
525 				error("defcmd");
526 		}else if(ct->text)
527 			cmd.text = collecttext();
528 		else if(ct->token)
529 			cmd.text = collecttoken(ct->token);
530 		else
531 			atnl();
532 	}else
533 		switch(cmd.cmdc){
534 		case '{':
535 			cp = 0;
536 			do{
537 				if(cmdskipbl()=='\n')
538 					getch();
539 				ncp = parsecmd(nest+1);
540 				if(cp)
541 					cp->next = ncp;
542 				else
543 					cmd.cmd = ncp;
544 			}while(cp = ncp);
545 			break;
546 		case '}':
547 			atnl();
548 			if(nest==0)
549 				editerror("right brace with no left brace");
550 			return 0;
551 		default:
552 			editerror("unknown command %c", cmd.cmdc);
553 		}
554     Return:
555 	cp = newcmd();
556 	*cp = cmd;
557 	return cp;
558 }
559 
560 String*
561 getregexp(int delim)
562 {
563 	String *buf, *r;
564 	int i, c;
565 
566 	buf = allocstring(0);
567 	for(i=0; ; i++){
568 		if((c = getch())=='\\'){
569 			if(nextc()==delim)
570 				c = getch();
571 			else if(nextc()=='\\'){
572 				Straddc(buf, c);
573 				c = getch();
574 			}
575 		}else if(c==delim || c=='\n')
576 			break;
577 		if(i >= RBUFSIZE)
578 			editerror("regular expression too long");
579 		Straddc(buf, c);
580 	}
581 	if(c!=delim && c)
582 		ungetch();
583 	if(buf->n > 0){
584 		patset = TRUE;
585 		freestring(lastpat);
586 		lastpat = buf;
587 	}else
588 		freestring(buf);
589 	if(lastpat->n == 0)
590 		editerror("no regular expression defined");
591 	r = newstring(lastpat->n);
592 	runemove(r->r, lastpat->r, lastpat->n);	/* newstring put \0 at end */
593 	return r;
594 }
595 
596 Addr *
597 simpleaddr(void)
598 {
599 	Addr addr;
600 	Addr *ap, *nap;
601 
602 	addr.next = 0;
603 	addr.left = 0;
604 	switch(cmdskipbl()){
605 	case '#':
606 		addr.type = getch();
607 		addr.num = getnum(1);
608 		break;
609 	case '0': case '1': case '2': case '3': case '4':
610 	case '5': case '6': case '7': case '8': case '9':
611 		addr.num = getnum(1);
612 		addr.type='l';
613 		break;
614 	case '/': case '?': case '"':
615 		addr.re = getregexp(addr.type = getch());
616 		break;
617 	case '.':
618 	case '$':
619 	case '+':
620 	case '-':
621 	case '\'':
622 		addr.type = getch();
623 		break;
624 	default:
625 		return 0;
626 	}
627 	if(addr.next = simpleaddr())
628 		switch(addr.next->type){
629 		case '.':
630 		case '$':
631 		case '\'':
632 			if(addr.type!='"')
633 		case '"':
634 				editerror("bad address syntax");
635 			break;
636 		case 'l':
637 		case '#':
638 			if(addr.type=='"')
639 				break;
640 			/* fall through */
641 		case '/':
642 		case '?':
643 			if(addr.type!='+' && addr.type!='-'){
644 				/* insert the missing '+' */
645 				nap = newaddr();
646 				nap->type='+';
647 				nap->next = addr.next;
648 				addr.next = nap;
649 			}
650 			break;
651 		case '+':
652 		case '-':
653 			break;
654 		default:
655 			error("simpleaddr");
656 		}
657 	ap = newaddr();
658 	*ap = addr;
659 	return ap;
660 }
661 
662 Addr *
663 compoundaddr(void)
664 {
665 	Addr addr;
666 	Addr *ap, *next;
667 
668 	addr.left = simpleaddr();
669 	if((addr.type = cmdskipbl())!=',' && addr.type!=';')
670 		return addr.left;
671 	getch();
672 	next = addr.next = compoundaddr();
673 	if(next && (next->type==',' || next->type==';') && next->left==0)
674 		editerror("bad address syntax");
675 	ap = newaddr();
676 	*ap = addr;
677 	return ap;
678 }
679