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