xref: /plan9-contrib/sys/src/cmd/acme/ecmd.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
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
39 clearcollection(void)
40 {
41 	free(collection);
42 	collection = nil;
43 	ncollection = 0;
44 }
45 
46 void
47 resetxec(void)
48 {
49 	Glooping = nest = 0;
50 	clearcollection();
51 }
52 
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 }
60 
61 int
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*
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*
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
169 a_cmd(Text *t, Cmd *cp)
170 {
171 	return append(t->file, cp, addr.r.q1);
172 }
173 
174 int
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
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
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
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
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
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
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
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
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 = &empty;
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
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
384 i_cmd(Text *t, Cmd *cp)
385 {
386 	return append(t->file, cp, addr.r.q0);
387 }
388 
389 void
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
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
417 		error("move overlaps itself");
418 }
419 
420 int
421 m_cmd(Text *t, Cmd *cp)
422 {
423 	Address dot, addr2;
424 
425 	mkaddr(&dot, t->file);
426 	addr2 = cmdaddress(cp->mtaddr, dot, 0);
427 	if(cp->cmdc == 'm')
428 		move(t->file, addr2);
429 	else
430 		copy(t->file, addr2);
431 	return TRUE;
432 }
433 
434 int
435 p_cmd(Text *t, Cmd*)
436 {
437 	return pdisplay(t->file);
438 }
439 
440 int
441 s_cmd(Text *t, Cmd *cp)
442 {
443 	int i, j, k, c, m, n, nrp, didsub;
444 	long p1, op, delta;
445 	String *buf;
446 	Rangeset *rp;
447 	char *err;
448 	Rune *rbuf;
449 
450 	n = cp->num;
451 	op= -1;
452 	if(rxcompile(cp->re->r) == FALSE)
453 		editerror("bad regexp in s command");
454 	nrp = 0;
455 	rp = nil;
456 	delta = 0;
457 	didsub = FALSE;
458 	for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
459 		if(sel.r[0].q0 == sel.r[0].q1){	/* empty match? */
460 			if(sel.r[0].q0 == op){
461 				p1++;
462 				continue;
463 			}
464 			p1 = sel.r[0].q1+1;
465 		}else
466 			p1 = sel.r[0].q1;
467 		op = sel.r[0].q1;
468 		if(--n>0)
469 			continue;
470 		nrp++;
471 		rp = erealloc(rp, nrp*sizeof(Rangeset));
472 		rp[nrp-1] = sel;
473 	}
474 	rbuf = fbufalloc();
475 	buf = allocstring(0);
476 	for(m=0; m<nrp; m++){
477 		buf->n = 0;
478 		buf->r[0] = L'\0';
479 		sel = rp[m];
480 		for(i = 0; i<cp->text->n; i++)
481 			if((c = cp->text->r[i])=='\\' && i<cp->text->n-1){
482 				c = cp->text->r[++i];
483 				if('1'<=c && c<='9') {
484 					j = c-'0';
485 					if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
486 						err = "replacement string too long";
487 						goto Err;
488 					}
489 					bufread(t->file, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
490 					for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
491 						Straddc(buf, rbuf[k]);
492 				}else
493 				 	Straddc(buf, c);
494 			}else if(c!='&')
495 				Straddc(buf, c);
496 			else{
497 				if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
498 					err = "right hand side too long in substitution";
499 					goto Err;
500 				}
501 				bufread(t->file, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
502 				for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
503 					Straddc(buf, rbuf[k]);
504 			}
505 		elogreplace(t->file, sel.r[0].q0, sel.r[0].q1,  buf->r, buf->n);
506 		delta -= sel.r[0].q1-sel.r[0].q0;
507 		delta += buf->n;
508 		didsub = 1;
509 		if(!cp->flag)
510 			break;
511 	}
512 	free(rp);
513 	freestring(buf);
514 	fbuffree(rbuf);
515 	if(!didsub && nest==0)
516 		editerror("no substitution");
517 	t->q0 = addr.r.q0;
518 	t->q1 = addr.r.q1;
519 	return TRUE;
520 
521 Err:
522 	free(rp);
523 	freestring(buf);
524 	fbuffree(rbuf);
525 	editerror(err);
526 	return FALSE;
527 }
528 
529 int
530 u_cmd(Text *t, Cmd *cp)
531 {
532 	int n, oseq, flag;
533 
534 	n = cp->num;
535 	flag = TRUE;
536 	if(n < 0){
537 		n = -n;
538 		flag = FALSE;
539 	}
540 	oseq = -1;
541 	while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){
542 		oseq = t->file->seq;
543 		undo(t, nil, nil, flag, 0, nil, 0);
544 	}
545 	return TRUE;
546 }
547 
548 int
549 w_cmd(Text *t, Cmd *cp)
550 {
551 	Rune *r;
552 	File *f;
553 
554 	f = t->file;
555 	if(f->seq == seq)
556 		editerror("can't write file with pending modifications");
557 	r = cmdname(f, cp->text, FALSE);
558 	if(r == nil)
559 		editerror("no name specified for 'w' command");
560 	putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
561 	/* r is freed by putfile */
562 	return TRUE;
563 }
564 
565 int
566 x_cmd(Text *t, Cmd *cp)
567 {
568 	if(cp->re)
569 		looper(t->file, cp, cp->cmdc=='x');
570 	else
571 		linelooper(t->file, cp);
572 	return TRUE;
573 }
574 
575 int
576 X_cmd(Text*, Cmd *cp)
577 {
578 	filelooper(cp, cp->cmdc=='X');
579 	return TRUE;
580 }
581 
582 void
583 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
584 {
585 	Rune *r, *s;
586 	int n;
587 	Runestr dir;
588 	Window *w;
589 
590 	r = skipbl(cr, ncr, &n);
591 	if(n == 0)
592 		editerror("no command specified for %c", cmd);
593 	w = nil;
594 	if(state == Inserting){
595 		w = t->w;
596 		t->q0 = addr.r.q0;
597 		t->q1 = addr.r.q1;
598 		if(cmd == '<' || cmd=='|')
599 			elogdelete(t->file, t->q0, t->q1);
600 	}
601 	s = runemalloc(n+2);
602 	s[0] = cmd;
603 	runemove(s+1, r, n);
604 	n++;
605 	dir.r = nil;
606 	dir.nr = 0;
607 	if(t != nil)
608 		dir = dirname(t, nil, 0);
609 	if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
610 		free(dir.r);
611 		dir.r = nil;
612 		dir.nr = 0;
613 	}
614 	editing = state;
615 	if(t!=nil && t->w!=nil)
616 		incref(t->w);	/* run will decref */
617 	run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
618 	free(s);
619 	if(t!=nil && t->w!=nil)
620 		winunlock(t->w);
621 	qunlock(&row);
622 	recvul(cedit);
623 	qlock(&row);
624 	editing = Inactive;
625 	if(t!=nil && t->w!=nil)
626 		winlock(t->w, 'M');
627 }
628 
629 int
630 pipe_cmd(Text *t, Cmd *cp)
631 {
632 	runpipe(t, cp->cmdc, cp->text->r, cp->text->n, Inserting);
633 	return TRUE;
634 }
635 
636 long
637 nlcount(Text *t, long q0, long q1)
638 {
639 	long nl;
640 	Rune *buf;
641 	int i, nbuf;
642 
643 	buf = fbufalloc();
644 	nbuf = 0;
645 	i = nl = 0;
646 	while(q0 < q1){
647 		if(i == nbuf){
648 			nbuf = q1-q0;
649 			if(nbuf > RBUFSIZE)
650 				nbuf = RBUFSIZE;
651 			bufread(t->file, q0, buf, nbuf);
652 			i = 0;
653 		}
654 		if(buf[i++] == '\n')
655 			nl++;
656 		q0++;
657 	}
658 	fbuffree(buf);
659 	return nl;
660 }
661 
662 void
663 printposn(Text *t, int charsonly)
664 {
665 	long l1, l2;
666 
667 	if (t != nil && t->file != nil && t->file->name != nil)
668 		warning(nil, "%.*S:", t->file->nname, t->file->name);
669 	if(!charsonly){
670 		l1 = 1+nlcount(t, 0, addr.r.q0);
671 		l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
672 		/* check if addr ends with '\n' */
673 		if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
674 			--l2;
675 		warning(nil, "%lud", l1);
676 		if(l2 != l1)
677 			warning(nil, ",%lud", l2);
678 		warning(nil, "\n");
679 		return;
680 	}
681 	warning(nil, "#%d", addr.r.q0);
682 	if(addr.r.q1 != addr.r.q0)
683 		warning(nil, ",#%d", addr.r.q1);
684 	warning(nil, "\n");
685 }
686 
687 int
688 eq_cmd(Text *t, Cmd *cp)
689 {
690 	int charsonly;
691 
692 	switch(cp->text->n){
693 	case 0:
694 		charsonly = FALSE;
695 		break;
696 	case 1:
697 		if(cp->text->r[0] == '#'){
698 			charsonly = TRUE;
699 			break;
700 		}
701 	default:
702 		SET(charsonly);
703 		editerror("newline expected");
704 	}
705 	printposn(t, charsonly);
706 	return TRUE;
707 }
708 
709 int
710 nl_cmd(Text *t, Cmd *cp)
711 {
712 	Address a;
713 	File *f;
714 
715 	f = t->file;
716 	if(cp->addr == 0){
717 		/* First put it on newline boundaries */
718 		mkaddr(&a, f);
719 		addr = lineaddr(0, a, -1);
720 		a = lineaddr(0, a, 1);
721 		addr.r.q1 = a.r.q1;
722 		if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
723 			mkaddr(&a, f);
724 			addr = lineaddr(1, a, 1);
725 		}
726 	}
727 	textshow(t, addr.r.q0, addr.r.q1, 1);
728 	return TRUE;
729 }
730 
731 int
732 append(File *f, Cmd *cp, long p)
733 {
734 	if(cp->text->n > 0)
735 		eloginsert(f, p, cp->text->r, cp->text->n);
736 	f->curtext->q0 = p;
737 	f->curtext->q1 = p;
738 	return TRUE;
739 }
740 
741 int
742 pdisplay(File *f)
743 {
744 	long p1, p2;
745 	int np;
746 	Rune *buf;
747 
748 	p1 = addr.r.q0;
749 	p2 = addr.r.q1;
750 	if(p2 > f->nc)
751 		p2 = f->nc;
752 	buf = fbufalloc();
753 	while(p1 < p2){
754 		np = p2-p1;
755 		if(np>RBUFSIZE-1)
756 			np = RBUFSIZE-1;
757 		bufread(f, p1, buf, np);
758 		buf[np] = L'\0';
759 		warning(nil, "%S", buf);
760 		p1 += np;
761 	}
762 	fbuffree(buf);
763 	f->curtext->q0 = addr.r.q0;
764 	f->curtext->q1 = addr.r.q1;
765 	return TRUE;
766 }
767 
768 void
769 pfilename(File *f)
770 {
771 	int dirty;
772 	Window *w;
773 
774 	w = f->curtext->w;
775 	/* same check for dirty as in settag, but we know ncache==0 */
776 	dirty = !w->isdir && !w->isscratch && f->mod;
777 	warning(nil, "%c%c%c %.*S\n", " '"[dirty],
778 		'+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
779 }
780 
781 void
782 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
783 {
784 	long i;
785 
786 	for(i=0; i<nrp; i++){
787 		f->curtext->q0 = rp[i].q0;
788 		f->curtext->q1 = rp[i].q1;
789 		cmdexec(f->curtext, cp);
790 	}
791 }
792 
793 void
794 looper(File *f, Cmd *cp, int xy)
795 {
796 	long p, op, nrp;
797 	Range r, tr;
798 	Range *rp;
799 
800 	r = addr.r;
801 	op= xy? -1 : r.q0;
802 	nest++;
803 	if(rxcompile(cp->re->r) == FALSE)
804 		editerror("bad regexp in %c command", cp->cmdc);
805 	nrp = 0;
806 	rp = nil;
807 	for(p = r.q0; p<=r.q1; ){
808 		if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
809 			if(xy || op>r.q1)
810 				break;
811 			tr.q0 = op, tr.q1 = r.q1;
812 			p = r.q1+1;	/* exit next loop */
813 		}else{
814 			if(sel.r[0].q0==sel.r[0].q1){	/* empty match? */
815 				if(sel.r[0].q0==op){
816 					p++;
817 					continue;
818 				}
819 				p = sel.r[0].q1+1;
820 			}else
821 				p = sel.r[0].q1;
822 			if(xy)
823 				tr = sel.r[0];
824 			else
825 				tr.q0 = op, tr.q1 = sel.r[0].q0;
826 		}
827 		op = sel.r[0].q1;
828 		nrp++;
829 		rp = erealloc(rp, nrp*sizeof(Range));
830 		rp[nrp-1] = tr;
831 	}
832 	loopcmd(f, cp->cmd, rp, nrp);
833 	free(rp);
834 	--nest;
835 }
836 
837 void
838 linelooper(File *f, Cmd *cp)
839 {
840 	long nrp, p;
841 	Range r, linesel;
842 	Address a, a3;
843 	Range *rp;
844 
845 	nest++;
846 	nrp = 0;
847 	rp = nil;
848 	r = addr.r;
849 	a3.f = f;
850 	a3.r.q0 = a3.r.q1 = r.q0;
851 	a = lineaddr(0, a3, 1);
852 	linesel = a.r;
853 	for(p = r.q0; p<r.q1; p = a3.r.q1){
854 		a3.r.q0 = a3.r.q1;
855 		if(p!=r.q0 || linesel.q1==p){
856 			a = lineaddr(1, a3, 1);
857 			linesel = a.r;
858 		}
859 		if(linesel.q0 >= r.q1)
860 			break;
861 		if(linesel.q1 >= r.q1)
862 			linesel.q1 = r.q1;
863 		if(linesel.q1 > linesel.q0)
864 			if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
865 				a3.r = linesel;
866 				nrp++;
867 				rp = erealloc(rp, nrp*sizeof(Range));
868 				rp[nrp-1] = linesel;
869 				continue;
870 			}
871 		break;
872 	}
873 	loopcmd(f, cp->cmd, rp, nrp);
874 	free(rp);
875 	--nest;
876 }
877 
878 struct Looper
879 {
880 	Cmd *cp;
881 	int	XY;
882 	Window	**w;
883 	int	nw;
884 } loopstruct;	/* only one; X and Y can't nest */
885 
886 void
887 alllooper(Window *w, void *v)
888 {
889 	Text *t;
890 	struct Looper *lp;
891 	Cmd *cp;
892 
893 	lp = v;
894 	cp = lp->cp;
895 //	if(w->isscratch || w->isdir)
896 //		return;
897 	t = &w->body;
898 	/* only use this window if it's the current window for the file */
899 	if(t->file->curtext != t)
900 		return;
901 //	if(w->nopen[QWevent] > 0)
902 //		return;
903 	/* no auto-execute on files without names */
904 	if(cp->re==nil && t->file->nname==0)
905 		return;
906 	if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
907 		lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
908 		lp->w[lp->nw++] = w;
909 	}
910 }
911 
912 void
913 alllocker(Window *w, void *v)
914 {
915 	if(v)
916 		incref(w);
917 	else
918 		winclose(w);
919 }
920 
921 void
922 filelooper(Cmd *cp, int XY)
923 {
924 	int i;
925 
926 	if(Glooping++)
927 		editerror("can't nest %c command", "YX"[XY]);
928 	nest++;
929 
930 	loopstruct.cp = cp;
931 	loopstruct.XY = XY;
932 	if(loopstruct.w)	/* error'ed out last time */
933 		free(loopstruct.w);
934 	loopstruct.w = nil;
935 	loopstruct.nw = 0;
936 	allwindows(alllooper, &loopstruct);
937 	/*
938 	 * add a ref to all windows to keep safe windows accessed by X
939 	 * that would not otherwise have a ref to hold them up during
940 	 * the shenanigans.  note this with globalincref so that any
941 	 * newly created windows start with an extra reference.
942 	 */
943 	allwindows(alllocker, (void*)1);
944 	globalincref = 1;
945 	for(i=0; i<loopstruct.nw; i++)
946 		cmdexec(&loopstruct.w[i]->body, cp->cmd);
947 	allwindows(alllocker, (void*)0);
948 	globalincref = 0;
949 	free(loopstruct.w);
950 	loopstruct.w = nil;
951 
952 	--Glooping;
953 	--nest;
954 }
955 
956 void
957 nextmatch(File *f, String *r, long p, int sign)
958 {
959 	if(rxcompile(r->r) == FALSE)
960 		editerror("bad regexp in command address");
961 	if(sign >= 0){
962 		if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
963 			editerror("no match for regexp");
964 		if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
965 			if(++p>f->nc)
966 				p = 0;
967 			if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
968 				editerror("address");
969 		}
970 	}else{
971 		if(!rxbexecute(f->curtext, p, &sel))
972 			editerror("no match for regexp");
973 		if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
974 			if(--p<0)
975 				p = f->nc;
976 			if(!rxbexecute(f->curtext, p, &sel))
977 				editerror("address");
978 		}
979 	}
980 }
981 
982 File	*matchfile(String*);
983 Address	charaddr(long, Address, int);
984 Address	lineaddr(long, Address, int);
985 
986 Address
987 cmdaddress(Addr *ap, Address a, int sign)
988 {
989 	File *f = a.f;
990 	Address a1, a2;
991 
992 	do{
993 		switch(ap->type){
994 		case 'l':
995 		case '#':
996 			a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
997 			break;
998 
999 		case '.':
1000 			mkaddr(&a, f);
1001 			break;
1002 
1003 		case '$':
1004 			a.r.q0 = a.r.q1 = f->nc;
1005 			break;
1006 
1007 		case '\'':
1008 editerror("can't handle '");
1009 //			a.r = f->mark;
1010 			break;
1011 
1012 		case '?':
1013 			sign = -sign;
1014 			if(sign == 0)
1015 				sign = -1;
1016 			/* fall through */
1017 		case '/':
1018 			nextmatch(f, ap->re, sign>=0? a.r.q1 : a.r.q0, sign);
1019 			a.r = sel.r[0];
1020 			break;
1021 
1022 		case '"':
1023 			f = matchfile(ap->re);
1024 			mkaddr(&a, f);
1025 			break;
1026 
1027 		case '*':
1028 			a.r.q0 = 0, a.r.q1 = f->nc;
1029 			return a;
1030 
1031 		case ',':
1032 		case ';':
1033 			if(ap->left)
1034 				a1 = cmdaddress(ap->left, a, 0);
1035 			else
1036 				a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1037 			if(ap->type == ';'){
1038 				f = a1.f;
1039 				a = a1;
1040 				f->curtext->q0 = a1.r.q0;
1041 				f->curtext->q1 = a1.r.q1;
1042 			}
1043 			if(ap->next)
1044 				a2 = cmdaddress(ap->next, a, 0);
1045 			else
1046 				a2.f = a.f, a2.r.q0 = a2.r.q1 = f->nc;
1047 			if(a1.f != a2.f)
1048 				editerror("addresses in different files");
1049 			a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1050 			if(a.r.q1 < a.r.q0)
1051 				editerror("addresses out of order");
1052 			return a;
1053 
1054 		case '+':
1055 		case '-':
1056 			sign = 1;
1057 			if(ap->type == '-')
1058 				sign = -1;
1059 			if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1060 				a = lineaddr(1L, a, sign);
1061 			break;
1062 		default:
1063 			error("cmdaddress");
1064 			return a;
1065 		}
1066 	}while(ap = ap->next);	/* assign = */
1067 	return a;
1068 }
1069 
1070 struct Tofile{
1071 	File		*f;
1072 	String	*r;
1073 };
1074 
1075 void
1076 alltofile(Window *w, void *v)
1077 {
1078 	Text *t;
1079 	struct Tofile *tp;
1080 
1081 	tp = v;
1082 	if(tp->f != nil)
1083 		return;
1084 	if(w->isscratch || w->isdir)
1085 		return;
1086 	t = &w->body;
1087 	/* only use this window if it's the current window for the file */
1088 	if(t->file->curtext != t)
1089 		return;
1090 //	if(w->nopen[QWevent] > 0)
1091 //		return;
1092 	if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1093 		tp->f = t->file;
1094 }
1095 
1096 File*
1097 tofile(String *r)
1098 {
1099 	struct Tofile t;
1100 	String rr;
1101 
1102 	rr.r = skipbl(r->r, r->n, &rr.n);
1103 	t.f = nil;
1104 	t.r = &rr;
1105 	allwindows(alltofile, &t);
1106 	if(t.f == nil)
1107 		editerror("no such file\"%S\"", rr.r);
1108 	return t.f;
1109 }
1110 
1111 void
1112 allmatchfile(Window *w, void *v)
1113 {
1114 	struct Tofile *tp;
1115 	Text *t;
1116 
1117 	tp = v;
1118 	if(w->isscratch || w->isdir)
1119 		return;
1120 	t = &w->body;
1121 	/* only use this window if it's the current window for the file */
1122 	if(t->file->curtext != t)
1123 		return;
1124 //	if(w->nopen[QWevent] > 0)
1125 //		return;
1126 	if(filematch(w->body.file, tp->r)){
1127 		if(tp->f != nil)
1128 			editerror("too many files match \"%S\"", tp->r->r);
1129 		tp->f = w->body.file;
1130 	}
1131 }
1132 
1133 File*
1134 matchfile(String *r)
1135 {
1136 	struct Tofile tf;
1137 
1138 	tf.f = nil;
1139 	tf.r = r;
1140 	allwindows(allmatchfile, &tf);
1141 
1142 	if(tf.f == nil)
1143 		editerror("no file matches \"%S\"", r->r);
1144 	return tf.f;
1145 }
1146 
1147 int
1148 filematch(File *f, String *r)
1149 {
1150 	char *buf;
1151 	Rune *rbuf;
1152 	Window *w;
1153 	int match, i, dirty;
1154 	Rangeset s;
1155 
1156 	/* compile expr first so if we get an error, we haven't allocated anything */
1157 	if(rxcompile(r->r) == FALSE)
1158 		editerror("bad regexp in file match");
1159 	buf = fbufalloc();
1160 	w = f->curtext->w;
1161 	/* same check for dirty as in settag, but we know ncache==0 */
1162 	dirty = !w->isdir && !w->isscratch && f->mod;
1163 	snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1164 		'+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1165 	rbuf = bytetorune(buf, &i);
1166 	fbuffree(buf);
1167 	match = rxexecute(nil, rbuf, 0, i, &s);
1168 	free(rbuf);
1169 	return match;
1170 }
1171 
1172 Address
1173 charaddr(long l, Address addr, int sign)
1174 {
1175 	if(sign == 0)
1176 		addr.r.q0 = addr.r.q1 = l;
1177 	else if(sign < 0)
1178 		addr.r.q1 = addr.r.q0 -= l;
1179 	else if(sign > 0)
1180 		addr.r.q0 = addr.r.q1 += l;
1181 	if(addr.r.q0<0 || addr.r.q1>addr.f->nc)
1182 		editerror("address out of range");
1183 	return addr;
1184 }
1185 
1186 Address
1187 lineaddr(long l, Address addr, int sign)
1188 {
1189 	int n;
1190 	int c;
1191 	File *f = addr.f;
1192 	Address a;
1193 	long p;
1194 
1195 	a.f = f;
1196 	if(sign >= 0){
1197 		if(l == 0){
1198 			if(sign==0 || addr.r.q1==0){
1199 				a.r.q0 = a.r.q1 = 0;
1200 				return a;
1201 			}
1202 			a.r.q0 = addr.r.q1;
1203 			p = addr.r.q1-1;
1204 		}else{
1205 			if(sign==0 || addr.r.q1==0){
1206 				p = 0;
1207 				n = 1;
1208 			}else{
1209 				p = addr.r.q1-1;
1210 				n = textreadc(f->curtext, p++)=='\n';
1211 			}
1212 			while(n < l){
1213 				if(p >= f->nc)
1214 					editerror("address out of range");
1215 				if(textreadc(f->curtext, p++) == '\n')
1216 					n++;
1217 			}
1218 			a.r.q0 = p;
1219 		}
1220 		while(p < f->nc && textreadc(f->curtext, p++)!='\n')
1221 			;
1222 		a.r.q1 = p;
1223 	}else{
1224 		p = addr.r.q0;
1225 		if(l == 0)
1226 			a.r.q1 = addr.r.q0;
1227 		else{
1228 			for(n = 0; n<l; ){	/* always runs once */
1229 				if(p == 0){
1230 					if(++n != l)
1231 						editerror("address out of range");
1232 				}else{
1233 					c = textreadc(f->curtext, p-1);
1234 					if(c != '\n' || ++n != l)
1235 						p--;
1236 				}
1237 			}
1238 			a.r.q1 = p;
1239 			if(p > 0)
1240 				p--;
1241 		}
1242 		while(p > 0 && textreadc(f->curtext, p-1)!='\n')	/* lines start after a newline */
1243 			p--;
1244 		a.r.q0 = p;
1245 	}
1246 	return a;
1247 }
1248 
1249 struct Filecheck
1250 {
1251 	File	*f;
1252 	Rune	*r;
1253 	int nr;
1254 };
1255 
1256 void
1257 allfilecheck(Window *w, void *v)
1258 {
1259 	struct Filecheck *fp;
1260 	File *f;
1261 
1262 	fp = v;
1263 	f = w->body.file;
1264 	if(w->body.file == fp->f)
1265 		return;
1266 	if(runeeq(fp->r, fp->nr, f->name, f->nname))
1267 		warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1268 }
1269 
1270 Rune*
1271 cmdname(File *f, String *str, int set)
1272 {
1273 	Rune *r, *s;
1274 	int n;
1275 	struct Filecheck fc;
1276 	Runestr newname;
1277 
1278 	r = nil;
1279 	n = str->n;
1280 	s = str->r;
1281 	if(n == 0){
1282 		/* no name; use existing */
1283 		if(f->nname == 0)
1284 			return nil;
1285 		r = runemalloc(f->nname+1);
1286 		runemove(r, f->name, f->nname);
1287 		return r;
1288 	}
1289 	s = skipbl(s, n, &n);
1290 	if(n == 0)
1291 		goto Return;
1292 
1293 	if(s[0] == '/'){
1294 		r = runemalloc(n+1);
1295 		runemove(r, s, n);
1296 	}else{
1297 		newname = dirname(f->curtext, runestrdup(s), n);
1298 		n = newname.nr;
1299 		r = runemalloc(n+1);	/* NUL terminate */
1300 		runemove(r, newname.r, n);
1301 		free(newname.r);
1302 	}
1303 	fc.f = f;
1304 	fc.r = r;
1305 	fc.nr = n;
1306 	allwindows(allfilecheck, &fc);
1307 	if(f->nname == 0)
1308 		set = TRUE;
1309 
1310     Return:
1311 	if(set && !runeeq(r, n, f->name, f->nname)){
1312 		filemark(f);
1313 		f->mod = TRUE;
1314 		f->curtext->w->dirty = TRUE;
1315 		winsetname(f->curtext->w, r, n);
1316 	}
1317 	return r;
1318 }
1319