Blob


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"
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 };
55 Cmd *parsecmd(int);
56 Addr *compoundaddr(void);
57 Addr *simpleaddr(void);
58 void freecmd(void);
59 void okdelim(int);
61 Rune *cmdstartp;
62 Rune *cmdendp;
63 Rune *cmdp;
64 Channel *editerrc;
66 String *lastpat;
67 int patset;
69 List cmdlist;
70 List addrlist;
71 List stringlist;
72 Text *curtext;
73 int editing = Inactive;
75 String* newstring(int);
77 void
78 editthread(void *v)
79 {
80 Cmd *cmdp;
82 USED(v);
83 threadsetname("editthread");
84 while((cmdp=parsecmd(0)) != 0){
85 if(cmdexec(curtext, cmdp) == 0)
86 break;
87 freecmd();
88 }
89 sendp(editerrc, nil);
90 }
92 void
93 allelogterm(Window *w, void *x)
94 {
95 USED(x);
96 elogterm(w->body.file);
97 }
99 void
100 alleditinit(Window *w, void *x)
102 USED(x);
103 textcommit(&w->tag, TRUE);
104 textcommit(&w->body, TRUE);
105 w->body.file->editclean = FALSE;
108 void
109 allupdate(Window *w, void *x)
111 Text *t;
112 int i;
113 File *f;
115 USED(x);
116 t = &w->body;
117 f = t->file;
118 if(f->curtext != t) /* do curtext only */
119 return;
120 if(f->elog.type == Null)
121 elogterm(f);
122 else if(f->elog.type != Empty){
123 elogapply(f);
124 if(f->editclean){
125 f->mod = FALSE;
126 for(i=0; i<f->ntext; i++)
127 f->text[i]->w->dirty = FALSE;
130 textsetselect(t, t->q0, t->q1);
131 textscrdraw(t);
132 winsettag(w);
135 void
136 editerror(char *fmt, ...)
138 va_list arg;
139 char *s;
141 va_start(arg, fmt);
142 s = vsmprint(fmt, arg);
143 va_end(arg);
144 freecmd();
145 allwindows(allelogterm, nil); /* truncate the edit logs */
146 sendp(editerrc, s);
147 threadexits(nil);
150 void
151 editcmd(Text *ct, Rune *r, uint n)
153 char *err;
155 if(n == 0)
156 return;
157 if(2*n > RBUFSIZE){
158 warning(nil, "string too long\n");
159 return;
162 allwindows(alleditinit, nil);
163 if(cmdstartp)
164 free(cmdstartp);
165 cmdstartp = runemalloc(n+2);
166 runemove(cmdstartp, r, n);
167 if(r[n-1] != '\n')
168 cmdstartp[n++] = '\n';
169 cmdstartp[n] = '\0';
170 cmdendp = cmdstartp+n;
171 cmdp = cmdstartp;
172 if(ct->w == nil)
173 curtext = nil;
174 else
175 curtext = &ct->w->body;
176 resetxec();
177 if(editerrc == nil){
178 editerrc = chancreate(sizeof(char*), 0);
179 chansetname(editerrc, "editerrc");
180 lastpat = allocstring(0);
182 threadcreate(editthread, nil, STACK);
183 err = recvp(editerrc);
184 editing = Inactive;
185 if(err != nil){
186 if(err[0] != '\0')
187 warning(nil, "Edit: %s\n", err);
188 free(err);
191 /* update everyone whose edit log has data */
192 allwindows(allupdate, nil);
195 int
196 getch(void)
198 if(cmdp == cmdendp)
199 return -1;
200 return *cmdp++;
203 int
204 nextc(void)
206 if(cmdp == cmdendp)
207 return -1;
208 return *cmdp;
211 void
212 ungetch(void)
214 if(--cmdp < cmdstartp)
215 error("ungetch");
218 long
219 getnum(int signok)
221 long n;
222 int c, sign;
224 n = 0;
225 sign = 1;
226 if(signok>1 && nextc()=='-'){
227 sign = -1;
228 getch();
230 if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */
231 return sign;
232 while('0'<=(c=getch()) && c<='9')
233 n = n*10 + (c-'0');
234 ungetch();
235 return sign*n;
238 int
239 cmdskipbl(void)
241 int c;
242 do
243 c = getch();
244 while(c==' ' || c=='\t');
245 if(c >= 0)
246 ungetch();
247 return c;
250 /*
251 * Check that list has room for one more element.
252 */
253 void
254 growlist(List *l)
256 if(l->u.listptr==0 || l->nalloc==0){
257 l->nalloc = INCR;
258 l->u.listptr = emalloc(INCR*sizeof(void*));
259 l->nused = 0;
260 }else if(l->nused == l->nalloc){
261 l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(void*));
262 memset(l->u.ptr+l->nalloc, 0, INCR*sizeof(void*));
263 l->nalloc += INCR;
267 /*
268 * Remove the ith element from the list
269 */
270 void
271 dellist(List *l, int i)
273 memmove(&l->u.ptr[i], &l->u.ptr[i+1], (l->nused-(i+1))*sizeof(void*));
274 l->nused--;
277 /*
278 * Add a new element, whose position is i, to the list
279 */
280 void
281 inslist(List *l, int i, void *v)
283 growlist(l);
284 memmove(&l->u.ptr[i+1], &l->u.ptr[i], (l->nused-i)*sizeof(void*));
285 l->u.ptr[i] = v;
286 l->nused++;
289 void
290 listfree(List *l)
292 free(l->u.listptr);
293 free(l);
296 String*
297 allocstring(int n)
299 String *s;
301 s = emalloc(sizeof(String));
302 s->n = n;
303 s->nalloc = n+10;
304 s->r = emalloc(s->nalloc*sizeof(Rune));
305 s->r[n] = '\0';
306 return s;
309 void
310 freestring(String *s)
312 free(s->r);
313 free(s);
316 Cmd*
317 newcmd(void){
318 Cmd *p;
320 p = emalloc(sizeof(Cmd));
321 inslist(&cmdlist, cmdlist.nused, p);
322 return p;
325 String*
326 newstring(int n)
328 String *p;
330 p = allocstring(n);
331 inslist(&stringlist, stringlist.nused, p);
332 return p;
335 Addr*
336 newaddr(void)
338 Addr *p;
340 p = emalloc(sizeof(Addr));
341 inslist(&addrlist, addrlist.nused, p);
342 return p;
345 void
346 freecmd(void)
348 int i;
350 while(cmdlist.nused > 0)
351 free(cmdlist.u.ucharptr[--cmdlist.nused]);
352 while(addrlist.nused > 0)
353 free(addrlist.u.ucharptr[--addrlist.nused]);
354 while(stringlist.nused>0){
355 i = --stringlist.nused;
356 freestring(stringlist.u.stringptr[i]);
360 void
361 okdelim(int c)
363 if(c=='\\' || ('a'<=c && c<='z')
364 || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
365 editerror("bad delimiter %c\n", c);
368 void
369 atnl(void)
371 int c;
373 cmdskipbl();
374 c = getch();
375 if(c != '\n')
376 editerror("newline expected (saw %C)", c);
379 void
380 Straddc(String *s, int c)
382 if(s->n+1 >= s->nalloc){
383 s->nalloc += 10;
384 s->r = erealloc(s->r, s->nalloc*sizeof(Rune));
386 s->r[s->n++] = c;
387 s->r[s->n] = '\0';
390 void
391 getrhs(String *s, int delim, int cmd)
393 int c;
395 while((c = getch())>0 && c!=delim && c!='\n'){
396 if(c == '\\'){
397 if((c=getch()) <= 0)
398 error("bad right hand side");
399 if(c == '\n'){
400 ungetch();
401 c='\\';
402 }else if(c == 'n')
403 c='\n';
404 else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */
405 Straddc(s, '\\');
407 Straddc(s, c);
409 ungetch(); /* let client read whether delimiter, '\n' or whatever */
412 String *
413 collecttoken(char *end)
415 String *s = newstring(0);
416 int c;
418 while((c=nextc())==' ' || c=='\t')
419 Straddc(s, getch()); /* blanks significant for getname() */
420 while((c=getch())>0 && utfrune(end, c)==0)
421 Straddc(s, c);
422 if(c != '\n')
423 atnl();
424 return s;
427 String *
428 collecttext(void)
430 String *s;
431 int begline, i, c, delim;
433 s = newstring(0);
434 if(cmdskipbl()=='\n'){
435 getch();
436 i = 0;
437 do{
438 begline = i;
439 while((c = getch())>0 && c!='\n')
440 i++, Straddc(s, c);
441 i++, Straddc(s, '\n');
442 if(c < 0)
443 goto Return;
444 }while(s->r[begline]!='.' || s->r[begline+1]!='\n');
445 s->r[s->n-2] = '\0';
446 s->n -= 2;
447 }else{
448 okdelim(delim = getch());
449 getrhs(s, delim, 'a');
450 if(nextc()==delim)
451 getch();
452 atnl();
454 Return:
455 return s;
458 int
459 cmdlookup(int c)
461 int i;
463 for(i=0; cmdtab[i].cmdc; i++)
464 if(cmdtab[i].cmdc == c)
465 return i;
466 return -1;
469 Cmd*
470 parsecmd(int nest)
472 int i, c;
473 struct cmdtab *ct;
474 Cmd *cp, *ncp;
475 Cmd cmd;
477 cmd.next = cmd.u.cmd = 0;
478 cmd.re = 0;
479 cmd.flag = cmd.num = 0;
480 cmd.addr = compoundaddr();
481 if(cmdskipbl() == -1)
482 return 0;
483 if((c=getch())==-1)
484 return 0;
485 cmd.cmdc = c;
486 if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */
487 getch(); /* the 'd' */
488 cmd.cmdc='c'|0x100;
490 i = cmdlookup(cmd.cmdc);
491 if(i >= 0){
492 if(cmd.cmdc == '\n')
493 goto Return; /* let nl_cmd work it all out */
494 ct = &cmdtab[i];
495 if(ct->defaddr==aNo && cmd.addr)
496 editerror("command takes no address");
497 if(ct->count)
498 cmd.num = getnum(ct->count);
499 if(ct->regexp){
500 /* x without pattern -> .*\n, indicated by cmd.re==0 */
501 /* X without pattern is all files */
502 if((ct->cmdc!='x' && ct->cmdc!='X') ||
503 ((c = nextc())!=' ' && c!='\t' && c!='\n')){
504 cmdskipbl();
505 if((c = getch())=='\n' || c<0)
506 editerror("no address");
507 okdelim(c);
508 cmd.re = getregexp(c);
509 if(ct->cmdc == 's'){
510 cmd.u.text = newstring(0);
511 getrhs(cmd.u.text, c, 's');
512 if(nextc() == c){
513 getch();
514 if(nextc() == 'g')
515 cmd.flag = getch();
521 if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0)
522 editerror("bad address");
523 if(ct->defcmd){
524 if(cmdskipbl() == '\n'){
525 getch();
526 cmd.u.cmd = newcmd();
527 cmd.u.cmd->cmdc = ct->defcmd;
528 }else if((cmd.u.cmd = parsecmd(nest))==0)
529 error("defcmd");
530 }else if(ct->text)
531 cmd.u.text = collecttext();
532 else if(ct->token)
533 cmd.u.text = collecttoken(ct->token);
534 else
535 atnl();
536 }else
537 switch(cmd.cmdc){
538 case '{':
539 cp = 0;
540 do{
541 if(cmdskipbl()=='\n')
542 getch();
543 ncp = parsecmd(nest+1);
544 if(cp)
545 cp->next = ncp;
546 else
547 cmd.u.cmd = ncp;
548 }while(cp = ncp);
549 break;
550 case '}':
551 atnl();
552 if(nest==0)
553 editerror("right brace with no left brace");
554 return 0;
555 default:
556 editerror("unknown command %c", cmd.cmdc);
558 Return:
559 cp = newcmd();
560 *cp = cmd;
561 return cp;
564 String*
565 getregexp(int delim)
567 String *buf, *r;
568 int i, c;
570 buf = allocstring(0);
571 for(i=0; ; i++){
572 if((c = getch())=='\\'){
573 if(nextc()==delim)
574 c = getch();
575 else if(nextc()=='\\'){
576 Straddc(buf, c);
577 c = getch();
579 }else if(c==delim || c=='\n')
580 break;
581 if(i >= RBUFSIZE)
582 editerror("regular expression too long");
583 Straddc(buf, c);
585 if(c!=delim && c)
586 ungetch();
587 if(buf->n > 0){
588 patset = TRUE;
589 freestring(lastpat);
590 lastpat = buf;
591 }else
592 freestring(buf);
593 if(lastpat->n == 0)
594 editerror("no regular expression defined");
595 r = newstring(lastpat->n);
596 runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */
597 return r;
600 Addr *
601 simpleaddr(void)
603 Addr addr;
604 Addr *ap, *nap;
606 addr.num = 0;
607 addr.next = 0;
608 addr.u.left = 0;
609 switch(cmdskipbl()){
610 case '#':
611 addr.type = getch();
612 addr.num = getnum(1);
613 break;
614 case '0': case '1': case '2': case '3': case '4':
615 case '5': case '6': case '7': case '8': case '9':
616 addr.num = getnum(1);
617 addr.type='l';
618 break;
619 case '/': case '?': case '"':
620 addr.u.re = getregexp(addr.type = getch());
621 break;
622 case '.':
623 case '$':
624 case '+':
625 case '-':
626 case '\'':
627 addr.type = getch();
628 break;
629 default:
630 return 0;
632 if(addr.next = simpleaddr())
633 switch(addr.next->type){
634 case '.':
635 case '$':
636 case '\'':
637 if(addr.type!='"')
638 case '"':
639 editerror("bad address syntax");
640 break;
641 case 'l':
642 case '#':
643 if(addr.type=='"')
644 break;
645 /* fall through */
646 case '/':
647 case '?':
648 if(addr.type!='+' && addr.type!='-'){
649 /* insert the missing '+' */
650 nap = newaddr();
651 nap->type='+';
652 nap->next = addr.next;
653 addr.next = nap;
655 break;
656 case '+':
657 case '-':
658 break;
659 default:
660 error("simpleaddr");
662 ap = newaddr();
663 *ap = addr;
664 return ap;
667 Addr *
668 compoundaddr(void)
670 Addr addr;
671 Addr *ap, *next;
673 addr.u.left = simpleaddr();
674 if((addr.type = cmdskipbl())!=',' && addr.type!=';')
675 return addr.u.left;
676 getch();
677 next = addr.next = compoundaddr();
678 if(next && (next->type==',' || next->type==';') && next->u.left==0)
679 editerror("bad address syntax");
680 ap = newaddr();
681 *ap = addr;
682 return ap;