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 <libsec.h>
12 #include "dat.h"
13 #include "edit.h"
14 #include "fns.h"
16 int Glooping;
17 int nest;
18 char Enoname[] = "no file name given";
20 Address addr;
21 File *menu;
22 Rangeset sel;
23 extern Text* curtext;
24 Rune *collection;
25 int ncollection;
27 int append(File*, Cmd*, long);
28 int pdisplay(File*);
29 void pfilename(File*);
30 void looper(File*, Cmd*, int);
31 void filelooper(Cmd*, int);
32 void linelooper(File*, Cmd*);
33 Address lineaddr(long, Address, int);
34 int filematch(File*, String*);
35 File *tofile(String*);
36 Rune* cmdname(File *f, String *s, int);
37 void runpipe(Text*, int, Rune*, int, int);
39 void
40 clearcollection(void)
41 {
42 free(collection);
43 collection = nil;
44 ncollection = 0;
45 }
47 void
48 resetxec(void)
49 {
50 Glooping = nest = 0;
51 clearcollection();
52 }
54 void
55 mkaddr(Address *a, File *f)
56 {
57 a->r.q0 = f->curtext->q0;
58 a->r.q1 = f->curtext->q1;
59 a->f = f;
60 }
62 int
63 cmdexec(Text *t, Cmd *cp)
64 {
65 int i;
66 Addr *ap;
67 File *f;
68 Window *w;
69 Address dot;
71 if(t == nil)
72 w = nil;
73 else
74 w = t->w;
75 if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
76 !utfrune("bBnqUXY!", cp->cmdc) &&
77 !(cp->cmdc=='D' && cp->u.text))
78 editerror("no current window");
79 i = cmdlookup(cp->cmdc); /* will be -1 for '{' */
80 f = nil;
81 if(t && t->w){
82 t = &t->w->body;
83 f = t->file;
84 f->curtext = t;
85 }
86 if(i>=0 && cmdtab[i].defaddr != aNo){
87 if((ap=cp->addr)==0 && cp->cmdc!='\n'){
88 cp->addr = ap = newaddr();
89 ap->type = '.';
90 if(cmdtab[i].defaddr == aAll)
91 ap->type = '*';
92 }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
93 ap->next = newaddr();
94 ap->next->type = '.';
95 if(cmdtab[i].defaddr == aAll)
96 ap->next->type = '*';
97 }
98 if(cp->addr){ /* may be false for '\n' (only) */
99 static Address none = {0,0,nil};
100 if(f){
101 mkaddr(&dot, f);
102 addr = cmdaddress(ap, dot, 0);
103 }else /* a " */
104 addr = cmdaddress(ap, none, 0);
105 f = addr.f;
106 t = f->curtext;
109 switch(cp->cmdc){
110 case '{':
111 mkaddr(&dot, f);
112 if(cp->addr != nil)
113 dot = cmdaddress(cp->addr, dot, 0);
114 for(cp = cp->u.cmd; cp; cp = cp->next){
115 if(dot.r.q1 > t->file->b.nc)
116 editerror("dot extends past end of buffer during { command");
117 t->q0 = dot.r.q0;
118 t->q1 = dot.r.q1;
119 cmdexec(t, cp);
121 break;
122 default:
123 if(i < 0)
124 editerror("unknown command %c in cmdexec", cp->cmdc);
125 i = (*cmdtab[i].fn)(t, cp);
126 return i;
128 return 1;
131 char*
132 edittext(Window *w, int q, Rune *r, int nr)
134 File *f;
136 f = w->body.file;
137 switch(editing){
138 case Inactive:
139 return "permission denied";
140 case Inserting:
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.q1;
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, nil);
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 if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
421 ; /* move to self; no-op */
422 }else
423 editerror("move overlaps itself");
426 int
427 m_cmd(Text *t, Cmd *cp)
429 Address dot, addr2;
431 mkaddr(&dot, t->file);
432 addr2 = cmdaddress(cp->u.mtaddr, dot, 0);
433 if(cp->cmdc == 'm')
434 move(t->file, addr2);
435 else
436 copy(t->file, addr2);
437 return TRUE;
440 int
441 p_cmd(Text *t, Cmd *cp)
443 USED(cp);
444 return pdisplay(t->file);
447 int
448 s_cmd(Text *t, Cmd *cp)
450 int i, j, k, c, m, n, nrp, didsub;
451 long p1, op, delta;
452 String *buf;
453 Rangeset *rp;
454 char *err;
455 Rune *rbuf;
457 n = cp->num;
458 op= -1;
459 if(rxcompile(cp->re->r) == FALSE)
460 editerror("bad regexp in s command");
461 nrp = 0;
462 rp = nil;
463 delta = 0;
464 didsub = FALSE;
465 for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
466 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
467 if(sel.r[0].q0 == op){
468 p1++;
469 continue;
471 p1 = sel.r[0].q1+1;
472 }else
473 p1 = sel.r[0].q1;
474 op = sel.r[0].q1;
475 if(--n>0)
476 continue;
477 nrp++;
478 rp = erealloc(rp, nrp*sizeof(Rangeset));
479 rp[nrp-1] = sel;
481 rbuf = fbufalloc();
482 buf = allocstring(0);
483 for(m=0; m<nrp; m++){
484 buf->n = 0;
485 buf->r[0] = '\0';
486 sel = rp[m];
487 for(i = 0; i<cp->u.text->n; i++)
488 if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){
489 c = cp->u.text->r[++i];
490 if('1'<=c && c<='9') {
491 j = c-'0';
492 if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
493 err = "replacement string too long";
494 goto Err;
496 bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
497 for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
498 Straddc(buf, rbuf[k]);
499 }else
500 Straddc(buf, c);
501 }else if(c!='&')
502 Straddc(buf, c);
503 else{
504 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
505 err = "right hand side too long in substitution";
506 goto Err;
508 bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
509 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
510 Straddc(buf, rbuf[k]);
512 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
513 delta -= sel.r[0].q1-sel.r[0].q0;
514 delta += buf->n;
515 didsub = 1;
516 if(!cp->flag)
517 break;
519 free(rp);
520 freestring(buf);
521 fbuffree(rbuf);
522 if(!didsub && nest==0)
523 editerror("no substitution");
524 t->q0 = addr.r.q0;
525 t->q1 = addr.r.q1;
526 return TRUE;
528 Err:
529 free(rp);
530 freestring(buf);
531 fbuffree(rbuf);
532 editerror(err);
533 return FALSE;
536 int
537 u_cmd(Text *t, Cmd *cp)
539 int n, oseq, flag;
541 n = cp->num;
542 flag = TRUE;
543 if(n < 0){
544 n = -n;
545 flag = FALSE;
547 oseq = -1;
548 while(n-->0 && t->file->seq!=oseq){
549 oseq = t->file->seq;
550 undo(t, nil, nil, flag, 0, nil, 0);
552 return TRUE;
555 int
556 w_cmd(Text *t, Cmd *cp)
558 Rune *r;
559 File *f;
561 f = t->file;
562 if(f->seq == seq)
563 editerror("can't write file with pending modifications");
564 r = cmdname(f, cp->u.text, FALSE);
565 if(r == nil)
566 editerror("no name specified for 'w' command");
567 putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
568 /* r is freed by putfile */
569 return TRUE;
572 int
573 x_cmd(Text *t, Cmd *cp)
575 if(cp->re)
576 looper(t->file, cp, cp->cmdc=='x');
577 else
578 linelooper(t->file, cp);
579 return TRUE;
582 int
583 X_cmd(Text *t, Cmd *cp)
585 USED(t);
587 filelooper(cp, cp->cmdc=='X');
588 return TRUE;
591 void
592 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
594 Rune *r, *s;
595 int n;
596 Runestr dir;
597 Window *w;
598 QLock *q;
600 r = skipbl(cr, ncr, &n);
601 if(n == 0)
602 editerror("no command specified for %c", cmd);
603 w = nil;
604 if(state == Inserting){
605 w = t->w;
606 t->q0 = addr.r.q0;
607 t->q1 = addr.r.q1;
608 if(cmd == '<' || cmd=='|')
609 elogdelete(t->file, t->q0, t->q1);
611 s = runemalloc(n+2);
612 s[0] = cmd;
613 runemove(s+1, r, n);
614 n++;
615 dir.r = nil;
616 dir.nr = 0;
617 if(t != nil)
618 dir = dirname(t, nil, 0);
619 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
620 free(dir.r);
621 dir.r = nil;
622 dir.nr = 0;
624 editing = state;
625 if(t!=nil && t->w!=nil)
626 incref(&t->w->ref); /* run will decref */
627 run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
628 free(s);
629 if(t!=nil && t->w!=nil)
630 winunlock(t->w);
631 qunlock(&row.lk);
632 recvul(cedit);
633 /*
634 * The editoutlk exists only so that we can tell when
635 * the editout file has been closed. It can get closed *after*
636 * the process exits because, since the process cannot be
637 * connected directly to editout (no 9P kernel support),
638 * the process is actually connected to a pipe to another
639 * process (arranged via 9pserve) that reads from the pipe
640 * and then writes the data in the pipe to editout using
641 * 9P transactions. This process might still have a couple
642 * writes left to copy after the original process has exited.
643 */
644 if(w)
645 q = &w->editoutlk;
646 else
647 q = &editoutlk;
648 qlock(q); /* wait for file to close */
649 qunlock(q);
650 qlock(&row.lk);
651 editing = Inactive;
652 if(t!=nil && t->w!=nil)
653 winlock(t->w, 'M');
656 int
657 pipe_cmd(Text *t, Cmd *cp)
659 runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
660 return TRUE;
663 long
664 nlcount(Text *t, long q0, long q1, long *pnr)
666 long nl, start;
667 Rune *buf;
668 int i, nbuf;
670 buf = fbufalloc();
671 nbuf = 0;
672 i = nl = 0;
673 start = q0;
674 while(q0 < q1){
675 if(i == nbuf){
676 nbuf = q1-q0;
677 if(nbuf > RBUFSIZE)
678 nbuf = RBUFSIZE;
679 bufread(&t->file->b, q0, buf, nbuf);
680 i = 0;
682 if(buf[i++] == '\n') {
683 start = q0+1;
684 nl++;
686 q0++;
688 fbuffree(buf);
689 if(pnr != nil)
690 *pnr = q0 - start;
691 return nl;
694 enum {
695 PosnLine = 0,
696 PosnChars = 1,
697 PosnLineChars = 2,
698 };
700 void
701 printposn(Text *t, int mode)
703 long l1, l2, r1, r2;
705 if (t != nil && t->file != nil && t->file->name != nil)
706 warning(nil, "%.*S:", t->file->nname, t->file->name);
708 switch(mode) {
709 case PosnChars:
710 warning(nil, "#%d", addr.r.q0);
711 if(addr.r.q1 != addr.r.q0)
712 warning(nil, ",#%d", addr.r.q1);
713 warning(nil, "\n");
714 return;
716 default:
717 case PosnLine:
718 l1 = 1+nlcount(t, 0, addr.r.q0, nil);
719 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, nil);
720 /* check if addr ends with '\n' */
721 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
722 --l2;
723 warning(nil, "%lud", l1);
724 if(l2 != l1)
725 warning(nil, ",%lud", l2);
726 warning(nil, "\n");
727 return;
729 case PosnLineChars:
730 l1 = 1+nlcount(t, 0, addr.r.q0, &r1);
731 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, &r2);
732 if(l2 == l1)
733 r2 += r1;
734 warning(nil, "%lud+#%d", l1, r1);
735 if(l2 != l1)
736 warning(nil, ",%lud+#%d", l2, r2);
737 warning(nil, "\n");
738 return;
742 int
743 eq_cmd(Text *t, Cmd *cp)
745 int mode;
747 switch(cp->u.text->n){
748 case 0:
749 mode = PosnLine;
750 break;
751 case 1:
752 if(cp->u.text->r[0] == '#'){
753 mode = PosnChars;
754 break;
756 if(cp->u.text->r[0] == '+'){
757 mode = PosnLineChars;
758 break;
760 default:
761 SET(mode);
762 editerror("newline expected");
764 printposn(t, mode);
765 return TRUE;
768 int
769 nl_cmd(Text *t, Cmd *cp)
771 Address a;
772 File *f;
774 f = t->file;
775 if(cp->addr == 0){
776 /* First put it on newline boundaries */
777 mkaddr(&a, f);
778 addr = lineaddr(0, a, -1);
779 a = lineaddr(0, a, 1);
780 addr.r.q1 = a.r.q1;
781 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
782 mkaddr(&a, f);
783 addr = lineaddr(1, a, 1);
786 textshow(t, addr.r.q0, addr.r.q1, 1);
787 return TRUE;
790 int
791 append(File *f, Cmd *cp, long p)
793 if(cp->u.text->n > 0)
794 eloginsert(f, p, cp->u.text->r, cp->u.text->n);
795 f->curtext->q0 = p;
796 f->curtext->q1 = p;
797 return TRUE;
800 int
801 pdisplay(File *f)
803 long p1, p2;
804 int np;
805 Rune *buf;
807 p1 = addr.r.q0;
808 p2 = addr.r.q1;
809 if(p2 > f->b.nc)
810 p2 = f->b.nc;
811 buf = fbufalloc();
812 while(p1 < p2){
813 np = p2-p1;
814 if(np>RBUFSIZE-1)
815 np = RBUFSIZE-1;
816 bufread(&f->b, p1, buf, np);
817 buf[np] = '\0';
818 warning(nil, "%S", buf);
819 p1 += np;
821 fbuffree(buf);
822 f->curtext->q0 = addr.r.q0;
823 f->curtext->q1 = addr.r.q1;
824 return TRUE;
827 void
828 pfilename(File *f)
830 int dirty;
831 Window *w;
833 w = f->curtext->w;
834 /* same check for dirty as in settag, but we know ncache==0 */
835 dirty = !w->isdir && !w->isscratch && f->mod;
836 warning(nil, "%c%c%c %.*S\n", " '"[dirty],
837 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
840 void
841 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
843 long i;
845 for(i=0; i<nrp; i++){
846 f->curtext->q0 = rp[i].q0;
847 f->curtext->q1 = rp[i].q1;
848 cmdexec(f->curtext, cp);
852 void
853 looper(File *f, Cmd *cp, int xy)
855 long p, op, nrp;
856 Range r, tr;
857 Range *rp;
859 r = addr.r;
860 op= xy? -1 : r.q0;
861 nest++;
862 if(rxcompile(cp->re->r) == FALSE)
863 editerror("bad regexp in %c command", cp->cmdc);
864 nrp = 0;
865 rp = nil;
866 for(p = r.q0; p<=r.q1; ){
867 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
868 if(xy || op>r.q1)
869 break;
870 tr.q0 = op, tr.q1 = r.q1;
871 p = r.q1+1; /* exit next loop */
872 }else{
873 if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
874 if(sel.r[0].q0==op){
875 p++;
876 continue;
878 p = sel.r[0].q1+1;
879 }else
880 p = sel.r[0].q1;
881 if(xy)
882 tr = sel.r[0];
883 else
884 tr.q0 = op, tr.q1 = sel.r[0].q0;
886 op = sel.r[0].q1;
887 nrp++;
888 rp = erealloc(rp, nrp*sizeof(Range));
889 rp[nrp-1] = tr;
891 loopcmd(f, cp->u.cmd, rp, nrp);
892 free(rp);
893 --nest;
896 void
897 linelooper(File *f, Cmd *cp)
899 long nrp, p;
900 Range r, linesel;
901 Address a, a3;
902 Range *rp;
904 nest++;
905 nrp = 0;
906 rp = nil;
907 r = addr.r;
908 a3.f = f;
909 a3.r.q0 = a3.r.q1 = r.q0;
910 a = lineaddr(0, a3, 1);
911 linesel = a.r;
912 for(p = r.q0; p<r.q1; p = a3.r.q1){
913 a3.r.q0 = a3.r.q1;
914 if(p!=r.q0 || linesel.q1==p){
915 a = lineaddr(1, a3, 1);
916 linesel = a.r;
918 if(linesel.q0 >= r.q1)
919 break;
920 if(linesel.q1 >= r.q1)
921 linesel.q1 = r.q1;
922 if(linesel.q1 > linesel.q0)
923 if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
924 a3.r = linesel;
925 nrp++;
926 rp = erealloc(rp, nrp*sizeof(Range));
927 rp[nrp-1] = linesel;
928 continue;
930 break;
932 loopcmd(f, cp->u.cmd, rp, nrp);
933 free(rp);
934 --nest;
937 struct Looper
939 Cmd *cp;
940 int XY;
941 Window **w;
942 int nw;
943 } loopstruct; /* only one; X and Y can't nest */
945 void
946 alllooper(Window *w, void *v)
948 Text *t;
949 struct Looper *lp;
950 Cmd *cp;
952 lp = v;
953 cp = lp->cp;
954 /* if(w->isscratch || w->isdir) */
955 /* return; */
956 t = &w->body;
957 /* only use this window if it's the current window for the file */
958 if(t->file->curtext != t)
959 return;
960 /* if(w->nopen[QWevent] > 0) */
961 /* return; */
962 /* no auto-execute on files without names */
963 if(cp->re==nil && t->file->nname==0)
964 return;
965 if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
966 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
967 lp->w[lp->nw++] = w;
971 void
972 alllocker(Window *w, void *v)
974 if(v)
975 incref(&w->ref);
976 else
977 winclose(w);
980 void
981 filelooper(Cmd *cp, int XY)
983 int i;
985 if(Glooping++)
986 editerror("can't nest %c command", "YX"[XY]);
987 nest++;
989 loopstruct.cp = cp;
990 loopstruct.XY = XY;
991 if(loopstruct.w) /* error'ed out last time */
992 free(loopstruct.w);
993 loopstruct.w = nil;
994 loopstruct.nw = 0;
995 allwindows(alllooper, &loopstruct);
996 /*
997 * add a ref to all windows to keep safe windows accessed by X
998 * that would not otherwise have a ref to hold them up during
999 * the shenanigans. note this with globalincref so that any
1000 * newly created windows start with an extra reference.
1002 allwindows(alllocker, (void*)1);
1003 globalincref = 1;
1004 for(i=0; i<loopstruct.nw; i++)
1005 cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
1006 allwindows(alllocker, (void*)0);
1007 globalincref = 0;
1008 free(loopstruct.w);
1009 loopstruct.w = nil;
1011 --Glooping;
1012 --nest;
1015 void
1016 nextmatch(File *f, String *r, long p, int sign)
1018 if(rxcompile(r->r) == FALSE)
1019 editerror("bad regexp in command address");
1020 if(sign >= 0){
1021 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1022 editerror("no match for regexp");
1023 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
1024 if(++p>f->b.nc)
1025 p = 0;
1026 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1027 editerror("address");
1029 }else{
1030 if(!rxbexecute(f->curtext, p, &sel))
1031 editerror("no match for regexp");
1032 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
1033 if(--p<0)
1034 p = f->b.nc;
1035 if(!rxbexecute(f->curtext, p, &sel))
1036 editerror("address");
1041 File *matchfile(String*);
1042 Address charaddr(long, Address, int);
1043 Address lineaddr(long, Address, int);
1045 Address
1046 cmdaddress(Addr *ap, Address a, int sign)
1048 File *f = a.f;
1049 Address a1, a2;
1051 do{
1052 switch(ap->type){
1053 case 'l':
1054 case '#':
1055 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1056 break;
1058 case '.':
1059 mkaddr(&a, f);
1060 break;
1062 case '$':
1063 a.r.q0 = a.r.q1 = f->b.nc;
1064 break;
1066 case '\'':
1067 editerror("can't handle '");
1068 /* a.r = f->mark; */
1069 break;
1071 case '?':
1072 sign = -sign;
1073 if(sign == 0)
1074 sign = -1;
1075 /* fall through */
1076 case '/':
1077 nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
1078 a.r = sel.r[0];
1079 break;
1081 case '"':
1082 f = matchfile(ap->u.re);
1083 mkaddr(&a, f);
1084 break;
1086 case '*':
1087 a.r.q0 = 0, a.r.q1 = f->b.nc;
1088 return a;
1090 case ',':
1091 case ';':
1092 if(ap->u.left)
1093 a1 = cmdaddress(ap->u.left, a, 0);
1094 else
1095 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1096 if(ap->type == ';'){
1097 f = a1.f;
1098 a = a1;
1099 f->curtext->q0 = a1.r.q0;
1100 f->curtext->q1 = a1.r.q1;
1102 if(ap->next)
1103 a2 = cmdaddress(ap->next, a, 0);
1104 else
1105 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
1106 if(a1.f != a2.f)
1107 editerror("addresses in different files");
1108 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1109 if(a.r.q1 < a.r.q0)
1110 editerror("addresses out of order");
1111 return a;
1113 case '+':
1114 case '-':
1115 sign = 1;
1116 if(ap->type == '-')
1117 sign = -1;
1118 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1119 a = lineaddr(1L, a, sign);
1120 break;
1121 default:
1122 error("cmdaddress");
1123 return a;
1125 }while(ap = ap->next); /* assign = */
1126 return a;
1129 struct Tofile{
1130 File *f;
1131 String *r;
1134 void
1135 alltofile(Window *w, void *v)
1137 Text *t;
1138 struct Tofile *tp;
1140 tp = v;
1141 if(tp->f != nil)
1142 return;
1143 if(w->isscratch || w->isdir)
1144 return;
1145 t = &w->body;
1146 /* only use this window if it's the current window for the file */
1147 if(t->file->curtext != t)
1148 return;
1149 /* if(w->nopen[QWevent] > 0) */
1150 /* return; */
1151 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1152 tp->f = t->file;
1155 File*
1156 tofile(String *r)
1158 struct Tofile t;
1159 String rr;
1161 rr.r = skipbl(r->r, r->n, &rr.n);
1162 t.f = nil;
1163 t.r = &rr;
1164 allwindows(alltofile, &t);
1165 if(t.f == nil)
1166 editerror("no such file\"%S\"", rr.r);
1167 return t.f;
1170 void
1171 allmatchfile(Window *w, void *v)
1173 struct Tofile *tp;
1174 Text *t;
1176 tp = v;
1177 if(w->isscratch || w->isdir)
1178 return;
1179 t = &w->body;
1180 /* only use this window if it's the current window for the file */
1181 if(t->file->curtext != t)
1182 return;
1183 /* if(w->nopen[QWevent] > 0) */
1184 /* return; */
1185 if(filematch(w->body.file, tp->r)){
1186 if(tp->f != nil)
1187 editerror("too many files match \"%S\"", tp->r->r);
1188 tp->f = w->body.file;
1192 File*
1193 matchfile(String *r)
1195 struct Tofile tf;
1197 tf.f = nil;
1198 tf.r = r;
1199 allwindows(allmatchfile, &tf);
1201 if(tf.f == nil)
1202 editerror("no file matches \"%S\"", r->r);
1203 return tf.f;
1206 int
1207 filematch(File *f, String *r)
1209 char *buf;
1210 Rune *rbuf;
1211 Window *w;
1212 int match, i, dirty;
1213 Rangeset s;
1215 /* compile expr first so if we get an error, we haven't allocated anything */
1216 if(rxcompile(r->r) == FALSE)
1217 editerror("bad regexp in file match");
1218 buf = fbufalloc();
1219 w = f->curtext->w;
1220 /* same check for dirty as in settag, but we know ncache==0 */
1221 dirty = !w->isdir && !w->isscratch && f->mod;
1222 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1223 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1224 rbuf = bytetorune(buf, &i);
1225 fbuffree(buf);
1226 match = rxexecute(nil, rbuf, 0, i, &s);
1227 free(rbuf);
1228 return match;
1231 Address
1232 charaddr(long l, Address addr, int sign)
1234 if(sign == 0)
1235 addr.r.q0 = addr.r.q1 = l;
1236 else if(sign < 0)
1237 addr.r.q1 = addr.r.q0 -= l;
1238 else if(sign > 0)
1239 addr.r.q0 = addr.r.q1 += l;
1240 if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
1241 editerror("address out of range");
1242 return addr;
1245 Address
1246 lineaddr(long l, Address addr, int sign)
1248 int n;
1249 int c;
1250 File *f = addr.f;
1251 Address a;
1252 long p;
1254 a.f = f;
1255 if(sign >= 0){
1256 if(l == 0){
1257 if(sign==0 || addr.r.q1==0){
1258 a.r.q0 = a.r.q1 = 0;
1259 return a;
1261 a.r.q0 = addr.r.q1;
1262 p = addr.r.q1-1;
1263 }else{
1264 if(sign==0 || addr.r.q1==0){
1265 p = 0;
1266 n = 1;
1267 }else{
1268 p = addr.r.q1-1;
1269 n = textreadc(f->curtext, p++)=='\n';
1271 while(n < l){
1272 if(p >= f->b.nc)
1273 editerror("address out of range");
1274 if(textreadc(f->curtext, p++) == '\n')
1275 n++;
1277 a.r.q0 = p;
1279 while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1281 a.r.q1 = p;
1282 }else{
1283 p = addr.r.q0;
1284 if(l == 0)
1285 a.r.q1 = addr.r.q0;
1286 else{
1287 for(n = 0; n<l; ){ /* always runs once */
1288 if(p == 0){
1289 if(++n != l)
1290 editerror("address out of range");
1291 }else{
1292 c = textreadc(f->curtext, p-1);
1293 if(c != '\n' || ++n != l)
1294 p--;
1297 a.r.q1 = p;
1298 if(p > 0)
1299 p--;
1301 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1302 p--;
1303 a.r.q0 = p;
1305 return a;
1308 struct Filecheck
1310 File *f;
1311 Rune *r;
1312 int nr;
1315 void
1316 allfilecheck(Window *w, void *v)
1318 struct Filecheck *fp;
1319 File *f;
1321 fp = v;
1322 f = w->body.file;
1323 if(w->body.file == fp->f)
1324 return;
1325 if(runeeq(fp->r, fp->nr, f->name, f->nname))
1326 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1329 Rune*
1330 cmdname(File *f, String *str, int set)
1332 Rune *r, *s;
1333 int n;
1334 struct Filecheck fc;
1335 Runestr newname;
1337 r = nil;
1338 n = str->n;
1339 s = str->r;
1340 if(n == 0){
1341 /* no name; use existing */
1342 if(f->nname == 0)
1343 return nil;
1344 r = runemalloc(f->nname+1);
1345 runemove(r, f->name, f->nname);
1346 return r;
1348 s = skipbl(s, n, &n);
1349 if(n == 0)
1350 goto Return;
1352 if(s[0] == '/'){
1353 r = runemalloc(n+1);
1354 runemove(r, s, n);
1355 }else{
1356 newname = dirname(f->curtext, runestrdup(s), n);
1357 n = newname.nr;
1358 r = runemalloc(n+1); /* NUL terminate */
1359 runemove(r, newname.r, n);
1360 free(newname.r);
1362 fc.f = f;
1363 fc.r = r;
1364 fc.nr = n;
1365 allwindows(allfilecheck, &fc);
1366 if(f->nname == 0)
1367 set = TRUE;
1369 Return:
1370 if(set && !runeeq(r, n, f->name, f->nname)){
1371 filemark(f);
1372 f->mod = TRUE;
1373 f->curtext->w->dirty = TRUE;
1374 winsetname(f->curtext->w, r, n);
1376 return r;