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