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 
16 static char	linex[]="\n";
17 static char	wordx[]=" \t\n";
18 struct 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 
56 Cmd	*parsecmd(int);
57 Addr	*compoundaddr(void);
58 Addr	*simpleaddr(void);
59 void	freecmd(void);
60 void	okdelim(int);
61 
62 Rune	*cmdstartp;
63 Rune *cmdendp;
64 Rune	*cmdp;
65 Channel	*editerrc;
66 
67 String	*lastpat;
68 int	patset;
69 
70 List	cmdlist;
71 List	addrlist;
72 List	stringlist;
73 Text	*curtext;
74 int	editing = Inactive;
75 
76 String*	newstring(int);
77 
78 void
editthread(void * v)79 editthread(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 
93 void
allelogterm(Window * w,void * x)94 allelogterm(Window *w, void *x)
95 {
96 	USED(x);
97 	elogterm(w->body.file);
98 }
99 
100 void
alleditinit(Window * w,void * x)101 alleditinit(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 
109 void
allupdate(Window * w,void * x)110 allupdate(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 
136 void
editerror(char * fmt,...)137 editerror(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 
151 void
editcmd(Text * ct,Rune * r,uint n)152 editcmd(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 
196 int
getch(void)197 getch(void)
198 {
199 	if(cmdp == cmdendp)
200 		return -1;
201 	return *cmdp++;
202 }
203 
204 int
nextc(void)205 nextc(void)
206 {
207 	if(cmdp == cmdendp)
208 		return -1;
209 	return *cmdp;
210 }
211 
212 void
ungetch(void)213 ungetch(void)
214 {
215 	if(--cmdp < cmdstartp)
216 		error("ungetch");
217 }
218 
219 long
getnum(int signok)220 getnum(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 
239 int
cmdskipbl(void)240 cmdskipbl(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  */
254 void
growlist(List * l)255 growlist(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  */
271 void
dellist(List * l,int i)272 dellist(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  */
281 void
inslist(List * l,int i,void * v)282 inslist(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 
290 void
listfree(List * l)291 listfree(List *l)
292 {
293 	free(l->u.listptr);
294 	free(l);
295 }
296 
297 String*
allocstring(int n)298 allocstring(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 
310 void
freestring(String * s)311 freestring(String *s)
312 {
313 	free(s->r);
314 	free(s);
315 }
316 
317 Cmd*
newcmd(void)318 newcmd(void){
319 	Cmd *p;
320 
321 	p = emalloc(sizeof(Cmd));
322 	inslist(&cmdlist, cmdlist.nused, p);
323 	return p;
324 }
325 
326 String*
newstring(int n)327 newstring(int n)
328 {
329 	String *p;
330 
331 	p = allocstring(n);
332 	inslist(&stringlist, stringlist.nused, p);
333 	return p;
334 }
335 
336 Addr*
newaddr(void)337 newaddr(void)
338 {
339 	Addr *p;
340 
341 	p = emalloc(sizeof(Addr));
342 	inslist(&addrlist, addrlist.nused, p);
343 	return p;
344 }
345 
346 void
freecmd(void)347 freecmd(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 
361 void
okdelim(int c)362 okdelim(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 
369 void
atnl(void)370 atnl(void)
371 {
372 	int c;
373 
374 	cmdskipbl();
375 	c = getch();
376 	if(c != '\n')
377 		editerror("newline expected (saw %C)", c);
378 }
379 
380 void
Straddc(String * s,int c)381 Straddc(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 
391 void
getrhs(String * s,int delim,int cmd)392 getrhs(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 
413 String *
collecttoken(char * end)414 collecttoken(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 
428 String *
collecttext(void)429 collecttext(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 
459 int
cmdlookup(int c)460 cmdlookup(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 
470 Cmd*
parsecmd(int nest)471 parsecmd(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 
565 String*
getregexp(int delim)566 getregexp(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 
601 Addr *
simpleaddr(void)602 simpleaddr(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 
668 Addr *
compoundaddr(void)669 compoundaddr(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 }
685