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