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"
14
15 int Glooping;
16 int nest;
17 char Enoname[] = "no file name given";
18
19 Address addr;
20 File *menu;
21 Rangeset sel;
22 extern Text* curtext;
23 Rune *collection;
24 int ncollection;
25
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);
37
38 void
clearcollection(void)39 clearcollection(void)
40 {
41 free(collection);
42 collection = nil;
43 ncollection = 0;
44 }
45
46 void
resetxec(void)47 resetxec(void)
48 {
49 Glooping = nest = 0;
50 clearcollection();
51 }
52
53 void
mkaddr(Address * a,File * f)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 }
60
61 int
cmdexec(Text * t,Cmd * cp)62 cmdexec(Text *t, Cmd *cp)
63 {
64 int i;
65 Addr *ap;
66 File *f;
67 Window *w;
68 Address dot;
69
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->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;
106 }
107 }
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->cmd; cp; cp = cp->next){
114 if(dot.r.q1 > t->file->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);
119 }
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;
126 }
127 return 1;
128 }
129
130 char*
edittext(Window * w,int q,Rune * r,int nr)131 edittext(Window *w, int q, Rune *r, int nr)
132 {
133 File *f;
134
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";
150 }
151 }
152
153 /* string is known to be NUL-terminated */
154 Rune*
filelist(Text * t,Rune * r,int nr)155 filelist(Text *t, Rune *r, int nr)
156 {
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;
166 }
167
168 int
a_cmd(Text * t,Cmd * cp)169 a_cmd(Text *t, Cmd *cp)
170 {
171 return append(t->file, cp, addr.r.q1);
172 }
173
174 int
b_cmd(Text *,Cmd * cp)175 b_cmd(Text*, Cmd *cp)
176 {
177 File *f;
178
179 f = tofile(cp->text);
180 if(nest == 0)
181 pfilename(f);
182 curtext = f->curtext;
183 return TRUE;
184 }
185
186 int
B_cmd(Text * t,Cmd * cp)187 B_cmd(Text *t, Cmd *cp)
188 {
189 Rune *list, *r, *s;
190 int nr;
191
192 list = filelist(t, cp->text->r, cp->text->n);
193 if(list == nil)
194 editerror(Enoname);
195 r = list;
196 nr = runestrlen(r);
197 r = skipbl(r, nr, &nr);
198 if(nr == 0)
199 new(t, t, nil, 0, 0, r, 0);
200 else while(nr > 0){
201 s = findbl(r, nr, &nr);
202 *s = '\0';
203 new(t, t, nil, 0, 0, r, runestrlen(r));
204 if(nr > 0)
205 r = skipbl(s+1, nr-1, &nr);
206 }
207 clearcollection();
208 return TRUE;
209 }
210
211 int
c_cmd(Text * t,Cmd * cp)212 c_cmd(Text *t, Cmd *cp)
213 {
214 elogreplace(t->file, addr.r.q0, addr.r.q1, cp->text->r, cp->text->n);
215 t->q0 = addr.r.q0;
216 t->q1 = addr.r.q0;
217 return TRUE;
218 }
219
220 int
d_cmd(Text * t,Cmd *)221 d_cmd(Text *t, Cmd*)
222 {
223 if(addr.r.q1 > addr.r.q0)
224 elogdelete(t->file, addr.r.q0, addr.r.q1);
225 t->q0 = addr.r.q0;
226 t->q1 = addr.r.q0;
227 return TRUE;
228 }
229
230 void
D1(Text * t)231 D1(Text *t)
232 {
233 if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
234 colclose(t->col, t->w, TRUE);
235 }
236
237 int
D_cmd(Text * t,Cmd * cp)238 D_cmd(Text *t, Cmd *cp)
239 {
240 Rune *list, *r, *s, *n;
241 int nr, nn;
242 Window *w;
243 Runestr dir, rs;
244 char buf[128];
245
246 list = filelist(t, cp->text->r, cp->text->n);
247 if(list == nil){
248 D1(t);
249 return TRUE;
250 }
251 dir = dirname(t, nil, 0);
252 r = list;
253 nr = runestrlen(r);
254 r = skipbl(r, nr, &nr);
255 do{
256 s = findbl(r, nr, &nr);
257 *s = '\0';
258 /* first time through, could be empty string, meaning delete file empty name */
259 nn = runestrlen(r);
260 if(r[0]=='/' || nn==0 || dir.nr==0){
261 rs.r = runestrdup(r);
262 rs.nr = nn;
263 }else{
264 n = runemalloc(dir.nr+1+nn);
265 runemove(n, dir.r, dir.nr);
266 n[dir.nr] = '/';
267 runemove(n+dir.nr+1, r, nn);
268 rs = cleanrname((Runestr){n, dir.nr+1+nn});
269 }
270 w = lookfile(rs.r, rs.nr);
271 if(w == nil){
272 snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
273 free(rs.r);
274 editerror(buf);
275 }
276 free(rs.r);
277 D1(&w->body);
278 if(nr > 0)
279 r = skipbl(s+1, nr-1, &nr);
280 }while(nr > 0);
281 clearcollection();
282 free(dir.r);
283 return TRUE;
284 }
285
286 static int
readloader(void * v,uint q0,Rune * r,int nr)287 readloader(void *v, uint q0, Rune *r, int nr)
288 {
289 if(nr > 0)
290 eloginsert(v, q0, r, nr);
291 return 0;
292 }
293
294 int
e_cmd(Text * t,Cmd * cp)295 e_cmd(Text *t, Cmd *cp)
296 {
297 Rune *name;
298 File *f;
299 int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
300 char *s, tmp[128];
301 Dir *d;
302
303 f = t->file;
304 q0 = addr.r.q0;
305 q1 = addr.r.q1;
306 if(cp->cmdc == 'e'){
307 if(winclean(t->w, TRUE)==FALSE)
308 editerror(""); /* winclean generated message already */
309 q0 = 0;
310 q1 = f->nc;
311 }
312 allreplaced = (q0==0 && q1==f->nc);
313 name = cmdname(f, cp->text, cp->cmdc=='e');
314 if(name == nil)
315 editerror(Enoname);
316 i = runestrlen(name);
317 samename = runeeq(name, i, t->file->name, t->file->nname);
318 s = runetobyte(name, i);
319 free(name);
320 fd = open(s, OREAD);
321 if(fd < 0){
322 snprint(tmp, sizeof tmp, "can't open %s: %r", s);
323 free(s);
324 editerror(tmp);
325 }
326 d = dirfstat(fd);
327 isdir = (d!=nil && (d->qid.type&QTDIR));
328 free(d);
329 if(isdir){
330 close(fd);
331 snprint(tmp, sizeof tmp, "%s is a directory", s);
332 free(s);
333 editerror(tmp);
334 }
335 elogdelete(f, q0, q1);
336 nulls = 0;
337 loadfile(fd, q1, &nulls, readloader, f);
338 free(s);
339 close(fd);
340 if(nulls)
341 warning(nil, "%s: NUL bytes elided\n", s);
342 else if(allreplaced && samename)
343 f->editclean = TRUE;
344 return TRUE;
345 }
346
347 int
f_cmd(Text * t,Cmd * cp)348 f_cmd(Text *t, Cmd *cp)
349 {
350 Rune *name;
351 String *str;
352 String empty;
353
354 if(cp->text == nil){
355 empty.n = 0;
356 empty.r = L"";
357 str = ∅
358 }else
359 str = cp->text;
360 name = cmdname(t->file, str, TRUE);
361 free(name);
362 pfilename(t->file);
363 return TRUE;
364 }
365
366 int
g_cmd(Text * t,Cmd * cp)367 g_cmd(Text *t, Cmd *cp)
368 {
369 if(t->file != addr.f){
370 warning(nil, "internal error: g_cmd f!=addr.f\n");
371 return FALSE;
372 }
373 if(rxcompile(cp->re->r) == FALSE)
374 editerror("bad regexp in g command");
375 if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
376 t->q0 = addr.r.q0;
377 t->q1 = addr.r.q1;
378 return cmdexec(t, cp->cmd);
379 }
380 return TRUE;
381 }
382
383 int
i_cmd(Text * t,Cmd * cp)384 i_cmd(Text *t, Cmd *cp)
385 {
386 return append(t->file, cp, addr.r.q0);
387 }
388
389 void
copy(File * f,Address addr2)390 copy(File *f, Address addr2)
391 {
392 long p;
393 int ni;
394 Rune *buf;
395
396 buf = fbufalloc();
397 for(p=addr.r.q0; p<addr.r.q1; p+=ni){
398 ni = addr.r.q1-p;
399 if(ni > RBUFSIZE)
400 ni = RBUFSIZE;
401 bufread(f, p, buf, ni);
402 eloginsert(addr2.f, addr2.r.q1, buf, ni);
403 }
404 fbuffree(buf);
405 }
406
407 void
move(File * f,Address addr2)408 move(File *f, Address addr2)
409 {
410 if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
411 elogdelete(f, addr.r.q0, addr.r.q1);
412 copy(f, addr2);
413 }else if(addr.r.q0 >= addr2.r.q1){
414 copy(f, addr2);
415 elogdelete(f, addr.r.q0, addr.r.q1);
416 }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
417 ; /* move to self; no-op */
418 }else
419 editerror("move overlaps itself");
420 }
421
422 int
m_cmd(Text * t,Cmd * cp)423 m_cmd(Text *t, Cmd *cp)
424 {
425 Address dot, addr2;
426
427 mkaddr(&dot, t->file);
428 addr2 = cmdaddress(cp->mtaddr, dot, 0);
429 if(cp->cmdc == 'm')
430 move(t->file, addr2);
431 else
432 copy(t->file, addr2);
433 return TRUE;
434 }
435
436 int
p_cmd(Text * t,Cmd *)437 p_cmd(Text *t, Cmd*)
438 {
439 return pdisplay(t->file);
440 }
441
442 int
s_cmd(Text * t,Cmd * cp)443 s_cmd(Text *t, Cmd *cp)
444 {
445 int i, j, k, c, m, n, nrp, didsub;
446 long p1, op, delta;
447 String *buf;
448 Rangeset *rp;
449 char *err;
450 Rune *rbuf;
451
452 n = cp->num;
453 op= -1;
454 if(rxcompile(cp->re->r) == FALSE)
455 editerror("bad regexp in s command");
456 nrp = 0;
457 rp = nil;
458 delta = 0;
459 didsub = FALSE;
460 for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
461 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
462 if(sel.r[0].q0 == op){
463 p1++;
464 continue;
465 }
466 p1 = sel.r[0].q1+1;
467 }else
468 p1 = sel.r[0].q1;
469 op = sel.r[0].q1;
470 if(--n>0)
471 continue;
472 nrp++;
473 rp = erealloc(rp, nrp*sizeof(Rangeset));
474 rp[nrp-1] = sel;
475 }
476 rbuf = fbufalloc();
477 buf = allocstring(0);
478 for(m=0; m<nrp; m++){
479 buf->n = 0;
480 buf->r[0] = L'\0';
481 sel = rp[m];
482 for(i = 0; i<cp->text->n; i++)
483 if((c = cp->text->r[i])=='\\' && i<cp->text->n-1){
484 c = cp->text->r[++i];
485 if('1'<=c && c<='9') {
486 j = c-'0';
487 if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
488 err = "replacement string too long";
489 goto Err;
490 }
491 bufread(t->file, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
492 for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
493 Straddc(buf, rbuf[k]);
494 }else
495 Straddc(buf, c);
496 }else if(c!='&')
497 Straddc(buf, c);
498 else{
499 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
500 err = "right hand side too long in substitution";
501 goto Err;
502 }
503 bufread(t->file, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
504 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
505 Straddc(buf, rbuf[k]);
506 }
507 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
508 delta -= sel.r[0].q1-sel.r[0].q0;
509 delta += buf->n;
510 didsub = 1;
511 if(!cp->flag)
512 break;
513 }
514 free(rp);
515 freestring(buf);
516 fbuffree(rbuf);
517 if(!didsub && nest==0)
518 editerror("no substitution");
519 t->q0 = addr.r.q0;
520 t->q1 = addr.r.q1;
521 return TRUE;
522
523 Err:
524 free(rp);
525 freestring(buf);
526 fbuffree(rbuf);
527 editerror(err);
528 return FALSE;
529 }
530
531 int
u_cmd(Text * t,Cmd * cp)532 u_cmd(Text *t, Cmd *cp)
533 {
534 int n, oseq, flag;
535
536 n = cp->num;
537 flag = TRUE;
538 if(n < 0){
539 n = -n;
540 flag = FALSE;
541 }
542 oseq = -1;
543 while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){
544 oseq = t->file->seq;
545 undo(t, nil, nil, flag, 0, nil, 0);
546 }
547 return TRUE;
548 }
549
550 int
w_cmd(Text * t,Cmd * cp)551 w_cmd(Text *t, Cmd *cp)
552 {
553 Rune *r;
554 File *f;
555
556 f = t->file;
557 if(f->seq == seq)
558 editerror("can't write file with pending modifications");
559 r = cmdname(f, cp->text, FALSE);
560 if(r == nil)
561 editerror("no name specified for 'w' command");
562 putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
563 /* r is freed by putfile */
564 return TRUE;
565 }
566
567 int
x_cmd(Text * t,Cmd * cp)568 x_cmd(Text *t, Cmd *cp)
569 {
570 if(cp->re)
571 looper(t->file, cp, cp->cmdc=='x');
572 else
573 linelooper(t->file, cp);
574 return TRUE;
575 }
576
577 int
X_cmd(Text *,Cmd * cp)578 X_cmd(Text*, Cmd *cp)
579 {
580 filelooper(cp, cp->cmdc=='X');
581 return TRUE;
582 }
583
584 void
runpipe(Text * t,int cmd,Rune * cr,int ncr,int state)585 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
586 {
587 Rune *r, *s;
588 int n;
589 Runestr dir;
590 Window *w;
591
592 r = skipbl(cr, ncr, &n);
593 if(n == 0)
594 editerror("no command specified for %c", cmd);
595 w = nil;
596 if(state == Inserting){
597 w = t->w;
598 t->q0 = addr.r.q0;
599 t->q1 = addr.r.q1;
600 if(cmd == '<' || cmd=='|')
601 elogdelete(t->file, t->q0, t->q1);
602 }
603 s = runemalloc(n+2);
604 s[0] = cmd;
605 runemove(s+1, r, n);
606 n++;
607 dir.r = nil;
608 dir.nr = 0;
609 if(t != nil)
610 dir = dirname(t, nil, 0);
611 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
612 free(dir.r);
613 dir.r = nil;
614 dir.nr = 0;
615 }
616 editing = state;
617 if(t!=nil && t->w!=nil)
618 incref(t->w); /* run will decref */
619 run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
620 free(s);
621 if(t!=nil && t->w!=nil)
622 winunlock(t->w);
623 qunlock(&row);
624 recvul(cedit);
625 qlock(&row);
626 editing = Inactive;
627 if(t!=nil && t->w!=nil)
628 winlock(t->w, 'M');
629 }
630
631 int
pipe_cmd(Text * t,Cmd * cp)632 pipe_cmd(Text *t, Cmd *cp)
633 {
634 runpipe(t, cp->cmdc, cp->text->r, cp->text->n, Inserting);
635 return TRUE;
636 }
637
638 long
nlcount(Text * t,long q0,long q1)639 nlcount(Text *t, long q0, long q1)
640 {
641 long nl;
642 Rune *buf;
643 int i, nbuf;
644
645 buf = fbufalloc();
646 nbuf = 0;
647 i = nl = 0;
648 while(q0 < q1){
649 if(i == nbuf){
650 nbuf = q1-q0;
651 if(nbuf > RBUFSIZE)
652 nbuf = RBUFSIZE;
653 bufread(t->file, q0, buf, nbuf);
654 i = 0;
655 }
656 if(buf[i++] == '\n')
657 nl++;
658 q0++;
659 }
660 fbuffree(buf);
661 return nl;
662 }
663
664 void
printposn(Text * t,int charsonly)665 printposn(Text *t, int charsonly)
666 {
667 long l1, l2;
668
669 if (t != nil && t->file != nil && t->file->name != nil)
670 warning(nil, "%.*S:", t->file->nname, t->file->name);
671 if(!charsonly){
672 l1 = 1+nlcount(t, 0, addr.r.q0);
673 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
674 /* check if addr ends with '\n' */
675 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
676 --l2;
677 warning(nil, "%lud", l1);
678 if(l2 != l1)
679 warning(nil, ",%lud", l2);
680 warning(nil, "\n");
681 return;
682 }
683 warning(nil, "#%d", addr.r.q0);
684 if(addr.r.q1 != addr.r.q0)
685 warning(nil, ",#%d", addr.r.q1);
686 warning(nil, "\n");
687 }
688
689 int
eq_cmd(Text * t,Cmd * cp)690 eq_cmd(Text *t, Cmd *cp)
691 {
692 int charsonly;
693
694 switch(cp->text->n){
695 case 0:
696 charsonly = FALSE;
697 break;
698 case 1:
699 if(cp->text->r[0] == '#'){
700 charsonly = TRUE;
701 break;
702 }
703 default:
704 SET(charsonly);
705 editerror("newline expected");
706 }
707 printposn(t, charsonly);
708 return TRUE;
709 }
710
711 int
nl_cmd(Text * t,Cmd * cp)712 nl_cmd(Text *t, Cmd *cp)
713 {
714 Address a;
715 File *f;
716
717 f = t->file;
718 if(cp->addr == 0){
719 /* First put it on newline boundaries */
720 mkaddr(&a, f);
721 addr = lineaddr(0, a, -1);
722 a = lineaddr(0, a, 1);
723 addr.r.q1 = a.r.q1;
724 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
725 mkaddr(&a, f);
726 addr = lineaddr(1, a, 1);
727 }
728 }
729 textshow(t, addr.r.q0, addr.r.q1, 1);
730 return TRUE;
731 }
732
733 int
append(File * f,Cmd * cp,long p)734 append(File *f, Cmd *cp, long p)
735 {
736 if(cp->text->n > 0)
737 eloginsert(f, p, cp->text->r, cp->text->n);
738 f->curtext->q0 = p;
739 f->curtext->q1 = p;
740 return TRUE;
741 }
742
743 int
pdisplay(File * f)744 pdisplay(File *f)
745 {
746 long p1, p2;
747 int np;
748 Rune *buf;
749
750 p1 = addr.r.q0;
751 p2 = addr.r.q1;
752 if(p2 > f->nc)
753 p2 = f->nc;
754 buf = fbufalloc();
755 while(p1 < p2){
756 np = p2-p1;
757 if(np>RBUFSIZE-1)
758 np = RBUFSIZE-1;
759 bufread(f, p1, buf, np);
760 buf[np] = L'\0';
761 warning(nil, "%S", buf);
762 p1 += np;
763 }
764 fbuffree(buf);
765 f->curtext->q0 = addr.r.q0;
766 f->curtext->q1 = addr.r.q1;
767 return TRUE;
768 }
769
770 void
pfilename(File * f)771 pfilename(File *f)
772 {
773 int dirty;
774 Window *w;
775
776 w = f->curtext->w;
777 /* same check for dirty as in settag, but we know ncache==0 */
778 dirty = !w->isdir && !w->isscratch && f->mod;
779 warning(nil, "%c%c%c %.*S\n", " '"[dirty],
780 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
781 }
782
783 void
loopcmd(File * f,Cmd * cp,Range * rp,long nrp)784 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
785 {
786 long i;
787
788 for(i=0; i<nrp; i++){
789 f->curtext->q0 = rp[i].q0;
790 f->curtext->q1 = rp[i].q1;
791 cmdexec(f->curtext, cp);
792 }
793 }
794
795 void
looper(File * f,Cmd * cp,int xy)796 looper(File *f, Cmd *cp, int xy)
797 {
798 long p, op, nrp;
799 Range r, tr;
800 Range *rp;
801
802 r = addr.r;
803 op= xy? -1 : r.q0;
804 nest++;
805 if(rxcompile(cp->re->r) == FALSE)
806 editerror("bad regexp in %c command", cp->cmdc);
807 nrp = 0;
808 rp = nil;
809 for(p = r.q0; p<=r.q1; ){
810 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
811 if(xy || op>r.q1)
812 break;
813 tr.q0 = op, tr.q1 = r.q1;
814 p = r.q1+1; /* exit next loop */
815 }else{
816 if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
817 if(sel.r[0].q0==op){
818 p++;
819 continue;
820 }
821 p = sel.r[0].q1+1;
822 }else
823 p = sel.r[0].q1;
824 if(xy)
825 tr = sel.r[0];
826 else
827 tr.q0 = op, tr.q1 = sel.r[0].q0;
828 }
829 op = sel.r[0].q1;
830 nrp++;
831 rp = erealloc(rp, nrp*sizeof(Range));
832 rp[nrp-1] = tr;
833 }
834 loopcmd(f, cp->cmd, rp, nrp);
835 free(rp);
836 --nest;
837 }
838
839 void
linelooper(File * f,Cmd * cp)840 linelooper(File *f, Cmd *cp)
841 {
842 long nrp, p;
843 Range r, linesel;
844 Address a, a3;
845 Range *rp;
846
847 nest++;
848 nrp = 0;
849 rp = nil;
850 r = addr.r;
851 a3.f = f;
852 a3.r.q0 = a3.r.q1 = r.q0;
853 a = lineaddr(0, a3, 1);
854 linesel = a.r;
855 for(p = r.q0; p<r.q1; p = a3.r.q1){
856 a3.r.q0 = a3.r.q1;
857 if(p!=r.q0 || linesel.q1==p){
858 a = lineaddr(1, a3, 1);
859 linesel = a.r;
860 }
861 if(linesel.q0 >= r.q1)
862 break;
863 if(linesel.q1 >= r.q1)
864 linesel.q1 = r.q1;
865 if(linesel.q1 > linesel.q0)
866 if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
867 a3.r = linesel;
868 nrp++;
869 rp = erealloc(rp, nrp*sizeof(Range));
870 rp[nrp-1] = linesel;
871 continue;
872 }
873 break;
874 }
875 loopcmd(f, cp->cmd, rp, nrp);
876 free(rp);
877 --nest;
878 }
879
880 struct Looper
881 {
882 Cmd *cp;
883 int XY;
884 Window **w;
885 int nw;
886 } loopstruct; /* only one; X and Y can't nest */
887
888 void
alllooper(Window * w,void * v)889 alllooper(Window *w, void *v)
890 {
891 Text *t;
892 struct Looper *lp;
893 Cmd *cp;
894
895 lp = v;
896 cp = lp->cp;
897 // if(w->isscratch || w->isdir)
898 // return;
899 t = &w->body;
900 /* only use this window if it's the current window for the file */
901 if(t->file->curtext != t)
902 return;
903 // if(w->nopen[QWevent] > 0)
904 // return;
905 /* no auto-execute on files without names */
906 if(cp->re==nil && t->file->nname==0)
907 return;
908 if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
909 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
910 lp->w[lp->nw++] = w;
911 }
912 }
913
914 void
alllocker(Window * w,void * v)915 alllocker(Window *w, void *v)
916 {
917 if(v)
918 incref(w);
919 else
920 winclose(w);
921 }
922
923 void
filelooper(Cmd * cp,int XY)924 filelooper(Cmd *cp, int XY)
925 {
926 int i;
927
928 if(Glooping++)
929 editerror("can't nest %c command", "YX"[XY]);
930 nest++;
931
932 loopstruct.cp = cp;
933 loopstruct.XY = XY;
934 if(loopstruct.w) /* error'ed out last time */
935 free(loopstruct.w);
936 loopstruct.w = nil;
937 loopstruct.nw = 0;
938 allwindows(alllooper, &loopstruct);
939 /*
940 * add a ref to all windows to keep safe windows accessed by X
941 * that would not otherwise have a ref to hold them up during
942 * the shenanigans. note this with globalincref so that any
943 * newly created windows start with an extra reference.
944 */
945 allwindows(alllocker, (void*)1);
946 globalincref = 1;
947 for(i=0; i<loopstruct.nw; i++)
948 cmdexec(&loopstruct.w[i]->body, cp->cmd);
949 allwindows(alllocker, (void*)0);
950 globalincref = 0;
951 free(loopstruct.w);
952 loopstruct.w = nil;
953
954 --Glooping;
955 --nest;
956 }
957
958 void
nextmatch(File * f,String * r,long p,int sign)959 nextmatch(File *f, String *r, long p, int sign)
960 {
961 if(rxcompile(r->r) == FALSE)
962 editerror("bad regexp in command address");
963 if(sign >= 0){
964 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
965 editerror("no match for regexp");
966 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
967 if(++p>f->nc)
968 p = 0;
969 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
970 editerror("address");
971 }
972 }else{
973 if(!rxbexecute(f->curtext, p, &sel))
974 editerror("no match for regexp");
975 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
976 if(--p<0)
977 p = f->nc;
978 if(!rxbexecute(f->curtext, p, &sel))
979 editerror("address");
980 }
981 }
982 }
983
984 File *matchfile(String*);
985 Address charaddr(long, Address, int);
986 Address lineaddr(long, Address, int);
987
988 Address
cmdaddress(Addr * ap,Address a,int sign)989 cmdaddress(Addr *ap, Address a, int sign)
990 {
991 File *f = a.f;
992 Address a1, a2;
993
994 do{
995 switch(ap->type){
996 case 'l':
997 case '#':
998 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
999 break;
1000
1001 case '.':
1002 mkaddr(&a, f);
1003 break;
1004
1005 case '$':
1006 a.r.q0 = a.r.q1 = f->nc;
1007 break;
1008
1009 case '\'':
1010 editerror("can't handle '");
1011 // a.r = f->mark;
1012 break;
1013
1014 case '?':
1015 sign = -sign;
1016 if(sign == 0)
1017 sign = -1;
1018 /* fall through */
1019 case '/':
1020 nextmatch(f, ap->re, sign>=0? a.r.q1 : a.r.q0, sign);
1021 a.r = sel.r[0];
1022 break;
1023
1024 case '"':
1025 f = matchfile(ap->re);
1026 mkaddr(&a, f);
1027 break;
1028
1029 case '*':
1030 a.r.q0 = 0, a.r.q1 = f->nc;
1031 return a;
1032
1033 case ',':
1034 case ';':
1035 if(ap->left)
1036 a1 = cmdaddress(ap->left, a, 0);
1037 else
1038 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1039 if(ap->type == ';'){
1040 f = a1.f;
1041 a = a1;
1042 f->curtext->q0 = a1.r.q0;
1043 f->curtext->q1 = a1.r.q1;
1044 }
1045 if(ap->next)
1046 a2 = cmdaddress(ap->next, a, 0);
1047 else
1048 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->nc;
1049 if(a1.f != a2.f)
1050 editerror("addresses in different files");
1051 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1052 if(a.r.q1 < a.r.q0)
1053 editerror("addresses out of order");
1054 return a;
1055
1056 case '+':
1057 case '-':
1058 sign = 1;
1059 if(ap->type == '-')
1060 sign = -1;
1061 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1062 a = lineaddr(1L, a, sign);
1063 break;
1064 default:
1065 error("cmdaddress");
1066 return a;
1067 }
1068 }while(ap = ap->next); /* assign = */
1069 return a;
1070 }
1071
1072 struct Tofile{
1073 File *f;
1074 String *r;
1075 };
1076
1077 void
alltofile(Window * w,void * v)1078 alltofile(Window *w, void *v)
1079 {
1080 Text *t;
1081 struct Tofile *tp;
1082
1083 tp = v;
1084 if(tp->f != nil)
1085 return;
1086 if(w->isscratch || w->isdir)
1087 return;
1088 t = &w->body;
1089 /* only use this window if it's the current window for the file */
1090 if(t->file->curtext != t)
1091 return;
1092 // if(w->nopen[QWevent] > 0)
1093 // return;
1094 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1095 tp->f = t->file;
1096 }
1097
1098 File*
tofile(String * r)1099 tofile(String *r)
1100 {
1101 struct Tofile t;
1102 String rr;
1103
1104 rr.r = skipbl(r->r, r->n, &rr.n);
1105 t.f = nil;
1106 t.r = &rr;
1107 allwindows(alltofile, &t);
1108 if(t.f == nil)
1109 editerror("no such file\"%S\"", rr.r);
1110 return t.f;
1111 }
1112
1113 void
allmatchfile(Window * w,void * v)1114 allmatchfile(Window *w, void *v)
1115 {
1116 struct Tofile *tp;
1117 Text *t;
1118
1119 tp = v;
1120 if(w->isscratch || w->isdir)
1121 return;
1122 t = &w->body;
1123 /* only use this window if it's the current window for the file */
1124 if(t->file->curtext != t)
1125 return;
1126 // if(w->nopen[QWevent] > 0)
1127 // return;
1128 if(filematch(w->body.file, tp->r)){
1129 if(tp->f != nil)
1130 editerror("too many files match \"%S\"", tp->r->r);
1131 tp->f = w->body.file;
1132 }
1133 }
1134
1135 File*
matchfile(String * r)1136 matchfile(String *r)
1137 {
1138 struct Tofile tf;
1139
1140 tf.f = nil;
1141 tf.r = r;
1142 allwindows(allmatchfile, &tf);
1143
1144 if(tf.f == nil)
1145 editerror("no file matches \"%S\"", r->r);
1146 return tf.f;
1147 }
1148
1149 int
filematch(File * f,String * r)1150 filematch(File *f, String *r)
1151 {
1152 char *buf;
1153 Rune *rbuf;
1154 Window *w;
1155 int match, i, dirty;
1156 Rangeset s;
1157
1158 /* compile expr first so if we get an error, we haven't allocated anything */
1159 if(rxcompile(r->r) == FALSE)
1160 editerror("bad regexp in file match");
1161 buf = fbufalloc();
1162 w = f->curtext->w;
1163 /* same check for dirty as in settag, but we know ncache==0 */
1164 dirty = !w->isdir && !w->isscratch && f->mod;
1165 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1166 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1167 rbuf = bytetorune(buf, &i);
1168 fbuffree(buf);
1169 match = rxexecute(nil, rbuf, 0, i, &s);
1170 free(rbuf);
1171 return match;
1172 }
1173
1174 Address
charaddr(long l,Address addr,int sign)1175 charaddr(long l, Address addr, int sign)
1176 {
1177 if(sign == 0)
1178 addr.r.q0 = addr.r.q1 = l;
1179 else if(sign < 0)
1180 addr.r.q1 = addr.r.q0 -= l;
1181 else if(sign > 0)
1182 addr.r.q0 = addr.r.q1 += l;
1183 if(addr.r.q0<0 || addr.r.q1>addr.f->nc)
1184 editerror("address out of range");
1185 return addr;
1186 }
1187
1188 Address
lineaddr(long l,Address addr,int sign)1189 lineaddr(long l, Address addr, int sign)
1190 {
1191 int n;
1192 int c;
1193 File *f = addr.f;
1194 Address a;
1195 long p;
1196
1197 a.f = f;
1198 if(sign >= 0){
1199 if(l == 0){
1200 if(sign==0 || addr.r.q1==0){
1201 a.r.q0 = a.r.q1 = 0;
1202 return a;
1203 }
1204 a.r.q0 = addr.r.q1;
1205 p = addr.r.q1-1;
1206 }else{
1207 if(sign==0 || addr.r.q1==0){
1208 p = 0;
1209 n = 1;
1210 }else{
1211 p = addr.r.q1-1;
1212 n = textreadc(f->curtext, p++)=='\n';
1213 }
1214 while(n < l){
1215 if(p >= f->nc)
1216 editerror("address out of range");
1217 if(textreadc(f->curtext, p++) == '\n')
1218 n++;
1219 }
1220 a.r.q0 = p;
1221 }
1222 while(p < f->nc && textreadc(f->curtext, p++)!='\n')
1223 ;
1224 a.r.q1 = p;
1225 }else{
1226 p = addr.r.q0;
1227 if(l == 0)
1228 a.r.q1 = addr.r.q0;
1229 else{
1230 for(n = 0; n<l; ){ /* always runs once */
1231 if(p == 0){
1232 if(++n != l)
1233 editerror("address out of range");
1234 }else{
1235 c = textreadc(f->curtext, p-1);
1236 if(c != '\n' || ++n != l)
1237 p--;
1238 }
1239 }
1240 a.r.q1 = p;
1241 if(p > 0)
1242 p--;
1243 }
1244 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1245 p--;
1246 a.r.q0 = p;
1247 }
1248 return a;
1249 }
1250
1251 struct Filecheck
1252 {
1253 File *f;
1254 Rune *r;
1255 int nr;
1256 };
1257
1258 void
allfilecheck(Window * w,void * v)1259 allfilecheck(Window *w, void *v)
1260 {
1261 struct Filecheck *fp;
1262 File *f;
1263
1264 fp = v;
1265 f = w->body.file;
1266 if(w->body.file == fp->f)
1267 return;
1268 if(runeeq(fp->r, fp->nr, f->name, f->nname))
1269 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1270 }
1271
1272 Rune*
cmdname(File * f,String * str,int set)1273 cmdname(File *f, String *str, int set)
1274 {
1275 Rune *r, *s;
1276 int n;
1277 struct Filecheck fc;
1278 Runestr newname;
1279
1280 r = nil;
1281 n = str->n;
1282 s = str->r;
1283 if(n == 0){
1284 /* no name; use existing */
1285 if(f->nname == 0)
1286 return nil;
1287 r = runemalloc(f->nname+1);
1288 runemove(r, f->name, f->nname);
1289 return r;
1290 }
1291 s = skipbl(s, n, &n);
1292 if(n == 0)
1293 goto Return;
1294
1295 if(s[0] == '/'){
1296 r = runemalloc(n+1);
1297 runemove(r, s, n);
1298 }else{
1299 newname = dirname(f->curtext, runestrdup(s), n);
1300 n = newname.nr;
1301 r = runemalloc(n+1); /* NUL terminate */
1302 runemove(r, newname.r, n);
1303 free(newname.r);
1304 }
1305 fc.f = f;
1306 fc.r = r;
1307 fc.nr = n;
1308 allwindows(allfilecheck, &fc);
1309 if(f->nname == 0)
1310 set = TRUE;
1311
1312 Return:
1313 if(set && !runeeq(r, n, f->name, f->nname)){
1314 filemark(f);
1315 f->mod = TRUE;
1316 f->curtext->w->dirty = TRUE;
1317 winsetname(f->curtext->w, r, n);
1318 }
1319 return r;
1320 }
1321