plan9port

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

look.c (19187B)


      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 <regexp.h>
     11 #include <9pclient.h>
     12 #include <plumb.h>
     13 #include <libsec.h>
     14 #include "dat.h"
     15 #include "fns.h"
     16 
     17 CFid *plumbsendfid;
     18 CFid *plumbeditfid;
     19 
     20 Window*	openfile(Text*, Expand*);
     21 
     22 int	nuntitled;
     23 
     24 void
     25 plumbthread(void *v)
     26 {
     27 	CFid *fid;
     28 	Plumbmsg *m;
     29 	Timer *t;
     30 
     31 	USED(v);
     32 	threadsetname("plumbproc");
     33 
     34 	/*
     35 	 * Loop so that if plumber is restarted, acme need not be.
     36 	 */
     37 	for(;;){
     38 		/*
     39 		 * Connect to plumber.
     40 		 */
     41 		plumbunmount();
     42 		while((fid = plumbopenfid("edit", OREAD|OCEXEC)) == nil){
     43 			t = timerstart(2000);
     44 			recv(t->c, nil);
     45 			timerstop(t);
     46 		}
     47 		plumbeditfid = fid;
     48 		plumbsendfid = plumbopenfid("send", OWRITE|OCEXEC);
     49 
     50 		/*
     51 		 * Relay messages.
     52 		 */
     53 		for(;;){
     54 			m = plumbrecvfid(plumbeditfid);
     55 			if(m == nil)
     56 				break;
     57 			sendp(cplumb, m);
     58 		}
     59 
     60 		/*
     61 		 * Lost connection.
     62 		 */
     63 		fid = plumbsendfid;
     64 		plumbsendfid = nil;
     65 		fsclose(fid);
     66 
     67 		fid = plumbeditfid;
     68 		plumbeditfid = nil;
     69 		fsclose(fid);
     70 	}
     71 }
     72 
     73 void
     74 startplumbing(void)
     75 {
     76 	cplumb = chancreate(sizeof(Plumbmsg*), 0);
     77 	chansetname(cplumb, "cplumb");
     78 	threadcreate(plumbthread, nil, STACK);
     79 }
     80 
     81 
     82 void
     83 look3(Text *t, uint q0, uint q1, int external, int reverse)
     84 {
     85 	int n, c, f, expanded;
     86 	Text *ct;
     87 	Expand e;
     88 	Rune *r;
     89 	uint p;
     90 	Plumbmsg *m;
     91 	Runestr dir;
     92 	char buf[32];
     93 
     94 	ct = seltext;
     95 	if(ct == nil)
     96 		seltext = t;
     97 	expanded = expand(t, q0, q1, &e, reverse);
     98 	if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
     99 		/* send alphanumeric expansion to external client */
    100 		if(expanded == FALSE)
    101 			return;
    102 		f = 0;
    103 		if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil))
    104 			f = 1;		/* acme can do it without loading a file */
    105 		if(q0!=e.q0 || q1!=e.q1)
    106 			f |= 2;	/* second (post-expand) message follows */
    107 		if(e.nname)
    108 			f |= 4;	/* it's a file name */
    109 		c = 'l';
    110 		if(t->what == Body)
    111 			c = 'L';
    112 		if(reverse)
    113 			c += 'R' - 'L';
    114 		n = q1-q0;
    115 		if(n <= EVENTSIZE){
    116 			r = runemalloc(n);
    117 			bufread(&t->file->b, q0, r, n);
    118 			winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r);
    119 			free(r);
    120 		}else
    121 			winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n);
    122 		if(q0==e.q0 && q1==e.q1)
    123 			return;
    124 		if(e.nname){
    125 			n = e.nname;
    126 			if(e.a1 > e.a0)
    127 				n += 1+(e.a1-e.a0);
    128 			r = runemalloc(n);
    129 			runemove(r, e.name, e.nname);
    130 			if(e.a1 > e.a0){
    131 				r[e.nname] = ':';
    132 				bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0);
    133 			}
    134 		}else{
    135 			n = e.q1 - e.q0;
    136 			r = runemalloc(n);
    137 			bufread(&t->file->b, e.q0, r, n);
    138 		}
    139 		f &= ~2;
    140 		if(n <= EVENTSIZE)
    141 			winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r);
    142 		else
    143 			winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n);
    144 		free(r);
    145 		goto Return;
    146 	}
    147 	if(plumbsendfid != nil){
    148 		/* send whitespace-delimited word to plumber */
    149 		m = emalloc(sizeof(Plumbmsg));
    150 		m->src = estrdup("acme");
    151 		m->dst = nil;
    152 		dir = dirname(t, nil, 0);
    153 		if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */
    154 			free(dir.r);
    155 			dir.r = nil;
    156 			dir.nr = 0;
    157 		}
    158 		if(dir.nr == 0)
    159 			m->wdir = estrdup(wdir);
    160 		else
    161 			m->wdir = runetobyte(dir.r, dir.nr);
    162 		free(dir.r);
    163 		m->type = estrdup("text");
    164 		m->attr = nil;
    165 		buf[0] = '\0';
    166 		if(q1 == q0){
    167 			if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
    168 				q0 = t->q0;
    169 				q1 = t->q1;
    170 			}else{
    171 				p = q0;
    172 				while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n')
    173 					q0--;
    174 				while(q1<t->file->b.nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n')
    175 					q1++;
    176 				if(q1 == q0){
    177 					plumbfree(m);
    178 					goto Return;
    179 				}
    180 				sprint(buf, "click=%d", p-q0);
    181 				m->attr = plumbunpackattr(buf);
    182 			}
    183 		}
    184 		r = runemalloc(q1-q0);
    185 		bufread(&t->file->b, q0, r, q1-q0);
    186 		m->data = runetobyte(r, q1-q0);
    187 		m->ndata = strlen(m->data);
    188 		free(r);
    189 		if(m->ndata<messagesize-1024 && plumbsendtofid(plumbsendfid, m) >= 0){
    190 			plumbfree(m);
    191 			goto Return;
    192 		}
    193 		plumbfree(m);
    194 		/* plumber failed to match; fall through */
    195 	}
    196 
    197 	/* interpret alphanumeric string ourselves */
    198 	if(expanded == FALSE)
    199 		return;
    200 	if(e.name || e.u.at)
    201 		openfile(t, &e);
    202 	else{
    203 		if(t->w == nil)
    204 			return;
    205 		ct = &t->w->body;
    206 		if(t->w != ct->w)
    207 			winlock(ct->w, 'M');
    208 		if(t == ct) {
    209 			uint q;
    210 			q = e.q1;
    211 			if(reverse)
    212 				q = e.q0;
    213 			textsetselect(ct, q, q);
    214 		}
    215 		n = e.q1 - e.q0;
    216 		r = runemalloc(n);
    217 		bufread(&t->file->b, e.q0, r, n);
    218 		if(search(ct, r, n, reverse) && e.jump)
    219 			moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4)));
    220 		if(t->w != ct->w)
    221 			winunlock(ct->w);
    222 		free(r);
    223 	}
    224 
    225    Return:
    226 	free(e.name);
    227 	free(e.bname);
    228 }
    229 
    230 int
    231 plumbgetc(void *a, uint n)
    232 {
    233 	Rune *r;
    234 
    235 	r = a;
    236 	if(n>runestrlen(r))
    237 		return 0;
    238 	return r[n];
    239 }
    240 
    241 void
    242 plumblook(Plumbmsg *m)
    243 {
    244 	Expand e;
    245 	char *addr;
    246 
    247 	if(m->ndata >= BUFSIZE){
    248 		warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
    249 		return;
    250 	}
    251 	memset(&e, 0, sizeof e);
    252 	e.q0 = 0;
    253 	e.q1 = 0;
    254 	if(m->data[0] == '\0')
    255 		return;
    256 	e.u.ar = nil;
    257 	e.bname = m->data;
    258 	e.name = bytetorune(e.bname, &e.nname);
    259 	e.jump = TRUE;
    260 	e.a0 = 0;
    261 	e.a1 = 0;
    262 	addr = plumblookup(m->attr, "addr");
    263 	if(addr != nil){
    264 		e.u.ar = bytetorune(addr, &e.a1);
    265 		e.agetc = plumbgetc;
    266 	}
    267 	drawtopwindow();
    268 	openfile(nil, &e);
    269 	free(e.name);
    270 	free(e.u.at);
    271 }
    272 
    273 void
    274 plumbshow(Plumbmsg *m)
    275 {
    276 	Window *w;
    277 	Rune rb[256], *r;
    278 	int nb, nr;
    279 	Runestr rs;
    280 	char *name, *p, namebuf[16];
    281 
    282 	drawtopwindow();
    283 	w = makenewwindow(nil);
    284 	name = plumblookup(m->attr, "filename");
    285 	if(name == nil){
    286 		name = namebuf;
    287 		nuntitled++;
    288 		snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled);
    289 	}
    290 	p = nil;
    291 	if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){
    292 		nb = strlen(m->wdir) + 1 + strlen(name) + 1;
    293 		p = emalloc(nb);
    294 		snprint(p, nb, "%s/%s", m->wdir, name);
    295 		name = p;
    296 	}
    297 	cvttorunes(name, strlen(name), rb, &nb, &nr, nil);
    298 	free(p);
    299 	rs = cleanrname(runestr(rb, nr));
    300 	winsetname(w, rs.r, rs.nr);
    301 	r = runemalloc(m->ndata);
    302 	cvttorunes(m->data, m->ndata, r, &nb, &nr, nil);
    303 	textinsert(&w->body, 0, r, nr, TRUE);
    304 	free(r);
    305 	w->body.file->mod = FALSE;
    306 	w->dirty = FALSE;
    307 	winsettag(w);
    308 	textscrdraw(&w->body);
    309 	textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
    310 	xfidlog(w, "new");
    311 }
    312 
    313 int
    314 search(Text *ct, Rune *r, uint n, int reverse)
    315 {
    316 	uint nb, maxn;
    317 	int around;
    318 	Rune *s, *b;
    319 
    320 	if(n==0 || n>ct->file->b.nc)
    321 		return FALSE;
    322 	if(2*n > RBUFSIZE){
    323 		warning(nil, "string too long\n");
    324 		return FALSE;
    325 	}
    326 	maxn = max(2*n, RBUFSIZE);
    327 	s = fbufalloc();
    328 	b = s;
    329 	nb = 0;
    330 	b[nb] = 0;
    331 	around = 0;
    332 	if(reverse){
    333 		uint q1;
    334 		q1 = ct->q0; // q1 is (past) end of text being searched.
    335 		for(;;){
    336 			if(q1 <= 0){
    337 				q1 = ct->file->b.nc;
    338 				around = 1;
    339 				nb = 0;
    340 				b[nb] = 0;
    341 			}
    342 			if(nb > 0){
    343 				Rune *c;
    344 				for(c=b+nb; c>b; c--)
    345 					if(c[-1] == r[n-1])
    346 						break;
    347 				if(c == b) {
    348 					q1 -= nb;
    349 					nb = 0;
    350 					b[nb] = 0;
    351 					if(around && q1 <= 0)
    352 						break;
    353 					continue;
    354 				}
    355 				q1 -= nb - (c - b);
    356 				nb = c - b;
    357 			}
    358 			/* reload if buffer covers neither string nor beginning of file */
    359 			if(nb<n && nb!=q1){
    360 				nb = q1;
    361 				if(nb >= maxn)
    362 					nb = maxn-1;
    363 				bufread(&ct->file->b, q1-nb, s, nb);
    364 				b = s;
    365 				b[nb] = '\0';
    366 			}
    367 			if(runeeq(b+nb-n, n, r, n)==TRUE){
    368 				if(ct->w){
    369 					textshow(ct, q1-n, q1, 1);
    370 					winsettag(ct->w);
    371 				}else{
    372 					ct->q0 = q1-n;
    373 					ct->q1 = q1;
    374 				}
    375 				seltext = ct;
    376 				fbuffree(s);
    377 				return TRUE;
    378 			}
    379 			q1--;
    380 			nb--;
    381 			if(around && q1 <= 0)
    382 				break;
    383 		}
    384 	}else{
    385 		uint q;
    386 		q = ct->q1;
    387 		for(;;){
    388 			if(q >= ct->file->b.nc){
    389 				q = 0;
    390 				around = 1;
    391 				nb = 0;
    392 				b[nb] = 0;
    393 			}
    394 			if(nb > 0){
    395 				Rune *c;
    396 				c = runestrchr(b, r[0]);
    397 				if(c == nil){
    398 					q += nb;
    399 					nb = 0;
    400 					b[nb] = 0;
    401 					if(around && q>=ct->q1)
    402 						break;
    403 					continue;
    404 				}
    405 				q += (c-b);
    406 				nb -= (c-b);
    407 				b = c;
    408 			}
    409 			/* reload if buffer covers neither string nor rest of file */
    410 			if(nb<n && nb!=ct->file->b.nc-q){
    411 				nb = ct->file->b.nc-q;
    412 				if(nb >= maxn)
    413 					nb = maxn-1;
    414 				bufread(&ct->file->b, q, s, nb);
    415 				b = s;
    416 				b[nb] = '\0';
    417 			}
    418 			/* this runeeq is fishy but the null at b[nb] makes it safe */
    419 			if(runeeq(b, n, r, n)==TRUE){
    420 				if(ct->w){
    421 					textshow(ct, q, q+n, 1);
    422 					winsettag(ct->w);
    423 				}else{
    424 					ct->q0 = q;
    425 					ct->q1 = q+n;
    426 				}
    427 				seltext = ct;
    428 				fbuffree(s);
    429 				return TRUE;
    430 			}
    431 			--nb;
    432 			b++;
    433 			q++;
    434 			if(around && q>=ct->q1)
    435 				break;
    436 		}
    437 	}
    438 	fbuffree(s);
    439 	return FALSE;
    440 }
    441 
    442 int
    443 isfilec(Rune r)
    444 {
    445 	static Rune Lx[] = { '.', '-', '+', '/', ':', '@', 0 };
    446 	if(isalnum(r))
    447 		return TRUE;
    448 	if(runestrchr(Lx, r))
    449 		return TRUE;
    450 	return FALSE;
    451 }
    452 
    453 /* Runestr wrapper for cleanname */
    454 Runestr
    455 cleanrname(Runestr rs)
    456 {
    457 	char *s;
    458 	int nb, nulls;
    459 
    460 	s = runetobyte(rs.r, rs.nr);
    461 	cleanname(s);
    462 	cvttorunes(s, strlen(s), rs.r, &nb, &rs.nr, &nulls);
    463 	free(s);
    464 	return rs;
    465 }
    466 
    467 Runestr
    468 includefile(Rune *dir, Rune *file, int nfile)
    469 {
    470 	int m, n;
    471 	char *a;
    472 	Rune *r;
    473 	static Rune Lslash[] = { '/', 0 };
    474 
    475 	m = runestrlen(dir);
    476 	a = emalloc((m+1+nfile)*UTFmax+1);
    477 	sprint(a, "%S/%.*S", dir, nfile, file);
    478 	n = access(a, 0);
    479 	free(a);
    480 	if(n < 0)
    481 		return runestr(nil, 0);
    482 	r = runemalloc(m+1+nfile);
    483 	runemove(r, dir, m);
    484 	runemove(r+m, Lslash, 1);
    485 	runemove(r+m+1, file, nfile);
    486 	free(file);
    487 	return cleanrname(runestr(r, m+1+nfile));
    488 }
    489 
    490 static	Rune	*objdir;
    491 
    492 Runestr
    493 includename(Text *t, Rune *r, int n)
    494 {
    495 	Window *w;
    496 	char buf[128];
    497 	Rune Lsysinclude[] = { '/', 's', 'y', 's', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
    498 	Rune Lusrinclude[] = { '/', 'u', 's', 'r', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
    499 	Rune Lusrlocalinclude[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l',
    500 			'/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
    501 	Rune Lusrlocalplan9include[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l',
    502 			'/', 'p', 'l', 'a', 'n', '9', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
    503 	Runestr file;
    504 	int i;
    505 
    506 	if(objdir==nil && objtype!=nil){
    507 		sprint(buf, "/%s/include", objtype);
    508 		objdir = bytetorune(buf, &i);
    509 		objdir = runerealloc(objdir, i+1);
    510 		objdir[i] = '\0';
    511 	}
    512 
    513 	w = t->w;
    514 	if(n==0 || r[0]=='/' || w==nil)
    515 		goto Rescue;
    516 	if(n>2 && r[0]=='.' && r[1]=='/')
    517 		goto Rescue;
    518 	file.r = nil;
    519 	file.nr = 0;
    520 	for(i=0; i<w->nincl && file.r==nil; i++)
    521 		file = includefile(w->incl[i], r, n);
    522 
    523 	if(file.r == nil)
    524 		file = includefile(Lsysinclude, r, n);
    525 	if(file.r == nil)
    526 		file = includefile(Lusrlocalplan9include, r, n);
    527 	if(file.r == nil)
    528 		file = includefile(Lusrlocalinclude, r, n);
    529 	if(file.r == nil)
    530 		file = includefile(Lusrinclude, r, n);
    531 	if(file.r==nil && objdir!=nil)
    532 		file = includefile(objdir, r, n);
    533 	if(file.r == nil)
    534 		goto Rescue;
    535 	return file;
    536 
    537     Rescue:
    538 	return runestr(r, n);
    539 }
    540 
    541 Runestr
    542 dirname(Text *t, Rune *r, int n)
    543 {
    544 	Rune *b;
    545 	uint nt;
    546 	int slash, i;
    547 	Runestr tmp;
    548 
    549 	b = nil;
    550 	if(t==nil || t->w==nil)
    551 		goto Rescue;
    552 	nt = t->w->tag.file->b.nc;
    553 	if(nt == 0)
    554 		goto Rescue;
    555 	if(n>=1 && r[0]=='/')
    556 		goto Rescue;
    557 	b = parsetag(t->w, n, &i);
    558 	slash = -1;
    559 	for(i--; i >= 0; i--){
    560 		if(b[i] == '/'){
    561 			slash = i;
    562 			break;
    563 		}
    564 	}
    565 	if(slash < 0)
    566 		goto Rescue;
    567 	runemove(b+slash+1, r, n);
    568 	free(r);
    569 	return cleanrname(runestr(b, slash+1+n));
    570 
    571     Rescue:
    572 	free(b);
    573 	tmp = runestr(r, n);
    574 	if(r)
    575 		return cleanrname(tmp);
    576 	return tmp;
    577 }
    578 
    579 static int
    580 texthas(Text *t, uint q0, Rune *r)
    581 {
    582 	int i;
    583 
    584 	if((int)q0 < 0)
    585 		return FALSE;
    586 	for(i=0; r[i]; i++)
    587 		if(q0+i >= t->file->b.nc || textreadc(t, q0+i) != r[i])
    588 			return FALSE;
    589 	return TRUE;
    590 }
    591 
    592 int
    593 expandfile(Text *t, uint q0, uint q1, Expand *e, int reverse)
    594 {
    595 	int i, n, nname, colon, eval;
    596 	uint amin, amax;
    597 	Rune *r, c;
    598 	Window *w;
    599 	Runestr rs;
    600 	Rune Lhttpcss[] = {'h', 't', 't', 'p', ':', '/', '/', 0};
    601 	Rune Lhttpscss[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0};
    602 
    603 	amax = q1;
    604 	if(q1 == q0){
    605 		colon = -1;
    606 		while(q1<t->file->b.nc && isfilec(c=textreadc(t, q1))){
    607 			if(c == ':' && !texthas(t, q1-4, Lhttpcss) && !texthas(t, q1-5, Lhttpscss)){
    608 				colon = q1;
    609 				break;
    610 			}
    611 			q1++;
    612 		}
    613 		while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){
    614 			q0--;
    615 			if(colon<0 && c==':' && !texthas(t, q0-4, Lhttpcss) && !texthas(t, q0-5, Lhttpscss))
    616 				colon = q0;
    617 		}
    618 		/*
    619 		 * if it looks like it might begin file: , consume address chars after :
    620 		 * otherwise terminate expansion at :
    621 		 */
    622 		if(colon >= 0){
    623 			q1 = colon;
    624 			if(colon<t->file->b.nc-1 && isaddrc(textreadc(t, colon+1))){
    625 				q1 = colon+1;
    626 				while(q1<t->file->b.nc && isaddrc(textreadc(t, q1)))
    627 					q1++;
    628 			}
    629 		}
    630 		if(q1 > q0)
    631 			if(colon >= 0){	/* stop at white space */
    632 				for(amax=colon+1; amax<t->file->b.nc; amax++)
    633 					if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n')
    634 						break;
    635 			}else
    636 				amax = t->file->b.nc;
    637 		if(colon != q0)
    638 			reverse = FALSE;
    639 	}else if(reverse){
    640 		if(textreadc(t, q0) != ':')
    641 			reverse = FALSE;
    642 	}
    643 	amin = amax;
    644 	e->q0 = q0;
    645 	e->q1 = q1;
    646 	n = q1-q0;
    647 	if(n == 0)
    648 		return FALSE;
    649 	/* see if it's a file name */
    650 	r = runemalloc(n+1);
    651 	bufread(&t->file->b, q0, r, n);
    652 	r[n] = 0;
    653 	/* is it a URL? look for http:// and https:// prefix */
    654 	if(runestrncmp(r, Lhttpcss, 7) == 0 || runestrncmp(r, Lhttpscss, 8) == 0){
    655 		// Avoid capturing end-of-sentence punctuation.
    656 		if(r[n-1] == '.') {
    657 			e->q1--;
    658 			n--;
    659 		}
    660 		e->name = r;
    661 		e->nname = n;
    662 		e->u.at = t;
    663 		e->a0 = e->q1;
    664 		e->a1 = e->q1;
    665 		return TRUE;
    666 	}
    667 	/* first, does it have bad chars? */
    668 	nname = -1;
    669 	for(i=0; i<n; i++){
    670 		c = r[i];
    671 		if(c==':' && nname<0){
    672 			if(q0+i+1<t->file->b.nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1))))
    673 				amin = q0+i;
    674 			else
    675 				goto Isntfile;
    676 			nname = i;
    677 		}
    678 	}
    679 	if(nname == -1)
    680 		nname = n;
    681 	for(i=0; i<nname; i++)
    682 		if(!isfilec(r[i]) && r[i] != ' ')
    683 			goto Isntfile;
    684 	/*
    685 	 * See if it's a file name in <>, and turn that into an include
    686 	 * file name if so.  Should probably do it for "" too, but that's not
    687 	 * restrictive enough syntax and checking for a #include earlier on the
    688 	 * line would be silly.
    689 	 */
    690 	if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->b.nc && textreadc(t, q1)=='>'){
    691 		rs = includename(t, r, nname);
    692 		r = rs.r;
    693 		nname = rs.nr;
    694 	}
    695 	else if(amin == q0)
    696 		goto Isfile;
    697 	else{
    698 		rs = dirname(t, r, nname);
    699 		r = rs.r;
    700 		nname = rs.nr;
    701 	}
    702 	e->bname = runetobyte(r, nname);
    703 	/* if it's already a window name, it's a file */
    704 	w = lookfile(r, nname);
    705 	if(w != nil)
    706 		goto Isfile;
    707 	/* if it's the name of a file, it's a file */
    708 	if(ismtpt(e->bname) || access(e->bname, 0) < 0){
    709 		free(e->bname);
    710 		e->bname = nil;
    711 		goto Isntfile;
    712 	}
    713 
    714   Isfile:
    715 	e->name = r;
    716 	e->nname = nname;
    717 	e->u.at = t;
    718 	e->a0 = amin+1;
    719 	e->reverse = reverse;
    720 	eval = FALSE;
    721 	// Note: address is repeated in openfile when
    722 	// expandfile returns to expand returns to look3.
    723 	address(TRUE, nil, range(-1,-1), range(0,0), t, e->a0, amax, tgetc, &eval, (uint*)&e->a1, e->reverse);
    724 	return TRUE;
    725 
    726    Isntfile:
    727 	free(r);
    728 	return FALSE;
    729 }
    730 
    731 int
    732 expand(Text *t, uint q0, uint q1, Expand *e, int reverse)
    733 {
    734 	memset(e, 0, sizeof *e);
    735 	e->agetc = tgetc;
    736 	/* if in selection, choose selection */
    737 	e->jump = TRUE;
    738 	if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
    739 		q0 = t->q0;
    740 		q1 = t->q1;
    741 		if(t->what == Tag)
    742 			e->jump = FALSE;
    743 	}
    744 
    745 	if(expandfile(t, q0, q1, e, reverse))
    746 		return TRUE;
    747 
    748 	if(q0 == q1){
    749 		while(q1<t->file->b.nc && isalnum(textreadc(t, q1)))
    750 			q1++;
    751 		while(q0>0 && isalnum(textreadc(t, q0-1)))
    752 			q0--;
    753 	}
    754 	e->q0 = q0;
    755 	e->q1 = q1;
    756 	return q1 > q0;
    757 }
    758 
    759 Window*
    760 lookfile(Rune *s, int n)
    761 {
    762 	int i, j, k;
    763 	Window *w;
    764 	Column *c;
    765 	Text *t;
    766 
    767 	/* avoid terminal slash on directories */
    768 	if(n>1 && s[n-1] == '/')
    769 		--n;
    770 	for(j=0; j<row.ncol; j++){
    771 		c = row.col[j];
    772 		for(i=0; i<c->nw; i++){
    773 			w = c->w[i];
    774 			t = &w->body;
    775 			k = t->file->nname;
    776 			if(k>1 && t->file->name[k-1] == '/')
    777 				k--;
    778 			if(runeeq(t->file->name, k, s, n)){
    779 				w = w->body.file->curtext->w;
    780 				if(w->col != nil)	/* protect against race deleting w */
    781 					return w;
    782 			}
    783 		}
    784 	}
    785 	return nil;
    786 }
    787 
    788 Window*
    789 lookid(int id, int dump)
    790 {
    791 	int i, j;
    792 	Window *w;
    793 	Column *c;
    794 
    795 	for(j=0; j<row.ncol; j++){
    796 		c = row.col[j];
    797 		for(i=0; i<c->nw; i++){
    798 			w = c->w[i];
    799 			if(dump && w->dumpid == id)
    800 				return w;
    801 			if(!dump && w->id == id)
    802 				return w;
    803 		}
    804 	}
    805 	return nil;
    806 }
    807 
    808 
    809 Window*
    810 openfile(Text *t, Expand *e)
    811 {
    812 	Range r;
    813 	Window *w, *ow;
    814 	int eval, i, n;
    815 	Rune *rp;
    816 	Runestr rs;
    817 	uint dummy;
    818 
    819 	r.q0 = 0;
    820 	r.q1 = 0;
    821 	if(e->nname == 0){
    822 		w = t->w;
    823 		if(w == nil)
    824 			return nil;
    825 	}else{
    826 		w = lookfile(e->name, e->nname);
    827 		if(w == nil && e->name[0] != '/'){
    828 			/*
    829 			 * Unrooted path in new window.
    830 			 * This can happen if we type a pwd-relative path
    831 			 * in the topmost tag or the column tags.
    832 			 * Most of the time plumber takes care of these,
    833 			 * but plumber might not be running or might not
    834 			 * be configured to accept plumbed directories.
    835 			 * Make the name a full path, just like we would if
    836 			 * opening via the plumber.
    837 			 */
    838 			n = utflen(wdir)+1+e->nname+1;
    839 			rp = runemalloc(n);
    840 			runesnprint(rp, n, "%s/%.*S", wdir, e->nname, e->name);
    841 			rs = cleanrname(runestr(rp, n-1));
    842 			free(e->name);
    843 			e->name = rs.r;
    844 			e->nname = rs.nr;
    845 			w = lookfile(e->name, e->nname);
    846 		}
    847 	}
    848 	if(w){
    849 		t = &w->body;
    850 		if(!t->col->safe && t->fr.maxlines==0) /* window is obscured by full-column window */
    851 			colgrow(t->col, t->col->w[0], 1);
    852 	}else{
    853 		ow = nil;
    854 		if(t)
    855 			ow = t->w;
    856 		w = makenewwindow(t);
    857 		t = &w->body;
    858 		winsetname(w, e->name, e->nname);
    859 		if(textload(t, 0, e->bname, 1) >= 0)
    860 			t->file->unread = FALSE;
    861 		t->file->mod = FALSE;
    862 		t->w->dirty = FALSE;
    863 		winsettag(t->w);
    864 		textsetselect(&t->w->tag, t->w->tag.file->b.nc, t->w->tag.file->b.nc);
    865 		if(ow != nil){
    866 			for(i=ow->nincl; --i>=0; ){
    867 				n = runestrlen(ow->incl[i]);
    868 				rp = runemalloc(n);
    869 				runemove(rp, ow->incl[i], n);
    870 				winaddincl(w, rp, n);
    871 			}
    872 			w->autoindent = ow->autoindent;
    873 		}else
    874 			w->autoindent = globalautoindent;
    875 		xfidlog(w, "new");
    876 	}
    877 	if(e->a1 == e->a0)
    878 		eval = FALSE;
    879 	else{
    880 		eval = TRUE;
    881 		r = address(TRUE, t, range(-1,-1), range(t->q0, t->q1), e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy, e->reverse);
    882 		if(r.q0 > r.q1) {
    883 			eval = FALSE;
    884 			warning(nil, "addresses out of order\n");
    885 		}
    886 		if(eval == FALSE)
    887 			e->jump = FALSE;	/* don't jump if invalid address */
    888 	}
    889 	if(eval == FALSE){
    890 		r.q0 = t->q0;
    891 		r.q1 = t->q1;
    892 	}
    893 	textshow(t, r.q0, r.q1, 1);
    894 	winsettag(t->w);
    895 	seltext = t;
    896 	if(e->jump)
    897 		moveto(mousectl, addpt(frptofchar(&t->fr, t->fr.p0), Pt(4, font->height-4)));
    898 	return w;
    899 }
    900 
    901 void
    902 new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg)
    903 {
    904 	int ndone;
    905 	Rune *a, *f;
    906 	int na, nf;
    907 	Expand e;
    908 	Runestr rs;
    909 	Window *w;
    910 
    911 	getarg(argt, FALSE, TRUE, &a, &na);
    912 	if(a){
    913 		new(et, t, nil, flag1, flag2, a, na);
    914 		if(narg == 0)
    915 			return;
    916 	}
    917 	/* loop condition: *arg is not a blank */
    918 	for(ndone=0; ; ndone++){
    919 		a = findbl(arg, narg, &na);
    920 		if(a == arg){
    921 			if(ndone==0 && et->col!=nil) {
    922 				w = coladd(et->col, nil, nil, -1);
    923 				winsettag(w);
    924 				xfidlog(w, "new");
    925 			}
    926 			break;
    927 		}
    928 		nf = narg-na;
    929 		f = runemalloc(nf);
    930 		runemove(f, arg, nf);
    931 		rs = dirname(et, f, nf);
    932 		memset(&e, 0, sizeof e);
    933 		e.name = rs.r;
    934 		e.nname = rs.nr;
    935 		e.bname = runetobyte(rs.r, rs.nr);
    936 		e.jump = TRUE;
    937 		openfile(et, &e);
    938 		free(e.name);
    939 		free(e.bname);
    940 		arg = skipbl(a, na, &narg);
    941 	}
    942 }