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 /* ocurfile = curfile; */
86 /* loaded = curfile && !curfile->unread; */
87 if(cmdexec(curtext, cmdp) == 0)
88 break;
89 freecmd();
90 }
91 sendp(editerrc, nil);
92 }
94 void
95 allelogterm(Window *w, void *x)
96 {
97 USED(x);
98 elogterm(w->body.file);
99 }
101 void
102 alleditinit(Window *w, void *x)
104 USED(x);
105 textcommit(&w->tag, TRUE);
106 textcommit(&w->body, TRUE);
107 w->body.file->editclean = FALSE;
110 void
111 allupdate(Window *w, void *x)
113 Text *t;
114 int i;
115 File *f;
117 USED(x);
118 t = &w->body;
119 f = t->file;
120 if(f->curtext != t) /* do curtext only */
121 return;
122 if(f->elog.type == Null)
123 elogterm(f);
124 else if(f->elog.type != Empty){
125 elogapply(f);
126 if(f->editclean){
127 f->mod = FALSE;
128 for(i=0; i<f->ntext; i++)
129 f->text[i]->w->dirty = FALSE;
132 textsetselect(t, t->q0, t->q1);
133 textscrdraw(t);
134 winsettag(w);
137 void
138 editerror(char *fmt, ...)
140 va_list arg;
141 char *s;
143 va_start(arg, fmt);
144 s = vsmprint(fmt, arg);
145 va_end(arg);
146 freecmd();
147 allwindows(allelogterm, nil); /* truncate the edit logs */
148 sendp(editerrc, s);
149 threadexits(nil);
152 void
153 editcmd(Text *ct, Rune *r, uint n)
155 char *err;
157 if(n == 0)
158 return;
159 if(2*n > RBUFSIZE){
160 warning(nil, "string too long\n");
161 return;
164 allwindows(alleditinit, nil);
165 if(cmdstartp)
166 free(cmdstartp);
167 cmdstartp = runemalloc(n+2);
168 runemove(cmdstartp, r, n);
169 if(r[n] != '\n')
170 cmdstartp[n++] = '\n';
171 cmdstartp[n] = '\0';
172 cmdendp = cmdstartp+n;
173 cmdp = cmdstartp;
174 if(ct->w == nil)
175 curtext = nil;
176 else
177 curtext = &ct->w->body;
178 resetxec();
179 if(editerrc == nil){
180 editerrc = chancreate(sizeof(char*), 0);
181 chansetname(editerrc, "editerrc");
182 lastpat = allocstring(0);
184 threadcreate(editthread, nil, STACK);
185 err = recvp(editerrc);
186 editing = Inactive;
187 if(err != nil){
188 if(err[0] != '\0')
189 warning(nil, "Edit: %s\n", err);
190 free(err);
193 /* update everyone whose edit log has data */
194 allwindows(allupdate, nil);
197 int
198 getch(void)
200 if(*cmdp == *cmdendp)
201 return -1;
202 return *cmdp++;
205 int
206 nextc(void)
208 if(*cmdp == *cmdendp)
209 return -1;
210 return *cmdp;
213 void
214 ungetch(void)
216 if(--cmdp < cmdstartp)
217 error("ungetch");
220 long
221 getnum(int signok)
223 long n;
224 int c, sign;
226 n = 0;
227 sign = 1;
228 if(signok>1 && nextc()=='-'){
229 sign = -1;
230 getch();
232 if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */
233 return sign;
234 while('0'<=(c=getch()) && c<='9')
235 n = n*10 + (c-'0');
236 ungetch();
237 return sign*n;
240 int
241 cmdskipbl(void)
243 int c;
244 do
245 c = getch();
246 while(c==' ' || c=='\t');
247 if(c >= 0)
248 ungetch();
249 return c;
252 /*
253 * Check that list has room for one more element.
254 */
255 void
256 growlist(List *l)
258 if(l->u.listptr==0 || l->nalloc==0){
259 l->nalloc = INCR;
260 l->u.listptr = emalloc(INCR*sizeof(long));
261 l->nused = 0;
262 }else if(l->nused == l->nalloc){
263 l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(long));
264 memset((void*)(l->u.longptr+l->nalloc), 0, INCR*sizeof(long));
265 l->nalloc += INCR;
269 /*
270 * Remove the ith element from the list
271 */
272 void
273 dellist(List *l, int i)
275 memmove(&l->u.longptr[i], &l->u.longptr[i+1], (l->nused-(i+1))*sizeof(long));
276 l->nused--;
279 /*
280 * Add a new element, whose position is i, to the list
281 */
282 void
283 inslist(List *l, int i, long val)
285 growlist(l);
286 memmove(&l->u.longptr[i+1], &l->u.longptr[i], (l->nused-i)*sizeof(long));
287 l->u.longptr[i] = val;
288 l->nused++;
291 void
292 listfree(List *l)
294 free(l->u.listptr);
295 free(l);
298 String*
299 allocstring(int n)
301 String *s;
303 s = emalloc(sizeof(String));
304 s->n = n;
305 s->nalloc = n+10;
306 s->r = emalloc(s->nalloc*sizeof(Rune));
307 s->r[n] = '\0';
308 return s;
311 void
312 freestring(String *s)
314 free(s->r);
315 free(s);
318 Cmd*
319 newcmd(void){
320 Cmd *p;
322 p = emalloc(sizeof(Cmd));
323 inslist(&cmdlist, cmdlist.nused, (long)p);
324 return p;
327 String*
328 newstring(int n)
330 String *p;
332 p = allocstring(n);
333 inslist(&stringlist, stringlist.nused, (long)p);
334 return p;
337 Addr*
338 newaddr(void)
340 Addr *p;
342 p = emalloc(sizeof(Addr));
343 inslist(&addrlist, addrlist.nused, (long)p);
344 return p;
347 void
348 freecmd(void)
350 int i;
352 while(cmdlist.nused > 0)
353 free(cmdlist.u.ucharptr[--cmdlist.nused]);
354 while(addrlist.nused > 0)
355 free(addrlist.u.ucharptr[--addrlist.nused]);
356 while(stringlist.nused>0){
357 i = --stringlist.nused;
358 freestring(stringlist.u.stringptr[i]);
362 void
363 okdelim(int c)
365 if(c=='\\' || ('a'<=c && c<='z')
366 || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
367 editerror("bad delimiter %c\n", c);
370 void
371 atnl(void)
373 int c;
375 cmdskipbl();
376 c = getch();
377 if(c != '\n')
378 editerror("newline expected (saw %C)", c);
381 void
382 Straddc(String *s, int c)
384 if(s->n+1 >= s->nalloc){
385 s->nalloc += 10;
386 s->r = erealloc(s->r, s->nalloc*sizeof(Rune));
388 s->r[s->n++] = c;
389 s->r[s->n] = '\0';
392 void
393 getrhs(String *s, int delim, int cmd)
395 int c;
397 while((c = getch())>0 && c!=delim && c!='\n'){
398 if(c == '\\'){
399 if((c=getch()) <= 0)
400 error("bad right hand side");
401 if(c == '\n'){
402 ungetch();
403 c='\\';
404 }else if(c == 'n')
405 c='\n';
406 else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */
407 Straddc(s, '\\');
409 Straddc(s, c);
411 ungetch(); /* let client read whether delimiter, '\n' or whatever */
414 String *
415 collecttoken(char *end)
417 String *s = newstring(0);
418 int c;
420 while((c=nextc())==' ' || c=='\t')
421 Straddc(s, getch()); /* blanks significant for getname() */
422 while((c=getch())>0 && utfrune(end, c)==0)
423 Straddc(s, c);
424 if(c != '\n')
425 atnl();
426 return s;
429 String *
430 collecttext(void)
432 String *s;
433 int begline, i, c, delim;
435 s = newstring(0);
436 if(cmdskipbl()=='\n'){
437 getch();
438 i = 0;
439 do{
440 begline = i;
441 while((c = getch())>0 && c!='\n')
442 i++, Straddc(s, c);
443 i++, Straddc(s, '\n');
444 if(c < 0)
445 goto Return;
446 }while(s->r[begline]!='.' || s->r[begline+1]!='\n');
447 s->r[s->n-2] = '\0';
448 s->n -= 2;
449 }else{
450 okdelim(delim = getch());
451 getrhs(s, delim, 'a');
452 if(nextc()==delim)
453 getch();
454 atnl();
456 Return:
457 return s;
460 int
461 cmdlookup(int c)
463 int i;
465 for(i=0; cmdtab[i].cmdc; i++)
466 if(cmdtab[i].cmdc == c)
467 return i;
468 return -1;
471 Cmd*
472 parsecmd(int nest)
474 int i, c;
475 struct cmdtab *ct;
476 Cmd *cp, *ncp;
477 Cmd cmd;
479 cmd.next = cmd.u.cmd = 0;
480 cmd.re = 0;
481 cmd.flag = cmd.num = 0;
482 cmd.addr = compoundaddr();
483 if(cmdskipbl() == -1)
484 return 0;
485 if((c=getch())==-1)
486 return 0;
487 cmd.cmdc = c;
488 if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */
489 getch(); /* the 'd' */
490 cmd.cmdc='c'|0x100;
492 i = cmdlookup(cmd.cmdc);
493 if(i >= 0){
494 if(cmd.cmdc == '\n')
495 goto Return; /* let nl_cmd work it all out */
496 ct = &cmdtab[i];
497 if(ct->defaddr==aNo && cmd.addr)
498 editerror("command takes no address");
499 if(ct->count)
500 cmd.num = getnum(ct->count);
501 if(ct->regexp){
502 /* x without pattern -> .*\n, indicated by cmd.re==0 */
503 /* X without pattern is all files */
504 if((ct->cmdc!='x' && ct->cmdc!='X') ||
505 ((c = nextc())!=' ' && c!='\t' && c!='\n')){
506 cmdskipbl();
507 if((c = getch())=='\n' || c<0)
508 editerror("no address");
509 okdelim(c);
510 cmd.re = getregexp(c);
511 if(ct->cmdc == 's'){
512 cmd.u.text = newstring(0);
513 getrhs(cmd.u.text, c, 's');
514 if(nextc() == c){
515 getch();
516 if(nextc() == 'g')
517 cmd.flag = getch();
523 if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0)
524 editerror("bad address");
525 if(ct->defcmd){
526 if(cmdskipbl() == '\n'){
527 getch();
528 cmd.u.cmd = newcmd();
529 cmd.u.cmd->cmdc = ct->defcmd;
530 }else if((cmd.u.cmd = parsecmd(nest))==0)
531 error("defcmd");
532 }else if(ct->text)
533 cmd.u.text = collecttext();
534 else if(ct->token)
535 cmd.u.text = collecttoken(ct->token);
536 else
537 atnl();
538 }else
539 switch(cmd.cmdc){
540 case '{':
541 cp = 0;
542 do{
543 if(cmdskipbl()=='\n')
544 getch();
545 ncp = parsecmd(nest+1);
546 if(cp)
547 cp->next = ncp;
548 else
549 cmd.u.cmd = ncp;
550 }while(cp = ncp);
551 break;
552 case '}':
553 atnl();
554 if(nest==0)
555 editerror("right brace with no left brace");
556 return 0;
557 default:
558 editerror("unknown command %c", cmd.cmdc);
560 Return:
561 cp = newcmd();
562 *cp = cmd;
563 return cp;
566 String*
567 getregexp(int delim)
569 String *buf, *r;
570 int i, c;
572 buf = allocstring(0);
573 for(i=0; ; i++){
574 if((c = getch())=='\\'){
575 if(nextc()==delim)
576 c = getch();
577 else if(nextc()=='\\'){
578 Straddc(buf, c);
579 c = getch();
581 }else if(c==delim || c=='\n')
582 break;
583 if(i >= RBUFSIZE)
584 editerror("regular expression too long");
585 Straddc(buf, c);
587 if(c!=delim && c)
588 ungetch();
589 if(buf->n > 0){
590 patset = TRUE;
591 freestring(lastpat);
592 lastpat = buf;
593 }else
594 freestring(buf);
595 if(lastpat->n == 0)
596 editerror("no regular expression defined");
597 r = newstring(lastpat->n);
598 runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */
599 return r;
602 Addr *
603 simpleaddr(void)
605 Addr addr;
606 Addr *ap, *nap;
608 addr.num = 0;
609 addr.next = 0;
610 addr.u.left = 0;
611 switch(cmdskipbl()){
612 case '#':
613 addr.type = getch();
614 addr.num = getnum(1);
615 break;
616 case '0': case '1': case '2': case '3': case '4':
617 case '5': case '6': case '7': case '8': case '9':
618 addr.num = getnum(1);
619 addr.type='l';
620 break;
621 case '/': case '?': case '"':
622 addr.u.re = getregexp(addr.type = getch());
623 break;
624 case '.':
625 case '$':
626 case '+':
627 case '-':
628 case '\'':
629 addr.type = getch();
630 break;
631 default:
632 return 0;
634 if(addr.next = simpleaddr())
635 switch(addr.next->type){
636 case '.':
637 case '$':
638 case '\'':
639 if(addr.type!='"')
640 case '"':
641 editerror("bad address syntax");
642 break;
643 case 'l':
644 case '#':
645 if(addr.type=='"')
646 break;
647 /* fall through */
648 case '/':
649 case '?':
650 if(addr.type!='+' && addr.type!='-'){
651 /* insert the missing '+' */
652 nap = newaddr();
653 nap->type='+';
654 nap->next = addr.next;
655 addr.next = nap;
657 break;
658 case '+':
659 case '-':
660 break;
661 default:
662 error("simpleaddr");
664 ap = newaddr();
665 *ap = addr;
666 return ap;
669 Addr *
670 compoundaddr(void)
672 Addr addr;
673 Addr *ap, *next;
675 addr.u.left = simpleaddr();
676 if((addr.type = cmdskipbl())!=',' && addr.type!=';')
677 return addr.u.left;
678 getch();
679 next = addr.next = compoundaddr();
680 if(next && (next->type==',' || next->type==';') && next->u.left==0)
681 editerror("bad address syntax");
682 ap = newaddr();
683 *ap = addr;
684 return ap;