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 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";
153 /* string is known to be NUL-terminated */
154 Rune*
155 filelist(Text *t, Rune *r, int nr)
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;
168 int
169 a_cmd(Text *t, Cmd *cp)
171 return append(t->file, cp, addr.r.q1);
174 int
175 b_cmd(Text *t, Cmd *cp)
177 File *f;
179 USED(t);
180 f = tofile(cp->u.text);
181 if(nest == 0)
182 pfilename(f);
183 curtext = f->curtext;
184 return TRUE;
187 int
188 B_cmd(Text *t, Cmd *cp)
190 Rune *list, *r, *s;
191 int nr;
193 list = filelist(t, cp->u.text->r, cp->u.text->n);
194 if(list == nil)
195 editerror(Enoname);
196 r = list;
197 nr = runestrlen(r);
198 r = skipbl(r, nr, &nr);
199 if(nr == 0)
200 new(t, t, nil, 0, 0, r, 0);
201 else while(nr > 0){
202 s = findbl(r, nr, &nr);
203 *s = '\0';
204 new(t, t, nil, 0, 0, r, runestrlen(r));
205 if(nr > 0)
206 r = skipbl(s+1, nr-1, &nr);
208 clearcollection();
209 return TRUE;
212 int
213 c_cmd(Text *t, Cmd *cp)
215 elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n);
216 t->q0 = addr.r.q0;
217 t->q1 = addr.r.q1;
218 return TRUE;
221 int
222 d_cmd(Text *t, Cmd *cp)
224 USED(cp);
225 if(addr.r.q1 > addr.r.q0)
226 elogdelete(t->file, addr.r.q0, addr.r.q1);
227 t->q0 = addr.r.q0;
228 t->q1 = addr.r.q0;
229 return TRUE;
232 void
233 D1(Text *t)
235 if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
236 colclose(t->col, t->w, TRUE);
239 int
240 D_cmd(Text *t, Cmd *cp)
242 Rune *list, *r, *s, *n;
243 int nr, nn;
244 Window *w;
245 Runestr dir, rs;
246 char buf[128];
248 list = filelist(t, cp->u.text->r, cp->u.text->n);
249 if(list == nil){
250 D1(t);
251 return TRUE;
253 dir = dirname(t, nil, 0);
254 r = list;
255 nr = runestrlen(r);
256 r = skipbl(r, nr, &nr);
257 do{
258 s = findbl(r, nr, &nr);
259 *s = '\0';
260 /* first time through, could be empty string, meaning delete file empty name */
261 nn = runestrlen(r);
262 if(r[0]=='/' || nn==0 || dir.nr==0){
263 rs.r = runestrdup(r);
264 rs.nr = nn;
265 }else{
266 n = runemalloc(dir.nr+1+nn);
267 runemove(n, dir.r, dir.nr);
268 n[dir.nr] = '/';
269 runemove(n+dir.nr+1, r, nn);
270 rs = cleanrname(runestr(n, dir.nr+1+nn));
272 w = lookfile(rs.r, rs.nr);
273 if(w == nil){
274 snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
275 free(rs.r);
276 editerror(buf);
278 free(rs.r);
279 D1(&w->body);
280 if(nr > 0)
281 r = skipbl(s+1, nr-1, &nr);
282 }while(nr > 0);
283 clearcollection();
284 free(dir.r);
285 return TRUE;
288 static int
289 readloader(void *v, uint q0, Rune *r, int nr)
291 if(nr > 0)
292 eloginsert(v, q0, r, nr);
293 return 0;
296 int
297 e_cmd(Text *t, Cmd *cp)
299 Rune *name;
300 File *f;
301 int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
302 char *s, tmp[128];
303 Dir *d;
305 f = t->file;
306 q0 = addr.r.q0;
307 q1 = addr.r.q1;
308 if(cp->cmdc == 'e'){
309 if(winclean(t->w, TRUE)==FALSE)
310 editerror(""); /* winclean generated message already */
311 q0 = 0;
312 q1 = f->b.nc;
314 allreplaced = (q0==0 && q1==f->b.nc);
315 name = cmdname(f, cp->u.text, cp->cmdc=='e');
316 if(name == nil)
317 editerror(Enoname);
318 i = runestrlen(name);
319 samename = runeeq(name, i, t->file->name, t->file->nname);
320 s = runetobyte(name, i);
321 free(name);
322 fd = open(s, OREAD);
323 if(fd < 0){
324 snprint(tmp, sizeof tmp, "can't open %s: %r", s);
325 free(s);
326 editerror(tmp);
328 d = dirfstat(fd);
329 isdir = (d!=nil && (d->qid.type&QTDIR));
330 free(d);
331 if(isdir){
332 close(fd);
333 snprint(tmp, sizeof tmp, "%s is a directory", s);
334 free(s);
335 editerror(tmp);
337 elogdelete(f, q0, q1);
338 nulls = 0;
339 loadfile(fd, q1, &nulls, readloader, f);
340 free(s);
341 close(fd);
342 if(nulls)
343 warning(nil, "%s: NUL bytes elided\n", s);
344 else if(allreplaced && samename)
345 f->editclean = TRUE;
346 return TRUE;
349 static Rune Lempty[] = { 0 };
350 int
351 f_cmd(Text *t, Cmd *cp)
353 Rune *name;
354 String *str;
355 String empty;
357 if(cp->u.text == nil){
358 empty.n = 0;
359 empty.r = Lempty;
360 str = &empty;
361 }else
362 str = cp->u.text;
363 name = cmdname(t->file, str, TRUE);
364 free(name);
365 pfilename(t->file);
366 return TRUE;
369 int
370 g_cmd(Text *t, Cmd *cp)
372 if(t->file != addr.f){
373 warning(nil, "internal error: g_cmd f!=addr.f\n");
374 return FALSE;
376 if(rxcompile(cp->re->r) == FALSE)
377 editerror("bad regexp in g command");
378 if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
379 t->q0 = addr.r.q0;
380 t->q1 = addr.r.q1;
381 return cmdexec(t, cp->u.cmd);
383 return TRUE;
386 int
387 i_cmd(Text *t, Cmd *cp)
389 return append(t->file, cp, addr.r.q0);
392 void
393 copy(File *f, Address addr2)
395 long p;
396 int ni;
397 Rune *buf;
399 buf = fbufalloc();
400 for(p=addr.r.q0; p<addr.r.q1; p+=ni){
401 ni = addr.r.q1-p;
402 if(ni > RBUFSIZE)
403 ni = RBUFSIZE;
404 bufread(&f->b, p, buf, ni);
405 eloginsert(addr2.f, addr2.r.q1, buf, ni);
407 fbuffree(buf);
410 void
411 move(File *f, Address addr2)
413 if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
414 elogdelete(f, addr.r.q0, addr.r.q1);
415 copy(f, addr2);
416 }else if(addr.r.q0 >= addr2.r.q1){
417 copy(f, addr2);
418 elogdelete(f, addr.r.q0, addr.r.q1);
419 }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
420 ; /* move to self; no-op */
421 }else
422 editerror("move overlaps itself");
425 int
426 m_cmd(Text *t, Cmd *cp)
428 Address dot, addr2;
430 mkaddr(&dot, t->file);
431 addr2 = cmdaddress(cp->u.mtaddr, dot, 0);
432 if(cp->cmdc == 'm')
433 move(t->file, addr2);
434 else
435 copy(t->file, addr2);
436 return TRUE;
439 int
440 p_cmd(Text *t, Cmd *cp)
442 USED(cp);
443 return pdisplay(t->file);
446 int
447 s_cmd(Text *t, Cmd *cp)
449 int i, j, k, c, m, n, nrp, didsub;
450 long p1, op, delta;
451 String *buf;
452 Rangeset *rp;
453 char *err;
454 Rune *rbuf;
456 n = cp->num;
457 op= -1;
458 if(rxcompile(cp->re->r) == FALSE)
459 editerror("bad regexp in s command");
460 nrp = 0;
461 rp = nil;
462 delta = 0;
463 didsub = FALSE;
464 for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
465 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
466 if(sel.r[0].q0 == op){
467 p1++;
468 continue;
470 p1 = sel.r[0].q1+1;
471 }else
472 p1 = sel.r[0].q1;
473 op = sel.r[0].q1;
474 if(--n>0)
475 continue;
476 nrp++;
477 rp = erealloc(rp, nrp*sizeof(Rangeset));
478 rp[nrp-1] = sel;
480 rbuf = fbufalloc();
481 buf = allocstring(0);
482 for(m=0; m<nrp; m++){
483 buf->n = 0;
484 buf->r[0] = '\0';
485 sel = rp[m];
486 for(i = 0; i<cp->u.text->n; i++)
487 if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){
488 c = cp->u.text->r[++i];
489 if('1'<=c && c<='9') {
490 j = c-'0';
491 if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
492 err = "replacement string too long";
493 goto Err;
495 bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
496 for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
497 Straddc(buf, rbuf[k]);
498 }else
499 Straddc(buf, c);
500 }else if(c!='&')
501 Straddc(buf, c);
502 else{
503 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
504 err = "right hand side too long in substitution";
505 goto Err;
507 bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
508 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
509 Straddc(buf, rbuf[k]);
511 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
512 delta -= sel.r[0].q1-sel.r[0].q0;
513 delta += buf->n;
514 didsub = 1;
515 if(!cp->flag)
516 break;
518 free(rp);
519 freestring(buf);
520 fbuffree(rbuf);
521 if(!didsub && nest==0)
522 editerror("no substitution");
523 t->q0 = addr.r.q0;
524 t->q1 = addr.r.q1;
525 return TRUE;
527 Err:
528 free(rp);
529 freestring(buf);
530 fbuffree(rbuf);
531 editerror(err);
532 return FALSE;
535 int
536 u_cmd(Text *t, Cmd *cp)
538 int n, oseq, flag;
540 n = cp->num;
541 flag = TRUE;
542 if(n < 0){
543 n = -n;
544 flag = FALSE;
546 oseq = -1;
547 while(n-->0 && t->file->seq!=oseq){
548 oseq = t->file->seq;
549 undo(t, nil, nil, flag, 0, nil, 0);
551 return TRUE;
554 int
555 w_cmd(Text *t, Cmd *cp)
557 Rune *r;
558 File *f;
560 f = t->file;
561 if(f->seq == seq)
562 editerror("can't write file with pending modifications");
563 r = cmdname(f, cp->u.text, FALSE);
564 if(r == nil)
565 editerror("no name specified for 'w' command");
566 putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
567 /* r is freed by putfile */
568 return TRUE;
571 int
572 x_cmd(Text *t, Cmd *cp)
574 if(cp->re)
575 looper(t->file, cp, cp->cmdc=='x');
576 else
577 linelooper(t->file, cp);
578 return TRUE;
581 int
582 X_cmd(Text *t, Cmd *cp)
584 USED(t);
586 filelooper(cp, cp->cmdc=='X');
587 return TRUE;
590 void
591 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
593 Rune *r, *s;
594 int n;
595 Runestr dir;
596 Window *w;
597 QLock *q;
599 r = skipbl(cr, ncr, &n);
600 if(n == 0)
601 editerror("no command specified for %c", cmd);
602 w = nil;
603 if(state == Inserting){
604 w = t->w;
605 t->q0 = addr.r.q0;
606 t->q1 = addr.r.q1;
607 if(cmd == '<' || cmd=='|')
608 elogdelete(t->file, t->q0, t->q1);
610 s = runemalloc(n+2);
611 s[0] = cmd;
612 runemove(s+1, r, n);
613 n++;
614 dir.r = nil;
615 dir.nr = 0;
616 if(t != nil)
617 dir = dirname(t, nil, 0);
618 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
619 free(dir.r);
620 dir.r = nil;
621 dir.nr = 0;
623 editing = state;
624 if(t!=nil && t->w!=nil)
625 incref(&t->w->ref); /* run will decref */
626 run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
627 free(s);
628 if(t!=nil && t->w!=nil)
629 winunlock(t->w);
630 qunlock(&row.lk);
631 recvul(cedit);
632 /*
633 * The editoutlk exists only so that we can tell when
634 * the editout file has been closed. It can get closed *after*
635 * the process exits because, since the process cannot be
636 * connected directly to editout (no 9P kernel support),
637 * the process is actually connected to a pipe to another
638 * process (arranged via 9pserve) that reads from the pipe
639 * and then writes the data in the pipe to editout using
640 * 9P transactions. This process might still have a couple
641 * writes left to copy after the original process has exited.
642 */
643 if(w)
644 q = &w->editoutlk;
645 else
646 q = &editoutlk;
647 qlock(q); /* wait for file to close */
648 qunlock(q);
649 qlock(&row.lk);
650 editing = Inactive;
651 if(t!=nil && t->w!=nil)
652 winlock(t->w, 'M');
655 int
656 pipe_cmd(Text *t, Cmd *cp)
658 runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
659 return TRUE;
662 long
663 nlcount(Text *t, long q0, long q1)
665 long nl;
666 Rune *buf;
667 int i, nbuf;
669 buf = fbufalloc();
670 nbuf = 0;
671 i = nl = 0;
672 while(q0 < q1){
673 if(i == nbuf){
674 nbuf = q1-q0;
675 if(nbuf > RBUFSIZE)
676 nbuf = RBUFSIZE;
677 bufread(&t->file->b, q0, buf, nbuf);
678 i = 0;
680 if(buf[i++] == '\n')
681 nl++;
682 q0++;
684 fbuffree(buf);
685 return nl;
688 void
689 printposn(Text *t, int charsonly)
691 long l1, l2;
693 if (t != nil && t->file != nil && t->file->name != nil)
694 warning(nil, "%.*S:", t->file->nname, t->file->name);
695 if(!charsonly){
696 l1 = 1+nlcount(t, 0, addr.r.q0);
697 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
698 /* check if addr ends with '\n' */
699 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
700 --l2;
701 warning(nil, "%lud", l1);
702 if(l2 != l1)
703 warning(nil, ",%lud", l2);
704 warning(nil, "\n");
705 return;
707 warning(nil, "#%d", addr.r.q0);
708 if(addr.r.q1 != addr.r.q0)
709 warning(nil, ",#%d", addr.r.q1);
710 warning(nil, "\n");
713 int
714 eq_cmd(Text *t, Cmd *cp)
716 int charsonly;
718 switch(cp->u.text->n){
719 case 0:
720 charsonly = FALSE;
721 break;
722 case 1:
723 if(cp->u.text->r[0] == '#'){
724 charsonly = TRUE;
725 break;
727 default:
728 SET(charsonly);
729 editerror("newline expected");
731 printposn(t, charsonly);
732 return TRUE;
735 int
736 nl_cmd(Text *t, Cmd *cp)
738 Address a;
739 File *f;
741 f = t->file;
742 if(cp->addr == 0){
743 /* First put it on newline boundaries */
744 mkaddr(&a, f);
745 addr = lineaddr(0, a, -1);
746 a = lineaddr(0, a, 1);
747 addr.r.q1 = a.r.q1;
748 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
749 mkaddr(&a, f);
750 addr = lineaddr(1, a, 1);
753 textshow(t, addr.r.q0, addr.r.q1, 1);
754 return TRUE;
757 int
758 append(File *f, Cmd *cp, long p)
760 if(cp->u.text->n > 0)
761 eloginsert(f, p, cp->u.text->r, cp->u.text->n);
762 f->curtext->q0 = p;
763 f->curtext->q1 = p;
764 return TRUE;
767 int
768 pdisplay(File *f)
770 long p1, p2;
771 int np;
772 Rune *buf;
774 p1 = addr.r.q0;
775 p2 = addr.r.q1;
776 if(p2 > f->b.nc)
777 p2 = f->b.nc;
778 buf = fbufalloc();
779 while(p1 < p2){
780 np = p2-p1;
781 if(np>RBUFSIZE-1)
782 np = RBUFSIZE-1;
783 bufread(&f->b, p1, buf, np);
784 buf[np] = '\0';
785 warning(nil, "%S", buf);
786 p1 += np;
788 fbuffree(buf);
789 f->curtext->q0 = addr.r.q0;
790 f->curtext->q1 = addr.r.q1;
791 return TRUE;
794 void
795 pfilename(File *f)
797 int dirty;
798 Window *w;
800 w = f->curtext->w;
801 /* same check for dirty as in settag, but we know ncache==0 */
802 dirty = !w->isdir && !w->isscratch && f->mod;
803 warning(nil, "%c%c%c %.*S\n", " '"[dirty],
804 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
807 void
808 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
810 long i;
812 for(i=0; i<nrp; i++){
813 f->curtext->q0 = rp[i].q0;
814 f->curtext->q1 = rp[i].q1;
815 cmdexec(f->curtext, cp);
819 void
820 looper(File *f, Cmd *cp, int xy)
822 long p, op, nrp;
823 Range r, tr;
824 Range *rp;
826 r = addr.r;
827 op= xy? -1 : r.q0;
828 nest++;
829 if(rxcompile(cp->re->r) == FALSE)
830 editerror("bad regexp in %c command", cp->cmdc);
831 nrp = 0;
832 rp = nil;
833 for(p = r.q0; p<=r.q1; ){
834 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
835 if(xy || op>r.q1)
836 break;
837 tr.q0 = op, tr.q1 = r.q1;
838 p = r.q1+1; /* exit next loop */
839 }else{
840 if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
841 if(sel.r[0].q0==op){
842 p++;
843 continue;
845 p = sel.r[0].q1+1;
846 }else
847 p = sel.r[0].q1;
848 if(xy)
849 tr = sel.r[0];
850 else
851 tr.q0 = op, tr.q1 = sel.r[0].q0;
853 op = sel.r[0].q1;
854 nrp++;
855 rp = erealloc(rp, nrp*sizeof(Range));
856 rp[nrp-1] = tr;
858 loopcmd(f, cp->u.cmd, rp, nrp);
859 free(rp);
860 --nest;
863 void
864 linelooper(File *f, Cmd *cp)
866 long nrp, p;
867 Range r, linesel;
868 Address a, a3;
869 Range *rp;
871 nest++;
872 nrp = 0;
873 rp = nil;
874 r = addr.r;
875 a3.f = f;
876 a3.r.q0 = a3.r.q1 = r.q0;
877 a = lineaddr(0, a3, 1);
878 linesel = a.r;
879 for(p = r.q0; p<r.q1; p = a3.r.q1){
880 a3.r.q0 = a3.r.q1;
881 if(p!=r.q0 || linesel.q1==p){
882 a = lineaddr(1, a3, 1);
883 linesel = a.r;
885 if(linesel.q0 >= r.q1)
886 break;
887 if(linesel.q1 >= r.q1)
888 linesel.q1 = r.q1;
889 if(linesel.q1 > linesel.q0)
890 if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
891 a3.r = linesel;
892 nrp++;
893 rp = erealloc(rp, nrp*sizeof(Range));
894 rp[nrp-1] = linesel;
895 continue;
897 break;
899 loopcmd(f, cp->u.cmd, rp, nrp);
900 free(rp);
901 --nest;
904 struct Looper
906 Cmd *cp;
907 int XY;
908 Window **w;
909 int nw;
910 } loopstruct; /* only one; X and Y can't nest */
912 void
913 alllooper(Window *w, void *v)
915 Text *t;
916 struct Looper *lp;
917 Cmd *cp;
919 lp = v;
920 cp = lp->cp;
921 /* if(w->isscratch || w->isdir) */
922 /* return; */
923 t = &w->body;
924 /* only use this window if it's the current window for the file */
925 if(t->file->curtext != t)
926 return;
927 /* if(w->nopen[QWevent] > 0) */
928 /* return; */
929 /* no auto-execute on files without names */
930 if(cp->re==nil && t->file->nname==0)
931 return;
932 if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
933 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
934 lp->w[lp->nw++] = w;
938 void
939 alllocker(Window *w, void *v)
941 if(v)
942 incref(&w->ref);
943 else
944 winclose(w);
947 void
948 filelooper(Cmd *cp, int XY)
950 int i;
952 if(Glooping++)
953 editerror("can't nest %c command", "YX"[XY]);
954 nest++;
956 loopstruct.cp = cp;
957 loopstruct.XY = XY;
958 if(loopstruct.w) /* error'ed out last time */
959 free(loopstruct.w);
960 loopstruct.w = nil;
961 loopstruct.nw = 0;
962 allwindows(alllooper, &loopstruct);
963 /*
964 * add a ref to all windows to keep safe windows accessed by X
965 * that would not otherwise have a ref to hold them up during
966 * the shenanigans. note this with globalincref so that any
967 * newly created windows start with an extra reference.
968 */
969 allwindows(alllocker, (void*)1);
970 globalincref = 1;
971 for(i=0; i<loopstruct.nw; i++)
972 cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
973 allwindows(alllocker, (void*)0);
974 globalincref = 0;
975 free(loopstruct.w);
976 loopstruct.w = nil;
978 --Glooping;
979 --nest;
982 void
983 nextmatch(File *f, String *r, long p, int sign)
985 if(rxcompile(r->r) == FALSE)
986 editerror("bad regexp in command address");
987 if(sign >= 0){
988 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
989 editerror("no match for regexp");
990 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
991 if(++p>f->b.nc)
992 p = 0;
993 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
994 editerror("address");
996 }else{
997 if(!rxbexecute(f->curtext, p, &sel))
998 editerror("no match for regexp");
999 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
1000 if(--p<0)
1001 p = f->b.nc;
1002 if(!rxbexecute(f->curtext, p, &sel))
1003 editerror("address");
1008 File *matchfile(String*);
1009 Address charaddr(long, Address, int);
1010 Address lineaddr(long, Address, int);
1012 Address
1013 cmdaddress(Addr *ap, Address a, int sign)
1015 File *f = a.f;
1016 Address a1, a2;
1018 do{
1019 switch(ap->type){
1020 case 'l':
1021 case '#':
1022 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1023 break;
1025 case '.':
1026 mkaddr(&a, f);
1027 break;
1029 case '$':
1030 a.r.q0 = a.r.q1 = f->b.nc;
1031 break;
1033 case '\'':
1034 editerror("can't handle '");
1035 /* a.r = f->mark; */
1036 break;
1038 case '?':
1039 sign = -sign;
1040 if(sign == 0)
1041 sign = -1;
1042 /* fall through */
1043 case '/':
1044 nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
1045 a.r = sel.r[0];
1046 break;
1048 case '"':
1049 f = matchfile(ap->u.re);
1050 mkaddr(&a, f);
1051 break;
1053 case '*':
1054 a.r.q0 = 0, a.r.q1 = f->b.nc;
1055 return a;
1057 case ',':
1058 case ';':
1059 if(ap->u.left)
1060 a1 = cmdaddress(ap->u.left, a, 0);
1061 else
1062 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1063 if(ap->type == ';'){
1064 f = a1.f;
1065 a = a1;
1066 f->curtext->q0 = a1.r.q0;
1067 f->curtext->q1 = a1.r.q1;
1069 if(ap->next)
1070 a2 = cmdaddress(ap->next, a, 0);
1071 else
1072 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
1073 if(a1.f != a2.f)
1074 editerror("addresses in different files");
1075 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1076 if(a.r.q1 < a.r.q0)
1077 editerror("addresses out of order");
1078 return a;
1080 case '+':
1081 case '-':
1082 sign = 1;
1083 if(ap->type == '-')
1084 sign = -1;
1085 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1086 a = lineaddr(1L, a, sign);
1087 break;
1088 default:
1089 error("cmdaddress");
1090 return a;
1092 }while(ap = ap->next); /* assign = */
1093 return a;
1096 struct Tofile{
1097 File *f;
1098 String *r;
1101 void
1102 alltofile(Window *w, void *v)
1104 Text *t;
1105 struct Tofile *tp;
1107 tp = v;
1108 if(tp->f != nil)
1109 return;
1110 if(w->isscratch || w->isdir)
1111 return;
1112 t = &w->body;
1113 /* only use this window if it's the current window for the file */
1114 if(t->file->curtext != t)
1115 return;
1116 /* if(w->nopen[QWevent] > 0) */
1117 /* return; */
1118 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1119 tp->f = t->file;
1122 File*
1123 tofile(String *r)
1125 struct Tofile t;
1126 String rr;
1128 rr.r = skipbl(r->r, r->n, &rr.n);
1129 t.f = nil;
1130 t.r = &rr;
1131 allwindows(alltofile, &t);
1132 if(t.f == nil)
1133 editerror("no such file\"%S\"", rr.r);
1134 return t.f;
1137 void
1138 allmatchfile(Window *w, void *v)
1140 struct Tofile *tp;
1141 Text *t;
1143 tp = v;
1144 if(w->isscratch || w->isdir)
1145 return;
1146 t = &w->body;
1147 /* only use this window if it's the current window for the file */
1148 if(t->file->curtext != t)
1149 return;
1150 /* if(w->nopen[QWevent] > 0) */
1151 /* return; */
1152 if(filematch(w->body.file, tp->r)){
1153 if(tp->f != nil)
1154 editerror("too many files match \"%S\"", tp->r->r);
1155 tp->f = w->body.file;
1159 File*
1160 matchfile(String *r)
1162 struct Tofile tf;
1164 tf.f = nil;
1165 tf.r = r;
1166 allwindows(allmatchfile, &tf);
1168 if(tf.f == nil)
1169 editerror("no file matches \"%S\"", r->r);
1170 return tf.f;
1173 int
1174 filematch(File *f, String *r)
1176 char *buf;
1177 Rune *rbuf;
1178 Window *w;
1179 int match, i, dirty;
1180 Rangeset s;
1182 /* compile expr first so if we get an error, we haven't allocated anything */
1183 if(rxcompile(r->r) == FALSE)
1184 editerror("bad regexp in file match");
1185 buf = fbufalloc();
1186 w = f->curtext->w;
1187 /* same check for dirty as in settag, but we know ncache==0 */
1188 dirty = !w->isdir && !w->isscratch && f->mod;
1189 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1190 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1191 rbuf = bytetorune(buf, &i);
1192 fbuffree(buf);
1193 match = rxexecute(nil, rbuf, 0, i, &s);
1194 free(rbuf);
1195 return match;
1198 Address
1199 charaddr(long l, Address addr, int sign)
1201 if(sign == 0)
1202 addr.r.q0 = addr.r.q1 = l;
1203 else if(sign < 0)
1204 addr.r.q1 = addr.r.q0 -= l;
1205 else if(sign > 0)
1206 addr.r.q0 = addr.r.q1 += l;
1207 if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
1208 editerror("address out of range");
1209 return addr;
1212 Address
1213 lineaddr(long l, Address addr, int sign)
1215 int n;
1216 int c;
1217 File *f = addr.f;
1218 Address a;
1219 long p;
1221 a.f = f;
1222 if(sign >= 0){
1223 if(l == 0){
1224 if(sign==0 || addr.r.q1==0){
1225 a.r.q0 = a.r.q1 = 0;
1226 return a;
1228 a.r.q0 = addr.r.q1;
1229 p = addr.r.q1-1;
1230 }else{
1231 if(sign==0 || addr.r.q1==0){
1232 p = 0;
1233 n = 1;
1234 }else{
1235 p = addr.r.q1-1;
1236 n = textreadc(f->curtext, p++)=='\n';
1238 while(n < l){
1239 if(p >= f->b.nc)
1240 editerror("address out of range");
1241 if(textreadc(f->curtext, p++) == '\n')
1242 n++;
1244 a.r.q0 = p;
1246 while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1248 a.r.q1 = p;
1249 }else{
1250 p = addr.r.q0;
1251 if(l == 0)
1252 a.r.q1 = addr.r.q0;
1253 else{
1254 for(n = 0; n<l; ){ /* always runs once */
1255 if(p == 0){
1256 if(++n != l)
1257 editerror("address out of range");
1258 }else{
1259 c = textreadc(f->curtext, p-1);
1260 if(c != '\n' || ++n != l)
1261 p--;
1264 a.r.q1 = p;
1265 if(p > 0)
1266 p--;
1268 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1269 p--;
1270 a.r.q0 = p;
1272 return a;
1275 struct Filecheck
1277 File *f;
1278 Rune *r;
1279 int nr;
1282 void
1283 allfilecheck(Window *w, void *v)
1285 struct Filecheck *fp;
1286 File *f;
1288 fp = v;
1289 f = w->body.file;
1290 if(w->body.file == fp->f)
1291 return;
1292 if(runeeq(fp->r, fp->nr, f->name, f->nname))
1293 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1296 Rune*
1297 cmdname(File *f, String *str, int set)
1299 Rune *r, *s;
1300 int n;
1301 struct Filecheck fc;
1302 Runestr newname;
1304 r = nil;
1305 n = str->n;
1306 s = str->r;
1307 if(n == 0){
1308 /* no name; use existing */
1309 if(f->nname == 0)
1310 return nil;
1311 r = runemalloc(f->nname+1);
1312 runemove(r, f->name, f->nname);
1313 return r;
1315 s = skipbl(s, n, &n);
1316 if(n == 0)
1317 goto Return;
1319 if(s[0] == '/'){
1320 r = runemalloc(n+1);
1321 runemove(r, s, n);
1322 }else{
1323 newname = dirname(f->curtext, runestrdup(s), n);
1324 n = newname.nr;
1325 r = runemalloc(n+1); /* NUL terminate */
1326 runemove(r, newname.r, n);
1327 free(newname.r);
1329 fc.f = f;
1330 fc.r = r;
1331 fc.nr = n;
1332 allwindows(allfilecheck, &fc);
1333 if(f->nname == 0)
1334 set = TRUE;
1336 Return:
1337 if(set && !runeeq(r, n, f->name, f->nname)){
1338 filemark(f);
1339 f->mod = TRUE;
1340 f->curtext->w->dirty = TRUE;
1341 winsetname(f->curtext->w, r, n);
1343 return r;