Blob


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"
15 int Glooping;
16 int nest;
17 char Enoname[] = "no file name given";
19 Address addr;
20 File *menu;
21 Rangeset sel;
22 extern Text* curtext;
23 Rune *collection;
24 int ncollection;
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);
38 void
39 clearcollection(void)
40 {
41 free(collection);
42 collection = nil;
43 ncollection = 0;
44 }
46 void
47 resetxec(void)
48 {
49 Glooping = nest = 0;
50 clearcollection();
51 }
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 }
61 int
62 cmdexec(Text *t, Cmd *cp)
63 {
64 int i;
65 Addr *ap;
66 File *f;
67 Window *w;
68 Address dot;
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->u.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;
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->u.cmd; cp; cp = cp->next){
114 if(dot.r.q1 > t->file->b.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);
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;
127 return 1;
130 char*
131 edittext(Window *w, int q, Rune *r, int nr)
133 File *f;
135 f = w->body.file;
136 switch(editing){
137 case Inactive:
138 return "permission denied";
139 case Inserting:
140 w->neditwrsel += nr;
141 eloginsert(f, q, r, nr);
142 return nil;
143 case Collecting:
144 collection = runerealloc(collection, ncollection+nr+1);
145 runemove(collection+ncollection, r, nr);
146 ncollection += nr;
147 collection[ncollection] = '\0';
148 return nil;
149 default:
150 return "unknown state in edittext";
154 /* string is known to be NUL-terminated */
155 Rune*
156 filelist(Text *t, Rune *r, int nr)
158 if(nr == 0)
159 return nil;
160 r = skipbl(r, nr, &nr);
161 if(r[0] != '<')
162 return runestrdup(r);
163 /* use < command to collect text */
164 clearcollection();
165 runpipe(t, '<', r+1, nr-1, Collecting);
166 return collection;
169 int
170 a_cmd(Text *t, Cmd *cp)
172 return append(t->file, cp, addr.r.q1);
175 int
176 b_cmd(Text *t, Cmd *cp)
178 File *f;
180 USED(t);
181 f = tofile(cp->u.text);
182 if(nest == 0)
183 pfilename(f);
184 curtext = f->curtext;
185 return TRUE;
188 int
189 B_cmd(Text *t, Cmd *cp)
191 Rune *list, *r, *s;
192 int nr;
194 list = filelist(t, cp->u.text->r, cp->u.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);
209 clearcollection();
210 return TRUE;
213 int
214 c_cmd(Text *t, Cmd *cp)
216 elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n);
217 t->q0 = addr.r.q0;
218 t->q1 = addr.r.q0+cp->u.text->n;
219 return TRUE;
222 int
223 d_cmd(Text *t, Cmd *cp)
225 USED(cp);
226 if(addr.r.q1 > addr.r.q0)
227 elogdelete(t->file, addr.r.q0, addr.r.q1);
228 t->q0 = addr.r.q0;
229 t->q1 = addr.r.q0;
230 return TRUE;
233 void
234 D1(Text *t)
236 if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
237 colclose(t->col, t->w, TRUE);
240 int
241 D_cmd(Text *t, Cmd *cp)
243 Rune *list, *r, *s, *n;
244 int nr, nn;
245 Window *w;
246 Runestr dir, rs;
247 char buf[128];
249 list = filelist(t, cp->u.text->r, cp->u.text->n);
250 if(list == nil){
251 D1(t);
252 return TRUE;
254 dir = dirname(t, nil, 0);
255 r = list;
256 nr = runestrlen(r);
257 r = skipbl(r, nr, &nr);
258 do{
259 s = findbl(r, nr, &nr);
260 *s = '\0';
261 /* first time through, could be empty string, meaning delete file empty name */
262 nn = runestrlen(r);
263 if(r[0]=='/' || nn==0 || dir.nr==0){
264 rs.r = runestrdup(r);
265 rs.nr = nn;
266 }else{
267 n = runemalloc(dir.nr+1+nn);
268 runemove(n, dir.r, dir.nr);
269 n[dir.nr] = '/';
270 runemove(n+dir.nr+1, r, nn);
271 rs = cleanrname(runestr(n, dir.nr+1+nn));
273 w = lookfile(rs.r, rs.nr);
274 if(w == nil){
275 snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
276 free(rs.r);
277 editerror(buf);
279 free(rs.r);
280 D1(&w->body);
281 if(nr > 0)
282 r = skipbl(s+1, nr-1, &nr);
283 }while(nr > 0);
284 clearcollection();
285 free(dir.r);
286 return TRUE;
289 static int
290 readloader(void *v, uint q0, Rune *r, int nr)
292 if(nr > 0)
293 eloginsert(v, q0, r, nr);
294 return 0;
297 int
298 e_cmd(Text *t, Cmd *cp)
300 Rune *name;
301 File *f;
302 int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
303 char *s, tmp[128];
304 Dir *d;
306 f = t->file;
307 q0 = addr.r.q0;
308 q1 = addr.r.q1;
309 if(cp->cmdc == 'e'){
310 if(winclean(t->w, TRUE)==FALSE)
311 editerror(""); /* winclean generated message already */
312 q0 = 0;
313 q1 = f->b.nc;
315 allreplaced = (q0==0 && q1==f->b.nc);
316 name = cmdname(f, cp->u.text, cp->cmdc=='e');
317 if(name == nil)
318 editerror(Enoname);
319 i = runestrlen(name);
320 samename = runeeq(name, i, t->file->name, t->file->nname);
321 s = runetobyte(name, i);
322 free(name);
323 fd = open(s, OREAD);
324 if(fd < 0){
325 snprint(tmp, sizeof tmp, "can't open %s: %r", s);
326 free(s);
327 editerror(tmp);
329 d = dirfstat(fd);
330 isdir = (d!=nil && (d->qid.type&QTDIR));
331 free(d);
332 if(isdir){
333 close(fd);
334 snprint(tmp, sizeof tmp, "%s is a directory", s);
335 free(s);
336 editerror(tmp);
338 elogdelete(f, q0, q1);
339 nulls = 0;
340 loadfile(fd, q1, &nulls, readloader, f);
341 free(s);
342 close(fd);
343 if(nulls)
344 warning(nil, "%s: NUL bytes elided\n", s);
345 else if(allreplaced && samename)
346 f->editclean = TRUE;
347 return TRUE;
350 static Rune Lempty[] = { 0 };
351 int
352 f_cmd(Text *t, Cmd *cp)
354 Rune *name;
355 String *str;
356 String empty;
358 if(cp->u.text == nil){
359 empty.n = 0;
360 empty.r = Lempty;
361 str = &empty;
362 }else
363 str = cp->u.text;
364 name = cmdname(t->file, str, TRUE);
365 free(name);
366 pfilename(t->file);
367 return TRUE;
370 int
371 g_cmd(Text *t, Cmd *cp)
373 if(t->file != addr.f){
374 warning(nil, "internal error: g_cmd f!=addr.f\n");
375 return FALSE;
377 if(rxcompile(cp->re->r) == FALSE)
378 editerror("bad regexp in g command");
379 if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
380 t->q0 = addr.r.q0;
381 t->q1 = addr.r.q1;
382 return cmdexec(t, cp->u.cmd);
384 return TRUE;
387 int
388 i_cmd(Text *t, Cmd *cp)
390 return append(t->file, cp, addr.r.q0);
393 void
394 copy(File *f, Address addr2)
396 long p;
397 int ni;
398 Rune *buf;
400 buf = fbufalloc();
401 for(p=addr.r.q0; p<addr.r.q1; p+=ni){
402 ni = addr.r.q1-p;
403 if(ni > RBUFSIZE)
404 ni = RBUFSIZE;
405 bufread(&f->b, p, buf, ni);
406 eloginsert(addr2.f, addr2.r.q1, buf, ni);
408 fbuffree(buf);
411 void
412 move(File *f, Address addr2)
414 if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
415 elogdelete(f, addr.r.q0, addr.r.q1);
416 copy(f, addr2);
417 }else if(addr.r.q0 >= addr2.r.q1){
418 copy(f, addr2);
419 elogdelete(f, addr.r.q0, addr.r.q1);
420 }else
421 error("move overlaps itself");
424 int
425 m_cmd(Text *t, Cmd *cp)
427 Address dot, addr2;
429 mkaddr(&dot, t->file);
430 addr2 = cmdaddress(cp->u.mtaddr, dot, 0);
431 if(cp->cmdc == 'm')
432 move(t->file, addr2);
433 else
434 copy(t->file, addr2);
435 return TRUE;
438 int
439 p_cmd(Text *t, Cmd *cp)
441 USED(cp);
442 return pdisplay(t->file);
445 int
446 s_cmd(Text *t, Cmd *cp)
448 int i, j, k, c, m, n, nrp, didsub;
449 long p1, op, delta;
450 String *buf;
451 Rangeset *rp;
452 char *err;
453 Rune *rbuf;
455 n = cp->num;
456 op= -1;
457 if(rxcompile(cp->re->r) == FALSE)
458 editerror("bad regexp in s command");
459 nrp = 0;
460 rp = nil;
461 delta = 0;
462 didsub = FALSE;
463 for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
464 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
465 if(sel.r[0].q0 == op){
466 p1++;
467 continue;
469 p1 = sel.r[0].q1+1;
470 }else
471 p1 = sel.r[0].q1;
472 op = sel.r[0].q1;
473 if(--n>0)
474 continue;
475 nrp++;
476 rp = erealloc(rp, nrp*sizeof(Rangeset));
477 rp[nrp-1] = sel;
479 rbuf = fbufalloc();
480 buf = allocstring(0);
481 for(m=0; m<nrp; m++){
482 buf->n = 0;
483 buf->r[0] = L'\0';
484 sel = rp[m];
485 for(i = 0; i<cp->u.text->n; i++)
486 if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){
487 c = cp->u.text->r[++i];
488 if('1'<=c && c<='9') {
489 j = c-'0';
490 if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
491 err = "replacement string too long";
492 goto Err;
494 bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
495 for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
496 Straddc(buf, rbuf[k]);
497 }else
498 Straddc(buf, c);
499 }else if(c!='&')
500 Straddc(buf, c);
501 else{
502 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
503 err = "right hand side too long in substitution";
504 goto Err;
506 bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
507 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
508 Straddc(buf, rbuf[k]);
510 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
511 delta -= sel.r[0].q1-sel.r[0].q0;
512 delta += buf->n;
513 didsub = 1;
514 if(!cp->flag)
515 break;
517 free(rp);
518 freestring(buf);
519 fbuffree(rbuf);
520 if(!didsub && nest==0)
521 editerror("no substitution");
522 t->q0 = addr.r.q0;
523 t->q1 = addr.r.q1+delta;
524 return TRUE;
526 Err:
527 free(rp);
528 freestring(buf);
529 fbuffree(rbuf);
530 editerror(err);
531 return FALSE;
534 int
535 u_cmd(Text *t, Cmd *cp)
537 int n, oseq, flag;
539 n = cp->num;
540 flag = TRUE;
541 if(n < 0){
542 n = -n;
543 flag = FALSE;
545 oseq = -1;
546 while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){
547 oseq = t->file->seq;
548 undo(t, nil, nil, flag, 0, nil, 0);
550 return TRUE;
553 int
554 w_cmd(Text *t, Cmd *cp)
556 Rune *r;
557 File *f;
559 f = t->file;
560 if(f->seq == seq)
561 editerror("can't write file with pending modifications");
562 r = cmdname(f, cp->u.text, FALSE);
563 if(r == nil)
564 editerror("no name specified for 'w' command");
565 putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
566 /* r is freed by putfile */
567 return TRUE;
570 int
571 x_cmd(Text *t, Cmd *cp)
573 if(cp->re)
574 looper(t->file, cp, cp->cmdc=='x');
575 else
576 linelooper(t->file, cp);
577 return TRUE;
580 int
581 X_cmd(Text *t, Cmd *cp)
583 USED(t);
585 filelooper(cp, cp->cmdc=='X');
586 return TRUE;
589 void
590 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
592 Rune *r, *s;
593 int n;
594 Runestr dir;
595 Window *w;
597 r = skipbl(cr, ncr, &n);
598 if(n == 0)
599 editerror("no command specified for %c", cmd);
600 w = nil;
601 if(state == Inserting){
602 w = t->w;
603 t->q0 = addr.r.q0;
604 t->q1 = addr.r.q1;
605 w->neditwrsel = 0;
606 if(cmd == '<' || cmd=='|')
607 elogdelete(t->file, t->q0, t->q1);
609 s = runemalloc(n+2);
610 s[0] = cmd;
611 runemove(s+1, r, n);
612 n++;
613 dir.r = nil;
614 dir.nr = 0;
615 if(t != nil)
616 dir = dirname(t, nil, 0);
617 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
618 free(dir.r);
619 dir.r = nil;
620 dir.nr = 0;
622 editing = state;
623 if(t!=nil && t->w!=nil)
624 incref(&t->w->ref); /* run will decref */
625 run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
626 free(s);
627 if(t!=nil && t->w!=nil)
628 winunlock(t->w);
629 qunlock(&row.lk);
630 recvul(cedit);
631 qlock(&row.lk);
632 editing = Inactive;
633 if(t!=nil && t->w!=nil)
634 winlock(t->w, 'M');
635 if(state == Inserting){
636 t->q0 = addr.r.q0;
637 t->q1 = addr.r.q0 + t->w->neditwrsel;
641 int
642 pipe_cmd(Text *t, Cmd *cp)
644 runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
645 return TRUE;
648 long
649 nlcount(Text *t, long q0, long q1)
651 long nl;
652 Rune *buf;
653 int i, nbuf;
655 buf = fbufalloc();
656 nbuf = 0;
657 i = nl = 0;
658 while(q0 < q1){
659 if(i == nbuf){
660 nbuf = q1-q0;
661 if(nbuf > RBUFSIZE)
662 nbuf = RBUFSIZE;
663 bufread(&t->file->b, q0, buf, nbuf);
664 i = 0;
666 if(buf[i++] == '\n')
667 nl++;
668 q0++;
670 fbuffree(buf);
671 return nl;
674 void
675 printposn(Text *t, int charsonly)
677 long l1, l2;
679 if (t != nil && t->file != nil && t->file->name != nil)
680 warning(nil, "%.*S:", t->file->nname, t->file->name);
681 if(!charsonly){
682 l1 = 1+nlcount(t, 0, addr.r.q0);
683 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
684 /* check if addr ends with '\n' */
685 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
686 --l2;
687 warning(nil, "%lud", l1);
688 if(l2 != l1)
689 warning(nil, ",%lud", l2);
690 warning(nil, "\n");
691 return;
693 warning(nil, "#%d", addr.r.q0);
694 if(addr.r.q1 != addr.r.q0)
695 warning(nil, ",#%d", addr.r.q1);
696 warning(nil, "\n");
699 int
700 eq_cmd(Text *t, Cmd *cp)
702 int charsonly;
704 switch(cp->u.text->n){
705 case 0:
706 charsonly = FALSE;
707 break;
708 case 1:
709 if(cp->u.text->r[0] == '#'){
710 charsonly = TRUE;
711 break;
713 default:
714 SET(charsonly);
715 editerror("newline expected");
717 printposn(t, charsonly);
718 return TRUE;
721 int
722 nl_cmd(Text *t, Cmd *cp)
724 Address a;
725 File *f;
727 f = t->file;
728 if(cp->addr == 0){
729 /* First put it on newline boundaries */
730 mkaddr(&a, f);
731 addr = lineaddr(0, a, -1);
732 a = lineaddr(0, a, 1);
733 addr.r.q1 = a.r.q1;
734 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
735 mkaddr(&a, f);
736 addr = lineaddr(1, a, 1);
739 textshow(t, addr.r.q0, addr.r.q1, 1);
740 return TRUE;
743 int
744 append(File *f, Cmd *cp, long p)
746 if(cp->u.text->n > 0)
747 eloginsert(f, p, cp->u.text->r, cp->u.text->n);
748 f->curtext->q0 = p;
749 f->curtext->q1 = p+cp->u.text->n;
750 return TRUE;
753 int
754 pdisplay(File *f)
756 long p1, p2;
757 int np;
758 Rune *buf;
760 p1 = addr.r.q0;
761 p2 = addr.r.q1;
762 if(p2 > f->b.nc)
763 p2 = f->b.nc;
764 buf = fbufalloc();
765 while(p1 < p2){
766 np = p2-p1;
767 if(np>RBUFSIZE-1)
768 np = RBUFSIZE-1;
769 bufread(&f->b, p1, buf, np);
770 buf[np] = L'\0';
771 warning(nil, "%S", buf);
772 p1 += np;
774 fbuffree(buf);
775 f->curtext->q0 = addr.r.q0;
776 f->curtext->q1 = addr.r.q1;
777 return TRUE;
780 void
781 pfilename(File *f)
783 int dirty;
784 Window *w;
786 w = f->curtext->w;
787 /* same check for dirty as in settag, but we know ncache==0 */
788 dirty = !w->isdir && !w->isscratch && f->mod;
789 warning(nil, "%c%c%c %.*S\n", " '"[dirty],
790 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
793 void
794 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
796 long i;
798 for(i=0; i<nrp; i++){
799 f->curtext->q0 = rp[i].q0;
800 f->curtext->q1 = rp[i].q1;
801 cmdexec(f->curtext, cp);
805 void
806 looper(File *f, Cmd *cp, int xy)
808 long p, op, nrp;
809 Range r, tr;
810 Range *rp;
812 r = addr.r;
813 op= xy? -1 : r.q0;
814 nest++;
815 if(rxcompile(cp->re->r) == FALSE)
816 editerror("bad regexp in %c command", cp->cmdc);
817 nrp = 0;
818 rp = nil;
819 for(p = r.q0; p<=r.q1; ){
820 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
821 if(xy || op>r.q1)
822 break;
823 tr.q0 = op, tr.q1 = r.q1;
824 p = r.q1+1; /* exit next loop */
825 }else{
826 if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
827 if(sel.r[0].q0==op){
828 p++;
829 continue;
831 p = sel.r[0].q1+1;
832 }else
833 p = sel.r[0].q1;
834 if(xy)
835 tr = sel.r[0];
836 else
837 tr.q0 = op, tr.q1 = sel.r[0].q0;
839 op = sel.r[0].q1;
840 nrp++;
841 rp = erealloc(rp, nrp*sizeof(Range));
842 rp[nrp-1] = tr;
844 loopcmd(f, cp->u.cmd, rp, nrp);
845 free(rp);
846 --nest;
849 void
850 linelooper(File *f, Cmd *cp)
852 long nrp, p;
853 Range r, linesel;
854 Address a, a3;
855 Range *rp;
857 nest++;
858 nrp = 0;
859 rp = nil;
860 r = addr.r;
861 a3.f = f;
862 a3.r.q0 = a3.r.q1 = r.q0;
863 a = lineaddr(0, a3, 1);
864 linesel = a.r;
865 for(p = r.q0; p<r.q1; p = a3.r.q1){
866 a3.r.q0 = a3.r.q1;
867 if(p!=r.q0 || linesel.q1==p){
868 a = lineaddr(1, a3, 1);
869 linesel = a.r;
871 if(linesel.q0 >= r.q1)
872 break;
873 if(linesel.q1 >= r.q1)
874 linesel.q1 = r.q1;
875 if(linesel.q1 > linesel.q0)
876 if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
877 a3.r = linesel;
878 nrp++;
879 rp = erealloc(rp, nrp*sizeof(Range));
880 rp[nrp-1] = linesel;
881 continue;
883 break;
885 loopcmd(f, cp->u.cmd, rp, nrp);
886 free(rp);
887 --nest;
890 struct Looper
892 Cmd *cp;
893 int XY;
894 Window **w;
895 int nw;
896 } loopstruct; /* only one; X and Y can't nest */
898 void
899 alllooper(Window *w, void *v)
901 Text *t;
902 struct Looper *lp;
903 Cmd *cp;
905 lp = v;
906 cp = lp->cp;
907 // if(w->isscratch || w->isdir)
908 // return;
909 t = &w->body;
910 /* only use this window if it's the current window for the file */
911 if(t->file->curtext != t)
912 return;
913 // if(w->nopen[QWevent] > 0)
914 // return;
915 /* no auto-execute on files without names */
916 if(cp->re==nil && t->file->nname==0)
917 return;
918 if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
919 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
920 lp->w[lp->nw++] = w;
924 void
925 alllocker(Window *w, void *v)
927 if(v)
928 incref(&w->ref);
929 else
930 winclose(w);
933 void
934 filelooper(Cmd *cp, int XY)
936 int i;
938 if(Glooping++)
939 editerror("can't nest %c command", "YX"[XY]);
940 nest++;
942 loopstruct.cp = cp;
943 loopstruct.XY = XY;
944 if(loopstruct.w) /* error'ed out last time */
945 free(loopstruct.w);
946 loopstruct.w = nil;
947 loopstruct.nw = 0;
948 allwindows(alllooper, &loopstruct);
949 /*
950 * add a ref to all windows to keep safe windows accessed by X
951 * that would not otherwise have a ref to hold them up during
952 * the shenanigans. note this with globalincref so that any
953 * newly created windows start with an extra reference.
954 */
955 allwindows(alllocker, (void*)1);
956 globalincref = 1;
957 for(i=0; i<loopstruct.nw; i++)
958 cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
959 allwindows(alllocker, (void*)0);
960 globalincref = 0;
961 free(loopstruct.w);
962 loopstruct.w = nil;
964 --Glooping;
965 --nest;
968 void
969 nextmatch(File *f, String *r, long p, int sign)
971 if(rxcompile(r->r) == FALSE)
972 editerror("bad regexp in command address");
973 if(sign >= 0){
974 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
975 editerror("no match for regexp");
976 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
977 if(++p>f->b.nc)
978 p = 0;
979 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
980 editerror("address");
982 }else{
983 if(!rxbexecute(f->curtext, p, &sel))
984 editerror("no match for regexp");
985 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
986 if(--p<0)
987 p = f->b.nc;
988 if(!rxbexecute(f->curtext, p, &sel))
989 editerror("address");
994 File *matchfile(String*);
995 Address charaddr(long, Address, int);
996 Address lineaddr(long, Address, int);
998 Address
999 cmdaddress(Addr *ap, Address a, int sign)
1001 File *f = a.f;
1002 Address a1, a2;
1004 do{
1005 switch(ap->type){
1006 case 'l':
1007 case '#':
1008 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1009 break;
1011 case '.':
1012 mkaddr(&a, f);
1013 break;
1015 case '$':
1016 a.r.q0 = a.r.q1 = f->b.nc;
1017 break;
1019 case '\'':
1020 editerror("can't handle '");
1021 // a.r = f->mark;
1022 break;
1024 case '?':
1025 sign = -sign;
1026 if(sign == 0)
1027 sign = -1;
1028 /* fall through */
1029 case '/':
1030 nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
1031 a.r = sel.r[0];
1032 break;
1034 case '"':
1035 f = matchfile(ap->u.re);
1036 mkaddr(&a, f);
1037 break;
1039 case '*':
1040 a.r.q0 = 0, a.r.q1 = f->b.nc;
1041 return a;
1043 case ',':
1044 case ';':
1045 if(ap->u.left)
1046 a1 = cmdaddress(ap->u.left, a, 0);
1047 else
1048 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1049 if(ap->type == ';'){
1050 f = a1.f;
1051 a = a1;
1052 f->curtext->q0 = a1.r.q0;
1053 f->curtext->q1 = a1.r.q1;
1055 if(ap->next)
1056 a2 = cmdaddress(ap->next, a, 0);
1057 else
1058 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
1059 if(a1.f != a2.f)
1060 editerror("addresses in different files");
1061 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1062 if(a.r.q1 < a.r.q0)
1063 editerror("addresses out of order");
1064 return a;
1066 case '+':
1067 case '-':
1068 sign = 1;
1069 if(ap->type == '-')
1070 sign = -1;
1071 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1072 a = lineaddr(1L, a, sign);
1073 break;
1074 default:
1075 error("cmdaddress");
1076 return a;
1078 }while(ap = ap->next); /* assign = */
1079 return a;
1082 struct Tofile{
1083 File *f;
1084 String *r;
1087 void
1088 alltofile(Window *w, void *v)
1090 Text *t;
1091 struct Tofile *tp;
1093 tp = v;
1094 if(tp->f != nil)
1095 return;
1096 if(w->isscratch || w->isdir)
1097 return;
1098 t = &w->body;
1099 /* only use this window if it's the current window for the file */
1100 if(t->file->curtext != t)
1101 return;
1102 // if(w->nopen[QWevent] > 0)
1103 // return;
1104 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1105 tp->f = t->file;
1108 File*
1109 tofile(String *r)
1111 struct Tofile t;
1112 String rr;
1114 rr.r = skipbl(r->r, r->n, &rr.n);
1115 t.f = nil;
1116 t.r = &rr;
1117 allwindows(alltofile, &t);
1118 if(t.f == nil)
1119 editerror("no such file\"%S\"", rr.r);
1120 return t.f;
1123 void
1124 allmatchfile(Window *w, void *v)
1126 struct Tofile *tp;
1127 Text *t;
1129 tp = v;
1130 if(w->isscratch || w->isdir)
1131 return;
1132 t = &w->body;
1133 /* only use this window if it's the current window for the file */
1134 if(t->file->curtext != t)
1135 return;
1136 // if(w->nopen[QWevent] > 0)
1137 // return;
1138 if(filematch(w->body.file, tp->r)){
1139 if(tp->f != nil)
1140 editerror("too many files match \"%S\"", tp->r->r);
1141 tp->f = w->body.file;
1145 File*
1146 matchfile(String *r)
1148 struct Tofile tf;
1150 tf.f = nil;
1151 tf.r = r;
1152 allwindows(allmatchfile, &tf);
1154 if(tf.f == nil)
1155 editerror("no file matches \"%S\"", r->r);
1156 return tf.f;
1159 int
1160 filematch(File *f, String *r)
1162 char *buf;
1163 Rune *rbuf;
1164 Window *w;
1165 int match, i, dirty;
1166 Rangeset s;
1168 /* compile expr first so if we get an error, we haven't allocated anything */
1169 if(rxcompile(r->r) == FALSE)
1170 editerror("bad regexp in file match");
1171 buf = fbufalloc();
1172 w = f->curtext->w;
1173 /* same check for dirty as in settag, but we know ncache==0 */
1174 dirty = !w->isdir && !w->isscratch && f->mod;
1175 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1176 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1177 rbuf = bytetorune(buf, &i);
1178 fbuffree(buf);
1179 match = rxexecute(nil, rbuf, 0, i, &s);
1180 free(rbuf);
1181 return match;
1184 Address
1185 charaddr(long l, Address addr, int sign)
1187 if(sign == 0)
1188 addr.r.q0 = addr.r.q1 = l;
1189 else if(sign < 0)
1190 addr.r.q1 = addr.r.q0 -= l;
1191 else if(sign > 0)
1192 addr.r.q0 = addr.r.q1 += l;
1193 if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
1194 editerror("address out of range");
1195 return addr;
1198 Address
1199 lineaddr(long l, Address addr, int sign)
1201 int n;
1202 int c;
1203 File *f = addr.f;
1204 Address a;
1205 long p;
1207 a.f = f;
1208 if(sign >= 0){
1209 if(l == 0){
1210 if(sign==0 || addr.r.q1==0){
1211 a.r.q0 = a.r.q1 = 0;
1212 return a;
1214 a.r.q0 = addr.r.q1;
1215 p = addr.r.q1-1;
1216 }else{
1217 if(sign==0 || addr.r.q1==0){
1218 p = 0;
1219 n = 1;
1220 }else{
1221 p = addr.r.q1-1;
1222 n = textreadc(f->curtext, p++)=='\n';
1224 while(n < l){
1225 if(p >= f->b.nc)
1226 editerror("address out of range");
1227 if(textreadc(f->curtext, p++) == '\n')
1228 n++;
1230 a.r.q0 = p;
1232 while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1234 a.r.q1 = p;
1235 }else{
1236 p = addr.r.q0;
1237 if(l == 0)
1238 a.r.q1 = addr.r.q0;
1239 else{
1240 for(n = 0; n<l; ){ /* always runs once */
1241 if(p == 0){
1242 if(++n != l)
1243 editerror("address out of range");
1244 }else{
1245 c = textreadc(f->curtext, p-1);
1246 if(c != '\n' || ++n != l)
1247 p--;
1250 a.r.q1 = p;
1251 if(p > 0)
1252 p--;
1254 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1255 p--;
1256 a.r.q0 = p;
1258 return a;
1261 struct Filecheck
1263 File *f;
1264 Rune *r;
1265 int nr;
1268 void
1269 allfilecheck(Window *w, void *v)
1271 struct Filecheck *fp;
1272 File *f;
1274 fp = v;
1275 f = w->body.file;
1276 if(w->body.file == fp->f)
1277 return;
1278 if(runeeq(fp->r, fp->nr, f->name, f->nname))
1279 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1282 Rune*
1283 cmdname(File *f, String *str, int set)
1285 Rune *r, *s;
1286 int n;
1287 struct Filecheck fc;
1288 Runestr newname;
1290 r = nil;
1291 n = str->n;
1292 s = str->r;
1293 if(n == 0){
1294 /* no name; use existing */
1295 if(f->nname == 0)
1296 return nil;
1297 r = runemalloc(f->nname+1);
1298 runemove(r, f->name, f->nname);
1299 return r;
1301 s = skipbl(s, n, &n);
1302 if(n == 0)
1303 goto Return;
1305 if(s[0] == '/'){
1306 r = runemalloc(n+1);
1307 runemove(r, s, n);
1308 }else{
1309 newname = dirname(f->curtext, runestrdup(s), n);
1310 r = newname.r;
1311 n = newname.nr;
1313 fc.f = f;
1314 fc.r = r;
1315 fc.nr = n;
1316 allwindows(allfilecheck, &fc);
1317 if(f->nname == 0)
1318 set = TRUE;
1320 Return:
1321 if(set && !runeeq(r, n, f->name, f->nname)){
1322 filemark(f);
1323 f->mod = TRUE;
1324 f->curtext->w->dirty = TRUE;
1325 winsetname(f->curtext->w, r, n);
1327 return r;