master ohmu/acme2k / src / cmd / acme / ecmd.c
ohmu  ·  2026-09-21
   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
  16int	Glooping;
  17int	nest;
  18char	Enoname[] = "no file name given";
  19
  20Address	addr;
  21File	*menu;
  22Rangeset	sel;
  23extern	Text*	curtext;
  24Rune	*collection;
  25int	ncollection;
  26
  27int	append(File*, Cmd*, long);
  28int	pdisplay(File*);
  29void	pfilename(File*);
  30void	looper(File*, Cmd*, int);
  31void	filelooper(Cmd*, int);
  32void	linelooper(File*, Cmd*);
  33Address	lineaddr(long, Address, int);
  34int	filematch(File*, String*);
  35File	*tofile(String*);
  36Rune*	cmdname(File *f, String *s, int);
  37void	runpipe(Text*, int, Rune*, int, int);
  38
  39void
  40clearcollection(void)
  41{
  42	free(collection);
  43	collection = nil;
  44	ncollection = 0;
  45}
  46
  47void
  48resetxec(void)
  49{
  50	Glooping = nest = 0;
  51	clearcollection();
  52}
  53
  54void
  55mkaddr(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
  62int
  63cmdexec(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
 131char*
 132edittext(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 */
 155Rune*
 156filelist(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
 169int
 170a_cmd(Text *t, Cmd *cp)
 171{
 172	return append(t->file, cp, addr.r.q1);
 173}
 174
 175int
 176b_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
 188int
 189B_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
 213int
 214c_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
 222int
 223d_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
 233void
 234D1(Text *t)
 235{
 236	if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
 237		colclose(t->col, t->w, TRUE);
 238}
 239
 240int
 241D_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
 289static int
 290readloader(void *v, uint q0, Rune *r, int nr)
 291{
 292	if(nr > 0)
 293		eloginsert(v, q0, r, nr);
 294	return 0;
 295}
 296
 297int
 298e_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
 350static Rune Lempty[] = { 0 };
 351int
 352f_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
 370int
 371g_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
 387int
 388i_cmd(Text *t, Cmd *cp)
 389{
 390	return append(t->file, cp, addr.r.q0);
 391}
 392
 393void
 394copy(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
 411void
 412move(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
 426int
 427m_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
 440int
 441p_cmd(Text *t, Cmd *cp)
 442{
 443	USED(cp);
 444	return pdisplay(t->file);
 445}
 446
 447int
 448s_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
 528Err:
 529	free(rp);
 530	freestring(buf);
 531	fbuffree(rbuf);
 532	editerror(err);
 533	return FALSE;
 534}
 535
 536int
 537u_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
 555int
 556w_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
 572int
 573x_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
 582int
 583X_cmd(Text *t, Cmd *cp)
 584{
 585	USED(t);
 586
 587	filelooper(cp, cp->cmdc=='X');
 588	return TRUE;
 589}
 590
 591void
 592runpipe(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
 656int
 657pipe_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
 663long
 664nlcount(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
 694enum {
 695	PosnLine = 0,
 696	PosnChars = 1,
 697	PosnLineChars = 2,
 698};
 699
 700void
 701printposn(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
 742int
 743eq_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
 768int
 769nl_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
 790int
 791append(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
 800int
 801pdisplay(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
 827void
 828pfilename(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
 840void
 841loopcmd(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
 852void
 853looper(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
 896void
 897linelooper(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
 937struct 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
 945void
 946alllooper(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
 971void
 972alllocker(Window *w, void *v)
 973{
 974	if(v)
 975		incref(&w->ref);
 976	else
 977		winclose(w);
 978}
 979
 980void
 981filelooper(Cmd *cp, int XY)
 982{
 983	int i;
 984
 985	if(Glooping++)
 986		editerror("can't nest %c command", "YX"[XY]);
 987	nest++;
 988
 989	loopstruct.cp = cp;
 990	loopstruct.XY = XY;
 991	if(loopstruct.w)	/* error'ed out last time */
 992		free(loopstruct.w);
 993	loopstruct.w = nil;
 994	loopstruct.nw = 0;
 995	allwindows(alllooper, &loopstruct);
 996	/*
 997	 * add a ref to all windows to keep safe windows accessed by X
 998	 * that would not otherwise have a ref to hold them up during
 999	 * the shenanigans.  note this with globalincref so that any
1000	 * newly created windows start with an extra reference.
1001	 */
1002	allwindows(alllocker, (void*)1);
1003	globalincref = 1;
1004	for(i=0; i<loopstruct.nw; i++)
1005		cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
1006	allwindows(alllocker, (void*)0);
1007	globalincref = 0;
1008	free(loopstruct.w);
1009	loopstruct.w = nil;
1010
1011	--Glooping;
1012	--nest;
1013}
1014
1015void
1016nextmatch(File *f, String *r, long p, int sign)
1017{
1018	if(rxcompile(r->r) == FALSE)
1019		editerror("bad regexp in command address");
1020	if(sign >= 0){
1021		if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1022			editerror("no match for regexp");
1023		if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
1024			if(++p>f->b.nc)
1025				p = 0;
1026			if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1027				editerror("address");
1028		}
1029	}else{
1030		if(!rxbexecute(f->curtext, p, &sel))
1031			editerror("no match for regexp");
1032		if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
1033			if(--p<0)
1034				p = f->b.nc;
1035			if(!rxbexecute(f->curtext, p, &sel))
1036				editerror("address");
1037		}
1038	}
1039}
1040
1041File	*matchfile(String*);
1042Address	charaddr(long, Address, int);
1043Address	lineaddr(long, Address, int);
1044
1045Address
1046cmdaddress(Addr *ap, Address a, int sign)
1047{
1048	File *f = a.f;
1049	Address a1, a2;
1050
1051	do{
1052		switch(ap->type){
1053		case 'l':
1054		case '#':
1055			a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1056			break;
1057
1058		case '.':
1059			mkaddr(&a, f);
1060			break;
1061
1062		case '$':
1063			a.r.q0 = a.r.q1 = f->b.nc;
1064			break;
1065
1066		case '\'':
1067editerror("can't handle '");
1068/*			a.r = f->mark; */
1069			break;
1070
1071		case '?':
1072			sign = -sign;
1073			if(sign == 0)
1074				sign = -1;
1075			/* fall through */
1076		case '/':
1077			nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
1078			a.r = sel.r[0];
1079			break;
1080
1081		case '"':
1082			f = matchfile(ap->u.re);
1083			mkaddr(&a, f);
1084			break;
1085
1086		case '*':
1087			a.r.q0 = 0, a.r.q1 = f->b.nc;
1088			return a;
1089
1090		case ',':
1091		case ';':
1092			if(ap->u.left)
1093				a1 = cmdaddress(ap->u.left, a, 0);
1094			else
1095				a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1096			if(ap->type == ';'){
1097				f = a1.f;
1098				a = a1;
1099				f->curtext->q0 = a1.r.q0;
1100				f->curtext->q1 = a1.r.q1;
1101			}
1102			if(ap->next)
1103				a2 = cmdaddress(ap->next, a, 0);
1104			else
1105				a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
1106			if(a1.f != a2.f)
1107				editerror("addresses in different files");
1108			a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1109			if(a.r.q1 < a.r.q0)
1110				editerror("addresses out of order");
1111			return a;
1112
1113		case '+':
1114		case '-':
1115			sign = 1;
1116			if(ap->type == '-')
1117				sign = -1;
1118			if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1119				a = lineaddr(1L, a, sign);
1120			break;
1121		default:
1122			error("cmdaddress");
1123			return a;
1124		}
1125	}while(ap = ap->next);	/* assign = */
1126	return a;
1127}
1128
1129struct Tofile{
1130	File		*f;
1131	String	*r;
1132};
1133
1134void
1135alltofile(Window *w, void *v)
1136{
1137	Text *t;
1138	struct Tofile *tp;
1139
1140	tp = v;
1141	if(tp->f != nil)
1142		return;
1143	if(w->isscratch || w->isdir)
1144		return;
1145	t = &w->body;
1146	/* only use this window if it's the current window for the file */
1147	if(t->file->curtext != t)
1148		return;
1149/*	if(w->nopen[QWevent] > 0) */
1150/*		return; */
1151	if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1152		tp->f = t->file;
1153}
1154
1155File*
1156tofile(String *r)
1157{
1158	struct Tofile t;
1159	String rr;
1160
1161	rr.r = skipbl(r->r, r->n, &rr.n);
1162	t.f = nil;
1163	t.r = &rr;
1164	allwindows(alltofile, &t);
1165	if(t.f == nil)
1166		editerror("no such file\"%S\"", rr.r);
1167	return t.f;
1168}
1169
1170void
1171allmatchfile(Window *w, void *v)
1172{
1173	struct Tofile *tp;
1174	Text *t;
1175
1176	tp = v;
1177	if(w->isscratch || w->isdir)
1178		return;
1179	t = &w->body;
1180	/* only use this window if it's the current window for the file */
1181	if(t->file->curtext != t)
1182		return;
1183/*	if(w->nopen[QWevent] > 0) */
1184/*		return; */
1185	if(filematch(w->body.file, tp->r)){
1186		if(tp->f != nil)
1187			editerror("too many files match \"%S\"", tp->r->r);
1188		tp->f = w->body.file;
1189	}
1190}
1191
1192File*
1193matchfile(String *r)
1194{
1195	struct Tofile tf;
1196
1197	tf.f = nil;
1198	tf.r = r;
1199	allwindows(allmatchfile, &tf);
1200
1201	if(tf.f == nil)
1202		editerror("no file matches \"%S\"", r->r);
1203	return tf.f;
1204}
1205
1206int
1207filematch(File *f, String *r)
1208{
1209	char *buf;
1210	Rune *rbuf;
1211	Window *w;
1212	int match, i, dirty;
1213	Rangeset s;
1214
1215	/* compile expr first so if we get an error, we haven't allocated anything */
1216	if(rxcompile(r->r) == FALSE)
1217		editerror("bad regexp in file match");
1218	buf = fbufalloc();
1219	w = f->curtext->w;
1220	/* same check for dirty as in settag, but we know ncache==0 */
1221	dirty = !w->isdir && !w->isscratch && f->mod;
1222	snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1223		'+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1224	rbuf = bytetorune(buf, &i);
1225	fbuffree(buf);
1226	match = rxexecute(nil, rbuf, 0, i, &s);
1227	free(rbuf);
1228	return match;
1229}
1230
1231Address
1232charaddr(long l, Address addr, int sign)
1233{
1234	if(sign == 0)
1235		addr.r.q0 = addr.r.q1 = l;
1236	else if(sign < 0)
1237		addr.r.q1 = addr.r.q0 -= l;
1238	else if(sign > 0)
1239		addr.r.q0 = addr.r.q1 += l;
1240	if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
1241		editerror("address out of range");
1242	return addr;
1243}
1244
1245Address
1246lineaddr(long l, Address addr, int sign)
1247{
1248	int n;
1249	int c;
1250	File *f = addr.f;
1251	Address a;
1252	long p;
1253
1254	a.f = f;
1255	if(sign >= 0){
1256		if(l == 0){
1257			if(sign==0 || addr.r.q1==0){
1258				a.r.q0 = a.r.q1 = 0;
1259				return a;
1260			}
1261			a.r.q0 = addr.r.q1;
1262			p = addr.r.q1-1;
1263		}else{
1264			if(sign==0 || addr.r.q1==0){
1265				p = 0;
1266				n = 1;
1267			}else{
1268				p = addr.r.q1-1;
1269				n = textreadc(f->curtext, p++)=='\n';
1270			}
1271			while(n < l){
1272				if(p >= f->b.nc)
1273					editerror("address out of range");
1274				if(textreadc(f->curtext, p++) == '\n')
1275					n++;
1276			}
1277			a.r.q0 = p;
1278		}
1279		while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1280			;
1281		a.r.q1 = p;
1282	}else{
1283		p = addr.r.q0;
1284		if(l == 0)
1285			a.r.q1 = addr.r.q0;
1286		else{
1287			for(n = 0; n<l; ){	/* always runs once */
1288				if(p == 0){
1289					if(++n != l)
1290						editerror("address out of range");
1291				}else{
1292					c = textreadc(f->curtext, p-1);
1293					if(c != '\n' || ++n != l)
1294						p--;
1295				}
1296			}
1297			a.r.q1 = p;
1298			if(p > 0)
1299				p--;
1300		}
1301		while(p > 0 && textreadc(f->curtext, p-1)!='\n')	/* lines start after a newline */
1302			p--;
1303		a.r.q0 = p;
1304	}
1305	return a;
1306}
1307
1308struct Filecheck
1309{
1310	File	*f;
1311	Rune	*r;
1312	int nr;
1313};
1314
1315void
1316allfilecheck(Window *w, void *v)
1317{
1318	struct Filecheck *fp;
1319	File *f;
1320
1321	fp = v;
1322	f = w->body.file;
1323	if(w->body.file == fp->f)
1324		return;
1325	if(runeeq(fp->r, fp->nr, f->name, f->nname))
1326		warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1327}
1328
1329Rune*
1330cmdname(File *f, String *str, int set)
1331{
1332	Rune *r, *s;
1333	int n;
1334	struct Filecheck fc;
1335	Runestr newname;
1336
1337	r = nil;
1338	n = str->n;
1339	s = str->r;
1340	if(n == 0){
1341		/* no name; use existing */
1342		if(f->nname == 0)
1343			return nil;
1344		r = runemalloc(f->nname+1);
1345		runemove(r, f->name, f->nname);
1346		return r;
1347	}
1348	s = skipbl(s, n, &n);
1349	if(n == 0)
1350		goto Return;
1351
1352	if(s[0] == '/'){
1353		r = runemalloc(n+1);
1354		runemove(r, s, n);
1355	}else{
1356		newname = dirname(f->curtext, runestrdup(s), n);
1357		n = newname.nr;
1358		r = runemalloc(n+1);	/* NUL terminate */
1359		runemove(r, newname.r, n);
1360		free(newname.r);
1361	}
1362	fc.f = f;
1363	fc.r = r;
1364	fc.nr = n;
1365	allwindows(allfilecheck, &fc);
1366	if(f->nname == 0)
1367		set = TRUE;
1368
1369    Return:
1370	if(set && !runeeq(r, n, f->name, f->nname)){
1371		filemark(f);
1372		f->mod = TRUE;
1373		f->curtext->w->dirty = TRUE;
1374		winsetname(f->curtext->w, r, n);
1375	}
1376	return r;
1377}