plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

ecmd.c (26091B)


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