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