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(Text*, 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(t, 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(Text *t, Cmd *cp, int XY)
983 int i;
984 Text *targ;
986 if(Glooping++)
987 editerror("can't nest %c command", "YX"[XY]);
988 nest++;
990 loopstruct.cp = cp;
991 loopstruct.XY = XY;
992 if(loopstruct.w) /* error'ed out last time */
993 free(loopstruct.w);
994 loopstruct.w = nil;
995 loopstruct.nw = 0;
996 allwindows(alllooper, &loopstruct);
997 /*
998 * add a ref to all windows to keep safe windows accessed by X
999 * that would not otherwise have a ref to hold them up during
1000 * the shenanigans. note this with globalincref so that any
1001 * newly created windows start with an extra reference.
1003 allwindows(alllocker, (void*)1);
1004 globalincref = 1;
1007 * Unlock the window running the X command.
1008 * We'll need to lock and unlock each target window in turn.
1010 if(t && t->w)
1011 winunlock(t->w);
1013 for(i=0; i<loopstruct.nw; i++) {
1014 targ = &loopstruct.w[i]->body;
1015 if(targ && targ->w)
1016 winlock(targ->w, cp->cmdc);
1017 cmdexec(targ, cp->u.cmd);
1018 if(targ && targ->w)
1019 winunlock(targ->w);
1022 if(t && t->w)
1023 winlock(t->w, cp->cmdc);
1025 allwindows(alllocker, (void*)0);
1026 globalincref = 0;
1027 free(loopstruct.w);
1028 loopstruct.w = nil;
1030 --Glooping;
1031 --nest;
1034 void
1035 nextmatch(File *f, String *r, long p, int sign)
1037 if(rxcompile(r->r) == FALSE)
1038 editerror("bad regexp in command address");
1039 if(sign >= 0){
1040 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1041 editerror("no match for regexp");
1042 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
1043 if(++p>f->b.nc)
1044 p = 0;
1045 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1046 editerror("address");
1048 }else{
1049 if(!rxbexecute(f->curtext, p, &sel))
1050 editerror("no match for regexp");
1051 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
1052 if(--p<0)
1053 p = f->b.nc;
1054 if(!rxbexecute(f->curtext, p, &sel))
1055 editerror("address");
1060 File *matchfile(String*);
1061 Address charaddr(long, Address, int);
1062 Address lineaddr(long, Address, int);
1064 Address
1065 cmdaddress(Addr *ap, Address a, int sign)
1067 File *f = a.f;
1068 Address a1, a2;
1070 do{
1071 switch(ap->type){
1072 case 'l':
1073 case '#':
1074 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1075 break;
1077 case '.':
1078 mkaddr(&a, f);
1079 break;
1081 case '$':
1082 a.r.q0 = a.r.q1 = f->b.nc;
1083 break;
1085 case '\'':
1086 editerror("can't handle '");
1087 /* a.r = f->mark; */
1088 break;
1090 case '?':
1091 sign = -sign;
1092 if(sign == 0)
1093 sign = -1;
1094 /* fall through */
1095 case '/':
1096 nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
1097 a.r = sel.r[0];
1098 break;
1100 case '"':
1101 f = matchfile(ap->u.re);
1102 mkaddr(&a, f);
1103 break;
1105 case '*':
1106 a.r.q0 = 0, a.r.q1 = f->b.nc;
1107 return a;
1109 case ',':
1110 case ';':
1111 if(ap->u.left)
1112 a1 = cmdaddress(ap->u.left, a, 0);
1113 else
1114 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1115 if(ap->type == ';'){
1116 f = a1.f;
1117 a = a1;
1118 f->curtext->q0 = a1.r.q0;
1119 f->curtext->q1 = a1.r.q1;
1121 if(ap->next)
1122 a2 = cmdaddress(ap->next, a, 0);
1123 else
1124 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
1125 if(a1.f != a2.f)
1126 editerror("addresses in different files");
1127 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1128 if(a.r.q1 < a.r.q0)
1129 editerror("addresses out of order");
1130 return a;
1132 case '+':
1133 case '-':
1134 sign = 1;
1135 if(ap->type == '-')
1136 sign = -1;
1137 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1138 a = lineaddr(1L, a, sign);
1139 break;
1140 default:
1141 error("cmdaddress");
1142 return a;
1144 }while(ap = ap->next); /* assign = */
1145 return a;
1148 struct Tofile{
1149 File *f;
1150 String *r;
1153 void
1154 alltofile(Window *w, void *v)
1156 Text *t;
1157 struct Tofile *tp;
1159 tp = v;
1160 if(tp->f != nil)
1161 return;
1162 if(w->isscratch || w->isdir)
1163 return;
1164 t = &w->body;
1165 /* only use this window if it's the current window for the file */
1166 if(t->file->curtext != t)
1167 return;
1168 /* if(w->nopen[QWevent] > 0) */
1169 /* return; */
1170 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1171 tp->f = t->file;
1174 File*
1175 tofile(String *r)
1177 struct Tofile t;
1178 String rr;
1180 rr.r = skipbl(r->r, r->n, &rr.n);
1181 t.f = nil;
1182 t.r = &rr;
1183 allwindows(alltofile, &t);
1184 if(t.f == nil)
1185 editerror("no such file\"%S\"", rr.r);
1186 return t.f;
1189 void
1190 allmatchfile(Window *w, void *v)
1192 struct Tofile *tp;
1193 Text *t;
1195 tp = v;
1196 if(w->isscratch || w->isdir)
1197 return;
1198 t = &w->body;
1199 /* only use this window if it's the current window for the file */
1200 if(t->file->curtext != t)
1201 return;
1202 /* if(w->nopen[QWevent] > 0) */
1203 /* return; */
1204 if(filematch(w->body.file, tp->r)){
1205 if(tp->f != nil)
1206 editerror("too many files match \"%S\"", tp->r->r);
1207 tp->f = w->body.file;
1211 File*
1212 matchfile(String *r)
1214 struct Tofile tf;
1216 tf.f = nil;
1217 tf.r = r;
1218 allwindows(allmatchfile, &tf);
1220 if(tf.f == nil)
1221 editerror("no file matches \"%S\"", r->r);
1222 return tf.f;
1225 int
1226 filematch(File *f, String *r)
1228 char *buf;
1229 Rune *rbuf;
1230 Window *w;
1231 int match, i, dirty;
1232 Rangeset s;
1234 /* compile expr first so if we get an error, we haven't allocated anything */
1235 if(rxcompile(r->r) == FALSE)
1236 editerror("bad regexp in file match");
1237 buf = fbufalloc();
1238 w = f->curtext->w;
1239 /* same check for dirty as in settag, but we know ncache==0 */
1240 dirty = !w->isdir && !w->isscratch && f->mod;
1241 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1242 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1243 rbuf = bytetorune(buf, &i);
1244 fbuffree(buf);
1245 match = rxexecute(nil, rbuf, 0, i, &s);
1246 free(rbuf);
1247 return match;
1250 Address
1251 charaddr(long l, Address addr, int sign)
1253 if(sign == 0)
1254 addr.r.q0 = addr.r.q1 = l;
1255 else if(sign < 0)
1256 addr.r.q1 = addr.r.q0 -= l;
1257 else if(sign > 0)
1258 addr.r.q0 = addr.r.q1 += l;
1259 if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
1260 editerror("address out of range");
1261 return addr;
1264 Address
1265 lineaddr(long l, Address addr, int sign)
1267 int n;
1268 int c;
1269 File *f = addr.f;
1270 Address a;
1271 long p;
1273 a.f = f;
1274 if(sign >= 0){
1275 if(l == 0){
1276 if(sign==0 || addr.r.q1==0){
1277 a.r.q0 = a.r.q1 = 0;
1278 return a;
1280 a.r.q0 = addr.r.q1;
1281 p = addr.r.q1-1;
1282 }else{
1283 if(sign==0 || addr.r.q1==0){
1284 p = 0;
1285 n = 1;
1286 }else{
1287 p = addr.r.q1-1;
1288 n = textreadc(f->curtext, p++)=='\n';
1290 while(n < l){
1291 if(p >= f->b.nc)
1292 editerror("address out of range");
1293 if(textreadc(f->curtext, p++) == '\n')
1294 n++;
1296 a.r.q0 = p;
1298 while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1300 a.r.q1 = p;
1301 }else{
1302 p = addr.r.q0;
1303 if(l == 0)
1304 a.r.q1 = addr.r.q0;
1305 else{
1306 for(n = 0; n<l; ){ /* always runs once */
1307 if(p == 0){
1308 if(++n != l)
1309 editerror("address out of range");
1310 }else{
1311 c = textreadc(f->curtext, p-1);
1312 if(c != '\n' || ++n != l)
1313 p--;
1316 a.r.q1 = p;
1317 if(p > 0)
1318 p--;
1320 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1321 p--;
1322 a.r.q0 = p;
1324 return a;
1327 struct Filecheck
1329 File *f;
1330 Rune *r;
1331 int nr;
1334 void
1335 allfilecheck(Window *w, void *v)
1337 struct Filecheck *fp;
1338 File *f;
1340 fp = v;
1341 f = w->body.file;
1342 if(w->body.file == fp->f)
1343 return;
1344 if(runeeq(fp->r, fp->nr, f->name, f->nname))
1345 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1348 Rune*
1349 cmdname(File *f, String *str, int set)
1351 Rune *r, *s;
1352 int n;
1353 struct Filecheck fc;
1354 Runestr newname;
1356 r = nil;
1357 n = str->n;
1358 s = str->r;
1359 if(n == 0){
1360 /* no name; use existing */
1361 if(f->nname == 0)
1362 return nil;
1363 r = runemalloc(f->nname+1);
1364 runemove(r, f->name, f->nname);
1365 return r;
1367 s = skipbl(s, n, &n);
1368 if(n == 0)
1369 goto Return;
1371 if(s[0] == '/'){
1372 r = runemalloc(n+1);
1373 runemove(r, s, n);
1374 }else{
1375 newname = dirname(f->curtext, runestrdup(s), n);
1376 n = newname.nr;
1377 r = runemalloc(n+1); /* NUL terminate */
1378 runemove(r, newname.r, n);
1379 free(newname.r);
1381 fc.f = f;
1382 fc.r = r;
1383 fc.nr = n;
1384 allwindows(allfilecheck, &fc);
1385 if(f->nname == 0)
1386 set = TRUE;
1388 Return:
1389 if(set && !runeeq(r, n, f->name, f->nname)){
1390 filemark(f);
1391 f->mod = TRUE;
1392 f->curtext->w->dirty = TRUE;
1393 winsetname(f->curtext->w, r, n);
1395 return r;