]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/acme/ecmd.c
acme: fix suicide *and* resource leak in ecmd.c (thanks igor)
[plan9front.git] / sys / src / cmd / acme / ecmd.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 int     Glooping;
16 int     nest;
17 char    Enoname[] = "no file name given";
18
19 Address addr;
20 File    *menu;
21 Rangeset        sel;
22 extern  Text*   curtext;
23 Rune    *collection;
24 int     ncollection;
25
26 int     append(File*, Cmd*, long);
27 int     pdisplay(File*);
28 void    pfilename(File*);
29 void    looper(File*, Cmd*, int);
30 void    filelooper(Text*, Cmd*, int);
31 void    linelooper(File*, Cmd*);
32 Address lineaddr(long, Address, int);
33 int     filematch(File*, String*);
34 File    *tofile(String*);
35 Rune*   cmdname(File *f, String *s, int);
36 void    runpipe(Text*, int, Rune*, int, int);
37
38 void
39 clearcollection(void)
40 {
41         free(collection);
42         collection = nil;
43         ncollection = 0;
44 }
45
46 void
47 resetxec(void)
48 {
49         Glooping = nest = 0;
50         clearcollection();
51 }
52
53 void
54 mkaddr(Address *a, File *f)
55 {
56         a->r.q0 = f->curtext->q0;
57         a->r.q1 = f->curtext->q1;
58         a->f = f;
59 }
60
61 int
62 cmdexec(Text *t, Cmd *cp)
63 {
64         int i;
65         Addr *ap;
66         File *f;
67         Window *w;
68         Address dot;
69
70         if(t == nil)
71                 w = nil;
72         else
73                 w = t->w;
74         if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
75             !utfrune("bBnqUXY!", cp->cmdc) &&
76             !(cp->cmdc=='D' && cp->text))
77                 editerror("no current window");
78         i = cmdlookup(cp->cmdc);        /* will be -1 for '{' */
79         f = nil;
80         if(t && t->w){
81                 t = &t->w->body;
82                 f = t->file;
83                 f->curtext = t;
84         }
85         if(i>=0 && cmdtab[i].defaddr != aNo){
86                 if((ap=cp->addr)==0 && cp->cmdc!='\n'){
87                         cp->addr = ap = newaddr();
88                         ap->type = '.';
89                         if(cmdtab[i].defaddr == aAll)
90                                 ap->type = '*';
91                 }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
92                         ap->next = newaddr();
93                         ap->next->type = '.';
94                         if(cmdtab[i].defaddr == aAll)
95                                 ap->next->type = '*';
96                 }
97                 if(cp->addr){   /* may be false for '\n' (only) */
98                         static Address none = {0,0,nil};
99                         if(f){
100                                 mkaddr(&dot, f);
101                                 addr = cmdaddress(ap, dot, 0);
102                         }else   /* a " */
103                                 addr = cmdaddress(ap, none, 0);
104                         f = addr.f;
105                         t = f->curtext;
106                 }
107         }
108         switch(cp->cmdc){
109         case '{':
110                 mkaddr(&dot, f);
111                 if(cp->addr != nil)
112                         dot = cmdaddress(cp->addr, dot, 0);
113                 for(cp = cp->cmd; cp; cp = cp->next){
114                         if(dot.r.q1 > t->file->nc)
115                                 editerror("dot extends past end of buffer during { command");
116                         t->q0 = dot.r.q0;
117                         t->q1 = dot.r.q1;
118                         cmdexec(t, cp);
119                 }
120                 break;
121         default:
122                 if(i < 0)
123                         editerror("unknown command %c in cmdexec", cp->cmdc);
124                 i = (*cmdtab[i].fn)(t, cp);
125                 return i;
126         }
127         return 1;
128 }
129
130 char*
131 edittext(Window *w, int q, Rune *r, int nr)
132 {
133         File *f;
134
135         switch(editing){
136         case Inactive:
137                 return "permission denied";
138         case Inserting:
139                 f = w->body.file;
140                 eloginsert(f, q, r, nr);
141                 return nil;
142         case Collecting:
143                 collection = runerealloc(collection, ncollection+nr+1);
144                 runemove(collection+ncollection, r, nr);
145                 ncollection += nr;
146                 collection[ncollection] = '\0';
147                 return nil;
148         default:
149                 return "unknown state in edittext";
150         }
151 }
152
153 /* string is known to be NUL-terminated */
154 Rune*
155 filelist(Text *t, Rune *r, int nr)
156 {
157         if(nr == 0)
158                 return nil;
159         r = skipbl(r, nr, &nr);
160         clearcollection();
161         if(r[0] != '<'){
162                 if((collection = runestrdup(r)) != nil)
163                         ncollection += runestrlen(r);
164         }else
165                 /* use < command to collect text */
166                 runpipe(t, '<', r+1, nr-1, Collecting);
167         return collection;
168 }
169
170 int
171 a_cmd(Text *t, Cmd *cp)
172 {
173         return append(t->file, cp, addr.r.q1);
174 }
175
176 int
177 b_cmd(Text*, Cmd *cp)
178 {
179         File *f;
180
181         f = tofile(cp->text);
182         if(nest == 0)
183                 pfilename(f);
184         curtext = f->curtext;
185         return TRUE;
186 }
187
188 int
189 B_cmd(Text *t, Cmd *cp)
190 {
191         Rune *list, *r, *s;
192         int nr;
193
194         list = filelist(t, cp->text->r, cp->text->n);
195         if(list == nil)
196                 editerror(Enoname);
197         r = list;
198         nr = runestrlen(r);
199         r = skipbl(r, nr, &nr);
200         if(nr == 0)
201                 new(t, t, nil, 0, 0, r, 0);
202         else while(nr > 0){
203                 s = findbl(r, nr, &nr);
204                 *s = '\0';
205                 new(t, t, nil, 0, 0, r, runestrlen(r));
206                 if(nr > 0)
207                         r = skipbl(s+1, nr-1, &nr);
208         }
209         clearcollection();
210         return TRUE;
211 }
212
213 int
214 c_cmd(Text *t, Cmd *cp)
215 {
216         elogreplace(t->file, addr.r.q0, addr.r.q1, cp->text->r, cp->text->n);
217         t->q0 = addr.r.q0;
218         t->q1 = addr.r.q0;
219         return TRUE;
220 }
221
222 int
223 d_cmd(Text *t, Cmd*)
224 {
225         if(addr.r.q1 > addr.r.q0)
226                 elogdelete(t->file, addr.r.q0, addr.r.q1);
227         t->q0 = addr.r.q0;
228         t->q1 = addr.r.q0;
229         return TRUE;
230 }
231
232 void
233 D1(Text *t)
234 {
235         if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
236                 colclose(t->col, t->w, TRUE);
237 }
238
239 int
240 D_cmd(Text *t, Cmd *cp)
241 {
242         Rune *list, *r, *s, *n;
243         int nr, nn;
244         Window *w;
245         Runestr dir, rs;
246         char buf[128];
247
248         list = filelist(t, cp->text->r, cp->text->n);
249         if(list == nil){
250                 D1(t);
251                 return TRUE;
252         }
253         dir = dirname(t, nil, 0);
254         r = list;
255         nr = runestrlen(r);
256         r = skipbl(r, nr, &nr);
257         do{
258                 s = findbl(r, nr, &nr);
259                 *s = '\0';
260                 /* first time through, could be empty string, meaning delete file empty name */
261                 nn = runestrlen(r);
262                 if(r[0]=='/' || nn==0 || dir.nr==0){
263                         rs.r = runestrdup(r);
264                         rs.nr = nn;
265                 }else{
266                         n = runemalloc(dir.nr+1+nn);
267                         runemove(n, dir.r, dir.nr);
268                         n[dir.nr] = '/';
269                         runemove(n+dir.nr+1, r, nn);
270                         rs = cleanrname((Runestr){n, dir.nr+1+nn});
271                 }
272                 w = lookfile(rs.r, rs.nr);
273                 if(w == nil){
274                         snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
275                         free(rs.r);
276                         editerror(buf);
277                 }
278                 free(rs.r);
279                 D1(&w->body);
280                 if(nr > 0)
281                         r = skipbl(s+1, nr-1, &nr);
282         }while(nr > 0);
283         clearcollection();
284         free(dir.r);
285         return TRUE;
286 }
287
288 static int
289 readloader(void *v, uint q0, Rune *r, int nr)
290 {
291         if(nr > 0)
292                 eloginsert(v, q0, r, nr);
293         return 0;
294 }
295
296 int
297 e_cmd(Text *t, Cmd *cp)
298 {
299         Rune *name;
300         File *f;
301         int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
302         char *s, tmp[128];
303         Dir *d;
304
305         f = t->file;
306         q0 = addr.r.q0;
307         q1 = addr.r.q1;
308         if(cp->cmdc == 'e'){
309                 if(winclean(t->w, TRUE)==FALSE)
310                         editerror("");  /* winclean generated message already */
311                 q0 = 0;
312                 q1 = f->nc;
313         }
314         allreplaced = (q0==0 && q1==f->nc);
315         name = cmdname(f, cp->text, cp->cmdc=='e');
316         if(name == nil)
317                 editerror(Enoname);
318         i = runestrlen(name);
319         samename = runeeq(name, i, t->file->name, t->file->nname);
320         s = runetobyte(name, i);
321         free(name);
322         fd = open(s, OREAD);
323         if(fd < 0){
324                 snprint(tmp, sizeof tmp, "can't open %s: %r", s);
325                 free(s);
326                 editerror(tmp);
327         }
328         d = dirfstat(fd);
329         isdir = (d!=nil && (d->qid.type&QTDIR));
330         free(d);
331         if(isdir){
332                 close(fd);
333                 snprint(tmp, sizeof tmp, "%s is a directory", s);
334                 free(s);
335                 editerror(tmp);
336         }
337         elogdelete(f, q0, q1);
338         nulls = 0;
339         loadfile(fd, q1, &nulls, readloader, f);
340         free(s);
341         close(fd);
342         if(nulls)
343                 warning(nil, "%s: NUL bytes elided\n", s);
344         else if(allreplaced && samename)
345                 f->editclean = TRUE;
346         return TRUE;
347 }
348
349 int
350 f_cmd(Text *t, Cmd *cp)
351 {
352         Rune *name;
353         String *str;
354         String empty;
355
356         if(cp->text == nil){
357                 empty.n = 0;
358                 empty.r = L"";
359                 str = &empty;
360         }else
361                 str = cp->text;
362         name = cmdname(t->file, str, TRUE);
363         free(name);
364         pfilename(t->file);
365         return TRUE;
366 }
367
368 int
369 g_cmd(Text *t, Cmd *cp)
370 {
371         if(t->file != addr.f){
372                 warning(nil, "internal error: g_cmd f!=addr.f\n");
373                 return FALSE;
374         }
375         if(rxcompile(cp->re->r) == FALSE)
376                 editerror("bad regexp in g command");
377         if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
378                 t->q0 = addr.r.q0;
379                 t->q1 = addr.r.q1;
380                 return cmdexec(t, cp->cmd);
381         }
382         return TRUE;
383 }
384
385 int
386 i_cmd(Text *t, Cmd *cp)
387 {
388         return append(t->file, cp, addr.r.q0);
389 }
390
391 void
392 copy(File *f, Address addr2)
393 {
394         long p;
395         int ni;
396         Rune *buf;
397
398         buf = fbufalloc();
399         for(p=addr.r.q0; p<addr.r.q1; p+=ni){
400                 ni = addr.r.q1-p;
401                 if(ni > RBUFSIZE)
402                         ni = RBUFSIZE;
403                 bufread(f, p, buf, ni);
404                 eloginsert(addr2.f, addr2.r.q1, buf, ni);
405         }
406         fbuffree(buf);
407 }
408
409 void
410 move(File *f, Address addr2)
411 {
412         if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
413                 elogdelete(f, addr.r.q0, addr.r.q1);
414                 copy(f, addr2);
415         }else if(addr.r.q0 >= addr2.r.q1){
416                 copy(f, addr2);
417                 elogdelete(f, addr.r.q0, addr.r.q1);
418         }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
419                 ;       /* move to self; no-op */
420         }else
421                 editerror("move overlaps itself");
422 }
423
424 int
425 m_cmd(Text *t, Cmd *cp)
426 {
427         Address dot, addr2;
428
429         mkaddr(&dot, t->file);
430         addr2 = cmdaddress(cp->mtaddr, dot, 0);
431         if(cp->cmdc == 'm')
432                 move(t->file, addr2);
433         else
434                 copy(t->file, addr2);
435         return TRUE;
436 }
437
438 int
439 p_cmd(Text *t, Cmd*)
440 {
441         return pdisplay(t->file);
442 }
443
444 int
445 s_cmd(Text *t, Cmd *cp)
446 {
447         int i, j, k, c, m, n, nrp, didsub;
448         long p1, op, delta;
449         String *buf;
450         Rangeset *rp;
451         char *err;
452         Rune *rbuf;
453
454         n = cp->num;
455         op= -1;
456         if(rxcompile(cp->re->r) == FALSE)
457                 editerror("bad regexp in s command");
458         nrp = 0;
459         rp = nil;
460         delta = 0;
461         didsub = FALSE;
462         for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
463                 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
464                         if(sel.r[0].q0 == op){
465                                 p1++;
466                                 continue;
467                         }
468                         p1 = sel.r[0].q1+1;
469                 }else
470                         p1 = sel.r[0].q1;
471                 op = sel.r[0].q1;
472                 if(--n>0)
473                         continue;
474                 nrp++;
475                 rp = erealloc(rp, nrp*sizeof(Rangeset));
476                 rp[nrp-1] = sel;
477         }
478         rbuf = fbufalloc();
479         buf = allocstring(0);
480         for(m=0; m<nrp; m++){
481                 buf->n = 0;
482                 buf->r[0] = L'\0';
483                 sel = rp[m];
484                 for(i = 0; i<cp->text->n; i++)
485                         if((c = cp->text->r[i])=='\\' && i<cp->text->n-1){
486                                 c = cp->text->r[++i];
487                                 if('1'<=c && c<='9') {
488                                         j = c-'0';
489                                         if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
490                                                 err = "replacement string too long";
491                                                 goto Err;
492                                         }
493                                         bufread(t->file, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
494                                         for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
495                                                 Straddc(buf, rbuf[k]);
496                                 }else
497                                         Straddc(buf, c);
498                         }else if(c!='&')
499                                 Straddc(buf, c);
500                         else{
501                                 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
502                                         err = "right hand side too long in substitution";
503                                         goto Err;
504                                 }
505                                 bufread(t->file, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
506                                 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
507                                         Straddc(buf, rbuf[k]);
508                         }
509                 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1,  buf->r, buf->n);
510                 delta -= sel.r[0].q1-sel.r[0].q0;
511                 delta += buf->n;
512                 didsub = 1;
513                 if(!cp->flag)
514                         break;
515         }
516         free(rp);
517         freestring(buf);
518         fbuffree(rbuf);
519         if(!didsub && nest==0)
520                 editerror("no substitution");
521         t->q0 = addr.r.q0;
522         t->q1 = addr.r.q1;
523         return TRUE;
524
525 Err:
526         free(rp);
527         freestring(buf);
528         fbuffree(rbuf);
529         editerror(err);
530         return FALSE;
531 }
532
533 int
534 u_cmd(Text *t, Cmd *cp)
535 {
536         int n, oseq, flag;
537
538         n = cp->num;
539         flag = TRUE;
540         if(n < 0){
541                 n = -n;
542                 flag = FALSE;
543         }
544         oseq = -1;
545         while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){
546                 oseq = t->file->seq;
547                 undo(t, nil, nil, flag, 0, nil, 0);
548         }
549         return TRUE;
550 }
551
552 int
553 w_cmd(Text *t, Cmd *cp)
554 {
555         Rune *r;
556         File *f;
557
558         f = t->file;
559         if(f->seq == seq)
560                 editerror("can't write file with pending modifications");
561         r = cmdname(f, cp->text, FALSE);
562         if(r == nil)
563                 editerror("no name specified for 'w' command");
564         putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
565         /* r is freed by putfile */
566         return TRUE;
567 }
568
569 int
570 x_cmd(Text *t, Cmd *cp)
571 {
572         if(cp->re)
573                 looper(t->file, cp, cp->cmdc=='x');
574         else
575                 linelooper(t->file, cp);
576         return TRUE;
577 }
578
579 int
580 X_cmd(Text *t, Cmd *cp)
581 {
582         filelooper(t, cp, cp->cmdc=='X');
583         return TRUE;
584 }
585
586 void
587 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
588 {
589         Rune *r, *s;
590         int n;
591         Runestr dir;
592         Window *w;
593
594         r = skipbl(cr, ncr, &n);
595         if(n == 0)
596                 editerror("no command specified for %c", cmd);
597         w = nil;
598         if(state == Inserting){
599                 w = t->w;
600                 t->q0 = addr.r.q0;
601                 t->q1 = addr.r.q1;
602                 if(cmd == '<' || cmd=='|')
603                         elogdelete(t->file, t->q0, t->q1);
604         }
605         s = runemalloc(n+2);
606         s[0] = cmd;
607         runemove(s+1, r, n);
608         n++;
609         dir.r = nil;
610         dir.nr = 0;
611         if(t != nil)
612                 dir = dirname(t, nil, 0);
613         if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
614                 free(dir.r);
615                 dir.r = nil;
616                 dir.nr = 0;
617         }
618         editing = state;
619         if(t!=nil && t->w!=nil)
620                 incref(t->w);   /* run will decref */
621         run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
622         free(s);
623         if(t!=nil && t->w!=nil)
624                 winunlock(t->w);
625         qunlock(&row);
626         recvul(cedit);
627         qlock(&row);
628         editing = Inactive;
629         if(t!=nil && t->w!=nil)
630                 winlock(t->w, 'M');
631 }
632
633 int
634 pipe_cmd(Text *t, Cmd *cp)
635 {
636         runpipe(t, cp->cmdc, cp->text->r, cp->text->n, Inserting);
637         return TRUE;
638 }
639
640 long
641 nlcount(Text *t, long q0, long q1, long *pnr)
642 {
643         long nl, start;
644         Rune *buf;
645         int i, nbuf;
646
647         buf = fbufalloc();
648         nbuf = 0;
649         i = nl = 0;
650         start = q0;
651         while(q0 < q1){
652                 if(i == nbuf){
653                         nbuf = q1-q0;
654                         if(nbuf > RBUFSIZE)
655                                 nbuf = RBUFSIZE;
656                         bufread(t->file, q0, buf, nbuf);
657                         i = 0;
658                 }
659                 if(buf[i++] == '\n'){
660                         start = q0+1;
661                         nl++;
662                 }
663                 q0++;
664         }
665         fbuffree(buf);
666         if(pnr != nil)
667                 *pnr = q0 - start;
668         return nl;
669 }
670
671 enum {
672         PosnLine = 0,
673         PosnChars = 1,
674         PosnLineChars = 2,
675 };
676
677 void
678 printposn(Text *t, int mode)
679 {
680         long l1, l2, r1, r2;
681
682         if (t != nil && t->file != nil && t->file->name != nil)
683                 warning(nil, "%.*S:", t->file->nname, t->file->name);
684         switch(mode) {
685         case PosnChars:
686                 warning(nil, "#%d", addr.r.q0);
687                 if(addr.r.q1 != addr.r.q0)
688                         warning(nil, ",#%d", addr.r.q1);
689                 warning(nil, "\n");
690                 return;
691         default:
692         case PosnLine:
693                 l1 = 1+nlcount(t, 0, addr.r.q0, nil);
694                 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, nil);
695                 /* check if addr ends with '\n' */
696                 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
697                         --l2;
698                 warning(nil, "%lud", l1);
699                 if(l2 != l1)
700                         warning(nil, ",%lud", l2);
701                 warning(nil, "\n");
702                 return;
703         case PosnLineChars:
704                 l1 = 1+nlcount(t, 0, addr.r.q0, &r1);
705                 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, &r2);
706                 if(l2 == l1)
707                         r2 += r1;
708                 warning(nil, "%lud+#%lud", l1, r1);
709                 if(l2 != l1)
710                         warning(nil, ",%lud+#%lud", l2, r2);
711                 warning(nil, "\n");
712                 return;
713         }
714 }
715
716 int
717 eq_cmd(Text *t, Cmd *cp)
718 {
719         int mode;
720
721         switch(cp->text->n){
722         case 0:
723                 mode = PosnLine;
724                 break;
725         case 1:
726                 if(cp->text->r[0] == '#'){
727                         mode = PosnChars;
728                         break;
729                 }
730                 if(cp->text->r[0] == '+'){
731                         mode = PosnLineChars;
732                         break;
733                 }
734         default:
735                 SET(mode);
736                 editerror("newline expected");
737         }
738         printposn(t, mode);
739         return TRUE;
740 }
741
742 int
743 nl_cmd(Text *t, Cmd *cp)
744 {
745         Address a;
746         File *f;
747
748         f = t->file;
749         if(cp->addr == 0){
750                 /* First put it on newline boundaries */
751                 mkaddr(&a, f);
752                 addr = lineaddr(0, a, -1);
753                 a = lineaddr(0, a, 1);
754                 addr.r.q1 = a.r.q1;
755                 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
756                         mkaddr(&a, f);
757                         addr = lineaddr(1, a, 1);
758                 }
759         }
760         textshow(t, addr.r.q0, addr.r.q1, 1);
761         return TRUE;
762 }
763
764 int
765 append(File *f, Cmd *cp, long p)
766 {
767         if(cp->text->n > 0)
768                 eloginsert(f, p, cp->text->r, cp->text->n);
769         f->curtext->q0 = p;
770         f->curtext->q1 = p;
771         return TRUE;
772 }
773
774 int
775 pdisplay(File *f)
776 {
777         long p1, p2;
778         int np;
779         Rune *buf;
780
781         p1 = addr.r.q0;
782         p2 = addr.r.q1;
783         if(p2 > f->nc)
784                 p2 = f->nc;
785         buf = fbufalloc();
786         while(p1 < p2){
787                 np = p2-p1;
788                 if(np>RBUFSIZE-1)
789                         np = RBUFSIZE-1;
790                 bufread(f, p1, buf, np);
791                 buf[np] = L'\0';
792                 warning(nil, "%S", buf);
793                 p1 += np;
794         }
795         fbuffree(buf);
796         f->curtext->q0 = addr.r.q0;
797         f->curtext->q1 = addr.r.q1;
798         return TRUE;
799 }
800
801 void
802 pfilename(File *f)
803 {
804         int dirty;
805         Window *w;
806
807         w = f->curtext->w;
808         /* same check for dirty as in settag, but we know ncache==0 */
809         dirty = !w->isdir && !w->isscratch && f->mod;
810         warning(nil, "%c%c%c %.*S\n", " '"[dirty],
811                 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
812 }
813
814 void
815 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
816 {
817         long i;
818
819         for(i=0; i<nrp; i++){
820                 f->curtext->q0 = rp[i].q0;
821                 f->curtext->q1 = rp[i].q1;
822                 cmdexec(f->curtext, cp);
823         }
824 }
825
826 void
827 looper(File *f, Cmd *cp, int xy)
828 {
829         long p, op, nrp;
830         Range r, tr;
831         Range *rp;
832
833         r = addr.r;
834         op= xy? -1 : r.q0;
835         nest++;
836         if(rxcompile(cp->re->r) == FALSE)
837                 editerror("bad regexp in %c command", cp->cmdc);
838         nrp = 0;
839         rp = nil;
840         for(p = r.q0; p<=r.q1; ){
841                 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
842                         if(xy || op>r.q1)
843                                 break;
844                         tr.q0 = op, tr.q1 = r.q1;
845                         p = r.q1+1;     /* exit next loop */
846                 }else{
847                         if(sel.r[0].q0==sel.r[0].q1){   /* empty match? */
848                                 if(sel.r[0].q0==op){
849                                         p++;
850                                         continue;
851                                 }
852                                 p = sel.r[0].q1+1;
853                         }else
854                                 p = sel.r[0].q1;
855                         if(xy)
856                                 tr = sel.r[0];
857                         else
858                                 tr.q0 = op, tr.q1 = sel.r[0].q0;
859                 }
860                 op = sel.r[0].q1;
861                 nrp++;
862                 rp = erealloc(rp, nrp*sizeof(Range));
863                 rp[nrp-1] = tr;
864         }
865         loopcmd(f, cp->cmd, rp, nrp);
866         free(rp);
867         --nest;
868 }
869
870 void
871 linelooper(File *f, Cmd *cp)
872 {
873         long nrp, p;
874         Range r, linesel;
875         Address a, a3;
876         Range *rp;
877
878         nest++;
879         nrp = 0;
880         rp = nil;
881         r = addr.r;
882         a3.f = f;
883         a3.r.q0 = a3.r.q1 = r.q0;
884         a = lineaddr(0, a3, 1);
885         linesel = a.r;
886         for(p = r.q0; p<r.q1; p = a3.r.q1){
887                 a3.r.q0 = a3.r.q1;
888                 if(p!=r.q0 || linesel.q1==p){
889                         a = lineaddr(1, a3, 1);
890                         linesel = a.r;
891                 }
892                 if(linesel.q0 >= r.q1)
893                         break;
894                 if(linesel.q1 >= r.q1)
895                         linesel.q1 = r.q1;
896                 if(linesel.q1 > linesel.q0)
897                         if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
898                                 a3.r = linesel;
899                                 nrp++;
900                                 rp = erealloc(rp, nrp*sizeof(Range));
901                                 rp[nrp-1] = linesel;
902                                 continue;
903                         }
904                 break;
905         }
906         loopcmd(f, cp->cmd, rp, nrp);
907         free(rp);
908         --nest;
909 }
910
911 struct Looper
912 {
913         Cmd *cp;
914         int     XY;
915         Window  **w;
916         int     nw;
917 } loopstruct;   /* only one; X and Y can't nest */
918
919 void
920 alllooper(Window *w, void *v)
921 {
922         Text *t;
923         struct Looper *lp;
924         Cmd *cp;
925
926         lp = v;
927         cp = lp->cp;
928 //      if(w->isscratch || w->isdir)
929 //              return;
930         t = &w->body;
931         /* only use this window if it's the current window for the file */
932         if(t->file->curtext != t)
933                 return;
934 //      if(w->nopen[QWevent] > 0)
935 //              return;
936         /* no auto-execute on files without names */
937         if(cp->re==nil && t->file->nname==0)
938                 return;
939         if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
940                 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
941                 lp->w[lp->nw++] = w;
942         }
943 }
944
945 void
946 alllocker(Window *w, void *v)
947 {
948         if(v)
949                 incref(w);
950         else
951                 winclose(w);
952 }
953
954 void
955 filelooper(Text *t, Cmd *cp, int XY)
956 {
957         int i;
958         Text *targ;
959
960         if(Glooping++)
961                 editerror("can't nest %c command", "YX"[XY]);
962         nest++;
963
964         loopstruct.cp = cp;
965         loopstruct.XY = XY;
966         if(loopstruct.w)        /* error'ed out last time */
967                 free(loopstruct.w);
968         loopstruct.w = nil;
969         loopstruct.nw = 0;
970         allwindows(alllooper, &loopstruct);
971         /*
972          * add a ref to all windows to keep safe windows accessed by X
973          * that would not otherwise have a ref to hold them up during
974          * the shenanigans.  note this with globalincref so that any
975          * newly created windows start with an extra reference.
976          */
977         allwindows(alllocker, (void*)1);
978         globalincref = 1;
979         /*
980          * Unlock the window running the X command.
981          * We'll need to lock and unlock each target window in turn.
982          */
983         if(t && t->w)
984                 winunlock(t->w);
985         for(i=0; i<loopstruct.nw; i++){
986                 targ = &loopstruct.w[i]->body;
987                 if(targ && targ->w)
988                         winlock(targ->w, cp->cmdc);
989                 cmdexec(targ, cp->cmd);
990                 if(targ && targ->w)
991                         winunlock(targ->w);
992         }
993         if(t && t->w)
994                 winlock(t->w, cp->cmdc);
995         allwindows(alllocker, (void*)0);
996         globalincref = 0;
997         free(loopstruct.w);
998         loopstruct.w = nil;
999
1000         --Glooping;
1001         --nest;
1002 }
1003
1004 void
1005 nextmatch(File *f, String *r, long p, int sign)
1006 {
1007         if(rxcompile(r->r) == FALSE)
1008                 editerror("bad regexp in command address");
1009         if(sign >= 0){
1010                 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1011                         editerror("no match for regexp");
1012                 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
1013                         if(++p>f->nc)
1014                                 p = 0;
1015                         if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1016                                 editerror("address");
1017                 }
1018         }else{
1019                 if(!rxbexecute(f->curtext, p, &sel))
1020                         editerror("no match for regexp");
1021                 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
1022                         if(--p<0)
1023                                 p = f->nc;
1024                         if(!rxbexecute(f->curtext, p, &sel))
1025                                 editerror("address");
1026                 }
1027         }
1028 }
1029
1030 File    *matchfile(String*);
1031 Address charaddr(long, Address, int);
1032 Address lineaddr(long, Address, int);
1033
1034 Address
1035 cmdaddress(Addr *ap, Address a, int sign)
1036 {
1037         File *f = a.f;
1038         Address a1, a2;
1039
1040         do{
1041                 switch(ap->type){
1042                 case 'l':
1043                 case '#':
1044                         a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1045                         break;
1046
1047                 case '.':
1048                         mkaddr(&a, f);
1049                         break;
1050
1051                 case '$':
1052                         a.r.q0 = a.r.q1 = f->nc;
1053                         break;
1054
1055                 case '\'':
1056 editerror("can't handle '");
1057 //                      a.r = f->mark;
1058                         break;
1059
1060                 case '?':
1061                         sign = -sign;
1062                         if(sign == 0)
1063                                 sign = -1;
1064                         /* fall through */
1065                 case '/':
1066                         nextmatch(f, ap->re, sign>=0? a.r.q1 : a.r.q0, sign);
1067                         a.r = sel.r[0];
1068                         break;
1069
1070                 case '"':
1071                         f = matchfile(ap->re);
1072                         mkaddr(&a, f);
1073                         break;
1074
1075                 case '*':
1076                         a.r.q0 = 0, a.r.q1 = f->nc;
1077                         return a;
1078
1079                 case ',':
1080                 case ';':
1081                         if(ap->left)
1082                                 a1 = cmdaddress(ap->left, a, 0);
1083                         else
1084                                 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1085                         if(ap->type == ';'){
1086                                 f = a1.f;
1087                                 a = a1;
1088                                 f->curtext->q0 = a1.r.q0;
1089                                 f->curtext->q1 = a1.r.q1;
1090                         }
1091                         if(ap->next)
1092                                 a2 = cmdaddress(ap->next, a, 0);
1093                         else
1094                                 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->nc;
1095                         if(a1.f != a2.f)
1096                                 editerror("addresses in different files");
1097                         a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1098                         if(a.r.q1 < a.r.q0)
1099                                 editerror("addresses out of order");
1100                         return a;
1101
1102                 case '+':
1103                 case '-':
1104                         sign = 1;
1105                         if(ap->type == '-')
1106                                 sign = -1;
1107                         if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1108                                 a = lineaddr(1L, a, sign);
1109                         break;
1110                 default:
1111                         error("cmdaddress");
1112                         return a;
1113                 }
1114         }while(ap = ap->next);  /* assign = */
1115         return a;
1116 }
1117
1118 struct Tofile{
1119         File            *f;
1120         String  *r;
1121 };
1122
1123 void
1124 alltofile(Window *w, void *v)
1125 {
1126         Text *t;
1127         struct Tofile *tp;
1128
1129         tp = v;
1130         if(tp->f != nil)
1131                 return;
1132         if(w->isscratch || w->isdir)
1133                 return;
1134         t = &w->body;
1135         /* only use this window if it's the current window for the file */
1136         if(t->file->curtext != t)
1137                 return;
1138 //      if(w->nopen[QWevent] > 0)
1139 //              return;
1140         if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1141                 tp->f = t->file;
1142 }
1143
1144 File*
1145 tofile(String *r)
1146 {
1147         struct Tofile t;
1148         String rr;
1149
1150         rr.r = skipbl(r->r, r->n, &rr.n);
1151         t.f = nil;
1152         t.r = &rr;
1153         allwindows(alltofile, &t);
1154         if(t.f == nil)
1155                 editerror("no such file\"%S\"", rr.r);
1156         return t.f;
1157 }
1158
1159 void
1160 allmatchfile(Window *w, void *v)
1161 {
1162         struct Tofile *tp;
1163         Text *t;
1164
1165         tp = v;
1166         if(w->isscratch || w->isdir)
1167                 return;
1168         t = &w->body;
1169         /* only use this window if it's the current window for the file */
1170         if(t->file->curtext != t)
1171                 return;
1172 //      if(w->nopen[QWevent] > 0)
1173 //              return;
1174         if(filematch(w->body.file, tp->r)){
1175                 if(tp->f != nil)
1176                         editerror("too many files match \"%S\"", tp->r->r);
1177                 tp->f = w->body.file;
1178         }
1179 }
1180
1181 File*
1182 matchfile(String *r)
1183 {
1184         struct Tofile tf;
1185
1186         tf.f = nil;
1187         tf.r = r;
1188         allwindows(allmatchfile, &tf);
1189
1190         if(tf.f == nil)
1191                 editerror("no file matches \"%S\"", r->r);
1192         return tf.f;
1193 }
1194
1195 int
1196 filematch(File *f, String *r)
1197 {
1198         char *buf;
1199         Rune *rbuf;
1200         Window *w;
1201         int match, i, dirty;
1202         Rangeset s;
1203
1204         /* compile expr first so if we get an error, we haven't allocated anything */
1205         if(rxcompile(r->r) == FALSE)
1206                 editerror("bad regexp in file match");
1207         buf = fbufalloc();
1208         w = f->curtext->w;
1209         /* same check for dirty as in settag, but we know ncache==0 */
1210         dirty = !w->isdir && !w->isscratch && f->mod;
1211         snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1212                 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1213         rbuf = bytetorune(buf, &i);
1214         fbuffree(buf);
1215         match = rxexecute(nil, rbuf, 0, i, &s);
1216         free(rbuf);
1217         return match;
1218 }
1219
1220 Address
1221 charaddr(long l, Address addr, int sign)
1222 {
1223         if(sign == 0)
1224                 addr.r.q0 = addr.r.q1 = l;
1225         else if(sign < 0)
1226                 addr.r.q1 = addr.r.q0 -= l;
1227         else if(sign > 0)
1228                 addr.r.q0 = addr.r.q1 += l;
1229         if(addr.r.q0<0 || addr.r.q1>addr.f->nc)
1230                 editerror("address out of range");
1231         return addr;
1232 }
1233
1234 Address
1235 lineaddr(long l, Address addr, int sign)
1236 {
1237         int n;
1238         int c;
1239         File *f = addr.f;
1240         Address a;
1241         long p;
1242
1243         a.f = f;
1244         if(sign >= 0){
1245                 if(l == 0){
1246                         if(sign==0 || addr.r.q1==0){
1247                                 a.r.q0 = a.r.q1 = 0;
1248                                 return a;
1249                         }
1250                         a.r.q0 = addr.r.q1;
1251                         p = addr.r.q1-1;
1252                 }else{
1253                         if(sign==0 || addr.r.q1==0){
1254                                 p = 0;
1255                                 n = 1;
1256                         }else{
1257                                 p = addr.r.q1-1;
1258                                 n = textreadc(f->curtext, p++)=='\n';
1259                         }
1260                         while(n < l){
1261                                 if(p >= f->nc)
1262                                         editerror("address out of range");
1263                                 if(textreadc(f->curtext, p++) == '\n')
1264                                         n++;
1265                         }
1266                         a.r.q0 = p;
1267                 }
1268                 while(p < f->nc && textreadc(f->curtext, p++)!='\n')
1269                         ;
1270                 a.r.q1 = p;
1271         }else{
1272                 p = addr.r.q0;
1273                 if(l == 0)
1274                         a.r.q1 = addr.r.q0;
1275                 else{
1276                         for(n = 0; n<l; ){      /* always runs once */
1277                                 if(p == 0){
1278                                         if(++n != l)
1279                                                 editerror("address out of range");
1280                                 }else{
1281                                         c = textreadc(f->curtext, p-1);
1282                                         if(c != '\n' || ++n != l)
1283                                                 p--;
1284                                 }
1285                         }
1286                         a.r.q1 = p;
1287                         if(p > 0)
1288                                 p--;
1289                 }
1290                 while(p > 0 && textreadc(f->curtext, p-1)!='\n')        /* lines start after a newline */
1291                         p--;
1292                 a.r.q0 = p;
1293         }
1294         return a;
1295 }
1296
1297 struct Filecheck
1298 {
1299         File    *f;
1300         Rune    *r;
1301         int nr;
1302 };
1303
1304 void
1305 allfilecheck(Window *w, void *v)
1306 {
1307         struct Filecheck *fp;
1308         File *f;
1309
1310         fp = v;
1311         f = w->body.file;
1312         if(w->body.file == fp->f)
1313                 return;
1314         if(runeeq(fp->r, fp->nr, f->name, f->nname))
1315                 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1316 }
1317
1318 Rune*
1319 cmdname(File *f, String *str, int set)
1320 {
1321         Rune *r, *s;
1322         int n;
1323         struct Filecheck fc;
1324         Runestr newname;
1325
1326         r = nil;
1327         n = str->n;
1328         s = str->r;
1329         if(n == 0){
1330                 /* no name; use existing */
1331                 if(f->nname == 0)
1332                         return nil;
1333                 r = runemalloc(f->nname+1);
1334                 runemove(r, f->name, f->nname);
1335                 return r;
1336         }
1337         s = skipbl(s, n, &n);
1338         if(n == 0)
1339                 goto Return;
1340
1341         if(s[0] == '/'){
1342                 r = runemalloc(n+1);
1343                 runemove(r, s, n);
1344         }else{
1345                 newname = dirname(f->curtext, runestrdup(s), n);
1346                 n = newname.nr;
1347                 r = runemalloc(n+1);    /* NUL terminate */
1348                 runemove(r, newname.r, n);
1349                 free(newname.r);
1350         }
1351         fc.f = f;
1352         fc.r = r;
1353         fc.nr = n;
1354         allwindows(allfilecheck, &fc);
1355         if(f->nname == 0)
1356                 set = TRUE;
1357
1358     Return:
1359         if(set && !runeeq(r, n, f->name, f->nname)){
1360                 filemark(f);
1361                 f->mod = TRUE;
1362                 f->curtext->w->dirty = TRUE;
1363                 winsetname(f->curtext->w, r, n);
1364         }
1365         return r;
1366 }