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