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