]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/acme/edit.c
stats: show amount of reclaimable pages (add -r flag)
[plan9front.git] / sys / src / cmd / acme / edit.c
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
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
94 allelogterm(Window *w, void*)
95 {
96         elogterm(w->body.file);
97 }
98
99 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
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
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
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
193 getch(void)
194 {
195         if(*cmdp == *cmdendp)
196                 return -1;
197         return *cmdp++;
198 }
199
200 int
201 nextc(void)
202 {
203         if(*cmdp == *cmdendp)
204                 return -1;
205         return *cmdp;
206 }
207
208 void
209 ungetch(void)
210 {
211         if(--cmdp < cmdstartp)
212                 error("ungetch");
213 }
214
215 long
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
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
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
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
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
287 listfree(List *l)
288 {
289         free(l->listptr);
290         free(l);
291 }
292
293 String*
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
307 freestring(String *s)
308 {
309         free(s->r);
310         free(s);
311 }
312
313 Cmd*
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*
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*
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
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
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
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
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
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 *
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 *
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
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*
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*
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 *
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 *
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 }