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