plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

edit.c (12156B)


      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
     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
     94 allelogterm(Window *w, void *x)
     95 {
     96 	USED(x);
     97 	elogterm(w->body.file);
     98 }
     99 
    100 void
    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
    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
    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
    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
    197 getch(void)
    198 {
    199 	if(cmdp == cmdendp)
    200 		return -1;
    201 	return *cmdp++;
    202 }
    203 
    204 int
    205 nextc(void)
    206 {
    207 	if(cmdp == cmdendp)
    208 		return -1;
    209 	return *cmdp;
    210 }
    211 
    212 void
    213 ungetch(void)
    214 {
    215 	if(--cmdp < cmdstartp)
    216 		error("ungetch");
    217 }
    218 
    219 long
    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
    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
    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
    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
    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
    291 listfree(List *l)
    292 {
    293 	free(l->u.listptr);
    294 	free(l);
    295 }
    296 
    297 String*
    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
    311 freestring(String *s)
    312 {
    313 	free(s->r);
    314 	free(s);
    315 }
    316 
    317 Cmd*
    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*
    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*
    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
    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
    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
    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
    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
    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 *
    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 *
    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
    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*
    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*
    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 *
    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 				break;
    640 			/* fall through */
    641 		case '"':
    642 			editerror("bad address syntax");
    643 			break;
    644 		case 'l':
    645 		case '#':
    646 			if(addr.type=='"')
    647 				break;
    648 			/* fall through */
    649 		case '/':
    650 		case '?':
    651 			if(addr.type!='+' && addr.type!='-'){
    652 				/* insert the missing '+' */
    653 				nap = newaddr();
    654 				nap->type='+';
    655 				nap->next = addr.next;
    656 				addr.next = nap;
    657 			}
    658 			break;
    659 		case '+':
    660 		case '-':
    661 			break;
    662 		default:
    663 			error("simpleaddr");
    664 		}
    665 	ap = newaddr();
    666 	*ap = addr;
    667 	return ap;
    668 }
    669 
    670 Addr *
    671 compoundaddr(void)
    672 {
    673 	Addr addr;
    674 	Addr *ap, *next;
    675 
    676 	addr.u.left = simpleaddr();
    677 	if((addr.type = cmdskipbl())!=',' && addr.type!=';')
    678 		return addr.u.left;
    679 	getch();
    680 	next = addr.next = compoundaddr();
    681 	if(next && (next->type==',' || next->type==';') && next->u.left==0)
    682 		editerror("bad address syntax");
    683 	ap = newaddr();
    684 	*ap = addr;
    685 	return ap;
    686 }