tecmd.c - plan9port - [fork] Plan 9 from user space
HTML git clone git://src.adamsgaard.dk/plan9port
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tecmd.c (26091B)
---
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
16 int Glooping;
17 int nest;
18 char Enoname[] = "no file name given";
19
20 Address addr;
21 File *menu;
22 Rangeset sel;
23 extern Text* curtext;
24 Rune *collection;
25 int ncollection;
26
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);
38
39 void
40 clearcollection(void)
41 {
42 free(collection);
43 collection = nil;
44 ncollection = 0;
45 }
46
47 void
48 resetxec(void)
49 {
50 Glooping = nest = 0;
51 clearcollection();
52 }
53
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 }
61
62 int
63 cmdexec(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
131 char*
132 edittext(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 */
155 Rune*
156 filelist(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
169 int
170 a_cmd(Text *t, Cmd *cp)
171 {
172 return append(t->file, cp, addr.r.q1);
173 }
174
175 int
176 b_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
188 int
189 B_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
213 int
214 c_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
222 int
223 d_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
233 void
234 D1(Text *t)
235 {
236 if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
237 colclose(t->col, t->w, TRUE);
238 }
239
240 int
241 D_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
289 static int
290 readloader(void *v, uint q0, Rune *r, int nr)
291 {
292 if(nr > 0)
293 eloginsert(v, q0, r, nr);
294 return 0;
295 }
296
297 int
298 e_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
350 static Rune Lempty[] = { 0 };
351 int
352 f_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
370 int
371 g_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
387 int
388 i_cmd(Text *t, Cmd *cp)
389 {
390 return append(t->file, cp, addr.r.q0);
391 }
392
393 void
394 copy(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
411 void
412 move(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
426 int
427 m_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
440 int
441 p_cmd(Text *t, Cmd *cp)
442 {
443 USED(cp);
444 return pdisplay(t->file);
445 }
446
447 int
448 s_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
528 Err:
529 free(rp);
530 freestring(buf);
531 fbuffree(rbuf);
532 editerror(err);
533 return FALSE;
534 }
535
536 int
537 u_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
555 int
556 w_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
572 int
573 x_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
582 int
583 X_cmd(Text *t, Cmd *cp)
584 {
585 USED(t);
586
587 filelooper(t, cp, cp->cmdc=='X');
588 return TRUE;
589 }
590
591 void
592 runpipe(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
656 int
657 pipe_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
663 long
664 nlcount(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
694 enum {
695 PosnLine = 0,
696 PosnChars = 1,
697 PosnLineChars = 2,
698 };
699
700 void
701 printposn(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
742 int
743 eq_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
768 int
769 nl_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
790 int
791 append(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
800 int
801 pdisplay(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
827 void
828 pfilename(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
840 void
841 loopcmd(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
852 void
853 looper(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
896 void
897 linelooper(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
937 struct 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
945 void
946 alllooper(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
971 void
972 alllocker(Window *w, void *v)
973 {
974 if(v)
975 incref(&w->ref);
976 else
977 winclose(w);
978 }
979
980 void
981 filelooper(Text *t, Cmd *cp, int XY)
982 {
983 int i;
984 Text *targ;
985
986 if(Glooping++)
987 editerror("can't nest %c command", "YX"[XY]);
988 nest++;
989
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.
1002 */
1003 allwindows(alllocker, (void*)1);
1004 globalincref = 1;
1005
1006 /*
1007 * Unlock the window running the X command.
1008 * We'll need to lock and unlock each target window in turn.
1009 */
1010 if(t && t->w)
1011 winunlock(t->w);
1012
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);
1020 }
1021
1022 if(t && t->w)
1023 winlock(t->w, cp->cmdc);
1024
1025 allwindows(alllocker, (void*)0);
1026 globalincref = 0;
1027 free(loopstruct.w);
1028 loopstruct.w = nil;
1029
1030 --Glooping;
1031 --nest;
1032 }
1033
1034 void
1035 nextmatch(File *f, String *r, long p, int sign)
1036 {
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");
1047 }
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");
1056 }
1057 }
1058 }
1059
1060 File *matchfile(String*);
1061 Address charaddr(long, Address, int);
1062 Address lineaddr(long, Address, int);
1063
1064 Address
1065 cmdaddress(Addr *ap, Address a, int sign)
1066 {
1067 File *f = a.f;
1068 Address a1, a2;
1069
1070 do{
1071 switch(ap->type){
1072 case 'l':
1073 case '#':
1074 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1075 break;
1076
1077 case '.':
1078 mkaddr(&a, f);
1079 break;
1080
1081 case '$':
1082 a.r.q0 = a.r.q1 = f->b.nc;
1083 break;
1084
1085 case '\'':
1086 editerror("can't handle '");
1087 /* a.r = f->mark; */
1088 break;
1089
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;
1099
1100 case '"':
1101 f = matchfile(ap->u.re);
1102 mkaddr(&a, f);
1103 break;
1104
1105 case '*':
1106 a.r.q0 = 0, a.r.q1 = f->b.nc;
1107 return a;
1108
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;
1120 }
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;
1131
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;
1143 }
1144 }while(ap = ap->next); /* assign = */
1145 return a;
1146 }
1147
1148 struct Tofile{
1149 File *f;
1150 String *r;
1151 };
1152
1153 void
1154 alltofile(Window *w, void *v)
1155 {
1156 Text *t;
1157 struct Tofile *tp;
1158
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;
1172 }
1173
1174 File*
1175 tofile(String *r)
1176 {
1177 struct Tofile t;
1178 String rr;
1179
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;
1187 }
1188
1189 void
1190 allmatchfile(Window *w, void *v)
1191 {
1192 struct Tofile *tp;
1193 Text *t;
1194
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;
1208 }
1209 }
1210
1211 File*
1212 matchfile(String *r)
1213 {
1214 struct Tofile tf;
1215
1216 tf.f = nil;
1217 tf.r = r;
1218 allwindows(allmatchfile, &tf);
1219
1220 if(tf.f == nil)
1221 editerror("no file matches \"%S\"", r->r);
1222 return tf.f;
1223 }
1224
1225 int
1226 filematch(File *f, String *r)
1227 {
1228 char *buf;
1229 Rune *rbuf;
1230 Window *w;
1231 int match, i, dirty;
1232 Rangeset s;
1233
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;
1248 }
1249
1250 Address
1251 charaddr(long l, Address addr, int sign)
1252 {
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;
1262 }
1263
1264 Address
1265 lineaddr(long l, Address addr, int sign)
1266 {
1267 int n;
1268 int c;
1269 File *f = addr.f;
1270 Address a;
1271 long p;
1272
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;
1279 }
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';
1289 }
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++;
1295 }
1296 a.r.q0 = p;
1297 }
1298 while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1299 ;
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--;
1314 }
1315 }
1316 a.r.q1 = p;
1317 if(p > 0)
1318 p--;
1319 }
1320 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1321 p--;
1322 a.r.q0 = p;
1323 }
1324 return a;
1325 }
1326
1327 struct Filecheck
1328 {
1329 File *f;
1330 Rune *r;
1331 int nr;
1332 };
1333
1334 void
1335 allfilecheck(Window *w, void *v)
1336 {
1337 struct Filecheck *fp;
1338 File *f;
1339
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);
1346 }
1347
1348 Rune*
1349 cmdname(File *f, String *str, int set)
1350 {
1351 Rune *r, *s;
1352 int n;
1353 struct Filecheck fc;
1354 Runestr newname;
1355
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;
1366 }
1367 s = skipbl(s, n, &n);
1368 if(n == 0)
1369 goto Return;
1370
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);
1380 }
1381 fc.f = f;
1382 fc.r = r;
1383 fc.nr = n;
1384 allwindows(allfilecheck, &fc);
1385 if(f->nname == 0)
1386 set = TRUE;
1387
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);
1394 }
1395 return r;
1396 }