master
ecmd.c
ohmu
·
2026-09-13
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"
15
16int Glooping;
17int nest;
18char Enoname[] = "no file name given";
19
20Address addr;
21File *menu;
22Rangeset sel;
23extern Text* curtext;
24Rune *collection;
25int ncollection;
26
27int append(File*, Cmd*, long);
28int pdisplay(File*);
29void pfilename(File*);
30void looper(File*, Cmd*, int);
31void filelooper(Cmd*, int);
32void linelooper(File*, Cmd*);
33Address lineaddr(long, Address, int);
34int filematch(File*, String*);
35File *tofile(String*);
36Rune* cmdname(File *f, String *s, int);
37void runpipe(Text*, int, Rune*, int, int);
38
39void
40clearcollection(void)
41{
42 free(collection);
43 collection = nil;
44 ncollection = 0;
45}
46
47void
48resetxec(void)
49{
50 Glooping = nest = 0;
51 clearcollection();
52}
53
54void
55mkaddr(Address *a, File *f)
56{
57 a->r.q0 = f->curtext->q0;
58 a->r.q1 = f->curtext->q1;
59 a->f = f;
60}
61
62int
63cmdexec(Text *t, Cmd *cp)
64{
65 int i;
66 Addr *ap;
67 File *f;
68 Window *w;
69 Address dot;
70
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;
107 }
108 }
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);
120 }
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;
127 }
128 return 1;
129}
130
131char*
132edittext(Window *w, int q, Rune *r, int nr)
133{
134 File *f;
135
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";
151 }
152}
153
154/* string is known to be NUL-terminated */
155Rune*
156filelist(Text *t, Rune *r, int nr)
157{
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;
167}
168
169int
170a_cmd(Text *t, Cmd *cp)
171{
172 return append(t->file, cp, addr.r.q1);
173}
174
175int
176b_cmd(Text *t, Cmd *cp)
177{
178 File *f;
179
180 USED(t);
181 f = tofile(cp->u.text);
182 if(nest == 0)
183 pfilename(f);
184 curtext = f->curtext;
185 return TRUE;
186}
187
188int
189B_cmd(Text *t, Cmd *cp)
190{
191 Rune *list, *r, *s;
192 int nr;
193
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);
208 }
209 clearcollection();
210 return TRUE;
211}
212
213int
214c_cmd(Text *t, Cmd *cp)
215{
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;
220}
221
222int
223d_cmd(Text *t, Cmd *cp)
224{
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;
231}
232
233void
234D1(Text *t)
235{
236 if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
237 colclose(t->col, t->w, TRUE);
238}
239
240int
241D_cmd(Text *t, Cmd *cp)
242{
243 Rune *list, *r, *s, *n;
244 int nr, nn;
245 Window *w;
246 Runestr dir, rs;
247 char buf[128];
248
249 list = filelist(t, cp->u.text->r, cp->u.text->n);
250 if(list == nil){
251 D1(t);
252 return TRUE;
253 }
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));
272 }
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);
278 }
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;
287}
288
289static int
290readloader(void *v, uint q0, Rune *r, int nr)
291{
292 if(nr > 0)
293 eloginsert(v, q0, r, nr);
294 return 0;
295}
296
297int
298e_cmd(Text *t, Cmd *cp)
299{
300 Rune *name;
301 File *f;
302 int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
303 char *s, tmp[128];
304 Dir *d;
305
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;
314 }
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);
328 }
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);
337 }
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;
348}
349
350static Rune Lempty[] = { 0 };
351int
352f_cmd(Text *t, Cmd *cp)
353{
354 Rune *name;
355 String *str;
356 String empty;
357
358 if(cp->u.text == nil){
359 empty.n = 0;
360 empty.r = Lempty;
361 str = ∅
362 }else
363 str = cp->u.text;
364 name = cmdname(t->file, str, TRUE);
365 free(name);
366 pfilename(t->file);
367 return TRUE;
368}
369
370int
371g_cmd(Text *t, Cmd *cp)
372{
373 if(t->file != addr.f){
374 warning(nil, "internal error: g_cmd f!=addr.f\n");
375 return FALSE;
376 }
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);
383 }
384 return TRUE;
385}
386
387int
388i_cmd(Text *t, Cmd *cp)
389{
390 return append(t->file, cp, addr.r.q0);
391}
392
393void
394copy(File *f, Address addr2)
395{
396 long p;
397 int ni;
398 Rune *buf;
399
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);
407 }
408 fbuffree(buf);
409}
410
411void
412move(File *f, Address addr2)
413{
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");
424}
425
426int
427m_cmd(Text *t, Cmd *cp)
428{
429 Address dot, addr2;
430
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;
438}
439
440int
441p_cmd(Text *t, Cmd *cp)
442{
443 USED(cp);
444 return pdisplay(t->file);
445}
446
447int
448s_cmd(Text *t, Cmd *cp)
449{
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;
456
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;
470 }
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;
480 }
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;
495 }
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;
507 }
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]);
511 }
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;
518 }
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;
527
528Err:
529 free(rp);
530 freestring(buf);
531 fbuffree(rbuf);
532 editerror(err);
533 return FALSE;
534}
535
536int
537u_cmd(Text *t, Cmd *cp)
538{
539 int n, oseq, flag;
540
541 n = cp->num;
542 flag = TRUE;
543 if(n < 0){
544 n = -n;
545 flag = FALSE;
546 }
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);
551 }
552 return TRUE;
553}
554
555int
556w_cmd(Text *t, Cmd *cp)
557{
558 Rune *r;
559 File *f;
560
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;
570}
571
572int
573x_cmd(Text *t, Cmd *cp)
574{
575 if(cp->re)
576 looper(t->file, cp, cp->cmdc=='x');
577 else
578 linelooper(t->file, cp);
579 return TRUE;
580}
581
582int
583X_cmd(Text *t, Cmd *cp)
584{
585 USED(t);
586
587 filelooper(cp, cp->cmdc=='X');
588 return TRUE;
589}
590
591void
592runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
593{
594 Rune *r, *s;
595 int n;
596 Runestr dir;
597 Window *w;
598 QLock *q;
599
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);
610 }
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;
623 }
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');
654}
655
656int
657pipe_cmd(Text *t, Cmd *cp)
658{
659 runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
660 return TRUE;
661}
662
663long
664nlcount(Text *t, long q0, long q1, long *pnr)
665{
666 long nl, start;
667 Rune *buf;
668 int i, nbuf;
669
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;
681 }
682 if(buf[i++] == '\n') {
683 start = q0+1;
684 nl++;
685 }
686 q0++;
687 }
688 fbuffree(buf);
689 if(pnr != nil)
690 *pnr = q0 - start;
691 return nl;
692}
693
694enum {
695 PosnLine = 0,
696 PosnChars = 1,
697 PosnLineChars = 2,
698};
699
700void
701printposn(Text *t, int mode)
702{
703 long l1, l2, r1, r2;
704
705 if (t != nil && t->file != nil && t->file->name != nil)
706 warning(nil, "%.*S:", t->file->nname, t->file->name);
707
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;
715
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;
728
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;
739 }
740}
741
742int
743eq_cmd(Text *t, Cmd *cp)
744{
745 int mode;
746
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;
755 }
756 if(cp->u.text->r[0] == '+'){
757 mode = PosnLineChars;
758 break;
759 }
760 default:
761 SET(mode);
762 editerror("newline expected");
763 }
764 printposn(t, mode);
765 return TRUE;
766}
767
768int
769nl_cmd(Text *t, Cmd *cp)
770{
771 Address a;
772 File *f;
773
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);
784 }
785 }
786 textshow(t, addr.r.q0, addr.r.q1, 1);
787 return TRUE;
788}
789
790int
791append(File *f, Cmd *cp, long p)
792{
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;
798}
799
800int
801pdisplay(File *f)
802{
803 long p1, p2;
804 int np;
805 Rune *buf;
806
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;
820 }
821 fbuffree(buf);
822 f->curtext->q0 = addr.r.q0;
823 f->curtext->q1 = addr.r.q1;
824 return TRUE;
825}
826
827void
828pfilename(File *f)
829{
830 int dirty;
831 Window *w;
832
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);
838}
839
840void
841loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
842{
843 long i;
844
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);
849 }
850}
851
852void
853looper(File *f, Cmd *cp, int xy)
854{
855 long p, op, nrp;
856 Range r, tr;
857 Range *rp;
858
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;
877 }
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;
885 }
886 op = sel.r[0].q1;
887 nrp++;
888 rp = erealloc(rp, nrp*sizeof(Range));
889 rp[nrp-1] = tr;
890 }
891 loopcmd(f, cp->u.cmd, rp, nrp);
892 free(rp);
893 --nest;
894}
895
896void
897linelooper(File *f, Cmd *cp)
898{
899 long nrp, p;
900 Range r, linesel;
901 Address a, a3;
902 Range *rp;
903
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;
917 }
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;
929 }
930 break;
931 }
932 loopcmd(f, cp->u.cmd, rp, nrp);
933 free(rp);
934 --nest;
935}
936
937struct Looper
938{
939 Cmd *cp;
940 int XY;
941 Window **w;
942 int nw;
943} loopstruct; /* only one; X and Y can't nest */
944
945void
946alllooper(Window *w, void *v)
947{
948 Text *t;
949 struct Looper *lp;
950 Cmd *cp;
951
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;
968 }
969}
970
971void
972alllocker(Window *w, void *v)
973{
974 if(v)
975 incref(&w->ref);
976 else
977 winclose(w);
978}
979
980void
981filelooper(Cmd *cp, int XY)
982{
983 int i;
984
985 if(Glooping++)
986 editerror("can't nest %c command", "YX"[XY]);
987 nest++;
988
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.
1001 */
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;
1010
1011 --Glooping;
1012 --nest;
1013}
1014
1015void
1016nextmatch(File *f, String *r, long p, int sign)
1017{
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");
1028 }
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");
1037 }
1038 }
1039}
1040
1041File *matchfile(String*);
1042Address charaddr(long, Address, int);
1043Address lineaddr(long, Address, int);
1044
1045Address
1046cmdaddress(Addr *ap, Address a, int sign)
1047{
1048 File *f = a.f;
1049 Address a1, a2;
1050
1051 do{
1052 switch(ap->type){
1053 case 'l':
1054 case '#':
1055 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1056 break;
1057
1058 case '.':
1059 mkaddr(&a, f);
1060 break;
1061
1062 case '$':
1063 a.r.q0 = a.r.q1 = f->b.nc;
1064 break;
1065
1066 case '\'':
1067editerror("can't handle '");
1068/* a.r = f->mark; */
1069 break;
1070
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;
1080
1081 case '"':
1082 f = matchfile(ap->u.re);
1083 mkaddr(&a, f);
1084 break;
1085
1086 case '*':
1087 a.r.q0 = 0, a.r.q1 = f->b.nc;
1088 return a;
1089
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;
1101 }
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;
1112
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;
1124 }
1125 }while(ap = ap->next); /* assign = */
1126 return a;
1127}
1128
1129struct Tofile{
1130 File *f;
1131 String *r;
1132};
1133
1134void
1135alltofile(Window *w, void *v)
1136{
1137 Text *t;
1138 struct Tofile *tp;
1139
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;
1153}
1154
1155File*
1156tofile(String *r)
1157{
1158 struct Tofile t;
1159 String rr;
1160
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;
1168}
1169
1170void
1171allmatchfile(Window *w, void *v)
1172{
1173 struct Tofile *tp;
1174 Text *t;
1175
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;
1189 }
1190}
1191
1192File*
1193matchfile(String *r)
1194{
1195 struct Tofile tf;
1196
1197 tf.f = nil;
1198 tf.r = r;
1199 allwindows(allmatchfile, &tf);
1200
1201 if(tf.f == nil)
1202 editerror("no file matches \"%S\"", r->r);
1203 return tf.f;
1204}
1205
1206int
1207filematch(File *f, String *r)
1208{
1209 char *buf;
1210 Rune *rbuf;
1211 Window *w;
1212 int match, i, dirty;
1213 Rangeset s;
1214
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;
1229}
1230
1231Address
1232charaddr(long l, Address addr, int sign)
1233{
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;
1243}
1244
1245Address
1246lineaddr(long l, Address addr, int sign)
1247{
1248 int n;
1249 int c;
1250 File *f = addr.f;
1251 Address a;
1252 long p;
1253
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;
1260 }
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';
1270 }
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++;
1276 }
1277 a.r.q0 = p;
1278 }
1279 while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1280 ;
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--;
1295 }
1296 }
1297 a.r.q1 = p;
1298 if(p > 0)
1299 p--;
1300 }
1301 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1302 p--;
1303 a.r.q0 = p;
1304 }
1305 return a;
1306}
1307
1308struct Filecheck
1309{
1310 File *f;
1311 Rune *r;
1312 int nr;
1313};
1314
1315void
1316allfilecheck(Window *w, void *v)
1317{
1318 struct Filecheck *fp;
1319 File *f;
1320
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);
1327}
1328
1329Rune*
1330cmdname(File *f, String *str, int set)
1331{
1332 Rune *r, *s;
1333 int n;
1334 struct Filecheck fc;
1335 Runestr newname;
1336
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;
1347 }
1348 s = skipbl(s, n, &n);
1349 if(n == 0)
1350 goto Return;
1351
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);
1361 }
1362 fc.f = f;
1363 fc.r = r;
1364 fc.nr = n;
1365 allwindows(allfilecheck, &fc);
1366 if(f->nname == 0)
1367 set = TRUE;
1368
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);
1375 }
1376 return r;
1377}