|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <draw.h> | 
|  | #include <thread.h> | 
|  | #include <cursor.h> | 
|  | #include <mouse.h> | 
|  | #include <keyboard.h> | 
|  | #include <frame.h> | 
|  | #include <fcall.h> | 
|  | #include <regexp.h> | 
|  | #include <9pclient.h> | 
|  | #include <plumb.h> | 
|  | #include "dat.h" | 
|  | #include "fns.h" | 
|  |  | 
|  | CFid *plumbsendfid; | 
|  | CFid *plumbeditfid; | 
|  |  | 
|  | Window*	openfile(Text*, Expand*); | 
|  |  | 
|  | int	nuntitled; | 
|  |  | 
|  | void | 
|  | plumbthread(void *v) | 
|  | { | 
|  | CFid *fid; | 
|  | Plumbmsg *m; | 
|  | Timer *t; | 
|  |  | 
|  | USED(v); | 
|  | threadsetname("plumbproc"); | 
|  |  | 
|  | /* | 
|  | * Loop so that if plumber is restarted, acme need not be. | 
|  | */ | 
|  | for(;;){ | 
|  | /* | 
|  | * Connect to plumber. | 
|  | */ | 
|  | plumbunmount(); | 
|  | while((fid = plumbopenfid("edit", OREAD|OCEXEC)) == nil){ | 
|  | t = timerstart(2000); | 
|  | recv(t->c, nil); | 
|  | timerstop(t); | 
|  | } | 
|  | plumbeditfid = fid; | 
|  | plumbsendfid = plumbopenfid("send", OWRITE|OCEXEC); | 
|  |  | 
|  | /* | 
|  | * Relay messages. | 
|  | */ | 
|  | for(;;){ | 
|  | m = plumbrecvfid(plumbeditfid); | 
|  | if(m == nil) | 
|  | break; | 
|  | sendp(cplumb, m); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Lost connection. | 
|  | */ | 
|  | fid = plumbsendfid; | 
|  | plumbsendfid = nil; | 
|  | fsclose(fid); | 
|  |  | 
|  | fid = plumbeditfid; | 
|  | plumbeditfid = nil; | 
|  | fsclose(fid); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | startplumbing(void) | 
|  | { | 
|  | cplumb = chancreate(sizeof(Plumbmsg*), 0); | 
|  | chansetname(cplumb, "cplumb"); | 
|  | threadcreate(plumbthread, nil, STACK); | 
|  | } | 
|  |  | 
|  |  | 
|  | void | 
|  | look3(Text *t, uint q0, uint q1, int external) | 
|  | { | 
|  | int n, c, f, expanded; | 
|  | Text *ct; | 
|  | Expand e; | 
|  | Rune *r; | 
|  | uint p; | 
|  | Plumbmsg *m; | 
|  | Runestr dir; | 
|  | char buf[32]; | 
|  |  | 
|  | ct = seltext; | 
|  | if(ct == nil) | 
|  | seltext = t; | 
|  | expanded = expand(t, q0, q1, &e); | 
|  | if(!external && t->w!=nil && t->w->nopen[QWevent]>0){ | 
|  | /* send alphanumeric expansion to external client */ | 
|  | if(expanded == FALSE) | 
|  | return; | 
|  | f = 0; | 
|  | if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil)) | 
|  | f = 1;		/* acme can do it without loading a file */ | 
|  | if(q0!=e.q0 || q1!=e.q1) | 
|  | f |= 2;	/* second (post-expand) message follows */ | 
|  | if(e.nname) | 
|  | f |= 4;	/* it's a file name */ | 
|  | c = 'l'; | 
|  | if(t->what == Body) | 
|  | c = 'L'; | 
|  | n = q1-q0; | 
|  | if(n <= EVENTSIZE){ | 
|  | r = runemalloc(n); | 
|  | bufread(&t->file->b, q0, r, n); | 
|  | winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r); | 
|  | free(r); | 
|  | }else | 
|  | winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n); | 
|  | if(q0==e.q0 && q1==e.q1) | 
|  | return; | 
|  | if(e.nname){ | 
|  | n = e.nname; | 
|  | if(e.a1 > e.a0) | 
|  | n += 1+(e.a1-e.a0); | 
|  | r = runemalloc(n); | 
|  | runemove(r, e.name, e.nname); | 
|  | if(e.a1 > e.a0){ | 
|  | r[e.nname] = ':'; | 
|  | bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0); | 
|  | } | 
|  | }else{ | 
|  | n = e.q1 - e.q0; | 
|  | r = runemalloc(n); | 
|  | bufread(&t->file->b, e.q0, r, n); | 
|  | } | 
|  | f &= ~2; | 
|  | if(n <= EVENTSIZE) | 
|  | winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r); | 
|  | else | 
|  | winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n); | 
|  | free(r); | 
|  | goto Return; | 
|  | } | 
|  | if(plumbsendfid != nil){ | 
|  | /* send whitespace-delimited word to plumber */ | 
|  | m = emalloc(sizeof(Plumbmsg)); | 
|  | m->src = estrdup("acme"); | 
|  | m->dst = nil; | 
|  | dir = dirname(t, nil, 0); | 
|  | if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */ | 
|  | free(dir.r); | 
|  | dir.r = nil; | 
|  | dir.nr = 0; | 
|  | } | 
|  | if(dir.nr == 0) | 
|  | m->wdir = estrdup(wdir); | 
|  | else | 
|  | m->wdir = runetobyte(dir.r, dir.nr); | 
|  | free(dir.r); | 
|  | m->type = estrdup("text"); | 
|  | m->attr = nil; | 
|  | buf[0] = '\0'; | 
|  | if(q1 == q0){ | 
|  | if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ | 
|  | q0 = t->q0; | 
|  | q1 = t->q1; | 
|  | }else{ | 
|  | p = q0; | 
|  | while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n') | 
|  | q0--; | 
|  | while(q1<t->file->b.nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n') | 
|  | q1++; | 
|  | if(q1 == q0){ | 
|  | plumbfree(m); | 
|  | goto Return; | 
|  | } | 
|  | sprint(buf, "click=%d", p-q0); | 
|  | m->attr = plumbunpackattr(buf); | 
|  | } | 
|  | } | 
|  | r = runemalloc(q1-q0); | 
|  | bufread(&t->file->b, q0, r, q1-q0); | 
|  | m->data = runetobyte(r, q1-q0); | 
|  | m->ndata = strlen(m->data); | 
|  | free(r); | 
|  | if(m->ndata<messagesize-1024 && plumbsendtofid(plumbsendfid, m) >= 0){ | 
|  | plumbfree(m); | 
|  | goto Return; | 
|  | } | 
|  | plumbfree(m); | 
|  | /* plumber failed to match; fall through */ | 
|  | } | 
|  |  | 
|  | /* interpret alphanumeric string ourselves */ | 
|  | if(expanded == FALSE) | 
|  | return; | 
|  | if(e.name || e.u.at) | 
|  | openfile(t, &e); | 
|  | else{ | 
|  | if(t->w == nil) | 
|  | return; | 
|  | ct = &t->w->body; | 
|  | if(t->w != ct->w) | 
|  | winlock(ct->w, 'M'); | 
|  | if(t == ct) | 
|  | textsetselect(ct, e.q1, e.q1); | 
|  | n = e.q1 - e.q0; | 
|  | r = runemalloc(n); | 
|  | bufread(&t->file->b, e.q0, r, n); | 
|  | if(search(ct, r, n) && e.jump) | 
|  | moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4))); | 
|  | if(t->w != ct->w) | 
|  | winunlock(ct->w); | 
|  | free(r); | 
|  | } | 
|  |  | 
|  | Return: | 
|  | free(e.name); | 
|  | free(e.bname); | 
|  | } | 
|  |  | 
|  | int | 
|  | plumbgetc(void *a, uint n) | 
|  | { | 
|  | Rune *r; | 
|  |  | 
|  | r = a; | 
|  | if(n>runestrlen(r)) | 
|  | return 0; | 
|  | return r[n]; | 
|  | } | 
|  |  | 
|  | void | 
|  | plumblook(Plumbmsg *m) | 
|  | { | 
|  | Expand e; | 
|  | char *addr; | 
|  |  | 
|  | if(m->ndata >= BUFSIZE){ | 
|  | warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data); | 
|  | return; | 
|  | } | 
|  | e.q0 = 0; | 
|  | e.q1 = 0; | 
|  | if(m->data[0] == '\0') | 
|  | return; | 
|  | e.u.ar = nil; | 
|  | e.bname = m->data; | 
|  | e.name = bytetorune(e.bname, &e.nname); | 
|  | e.jump = TRUE; | 
|  | e.a0 = 0; | 
|  | e.a1 = 0; | 
|  | addr = plumblookup(m->attr, "addr"); | 
|  | if(addr != nil){ | 
|  | e.u.ar = bytetorune(addr, &e.a1); | 
|  | e.agetc = plumbgetc; | 
|  | } | 
|  | drawtopwindow(); | 
|  | openfile(nil, &e); | 
|  | free(e.name); | 
|  | free(e.u.at); | 
|  | } | 
|  |  | 
|  | void | 
|  | plumbshow(Plumbmsg *m) | 
|  | { | 
|  | Window *w; | 
|  | Rune rb[256], *r; | 
|  | int nb, nr; | 
|  | Runestr rs; | 
|  | char *name, *p, namebuf[16]; | 
|  |  | 
|  | drawtopwindow(); | 
|  | w = makenewwindow(nil); | 
|  | name = plumblookup(m->attr, "filename"); | 
|  | if(name == nil){ | 
|  | name = namebuf; | 
|  | nuntitled++; | 
|  | snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled); | 
|  | } | 
|  | p = nil; | 
|  | if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){ | 
|  | nb = strlen(m->wdir) + 1 + strlen(name) + 1; | 
|  | p = emalloc(nb); | 
|  | snprint(p, nb, "%s/%s", m->wdir, name); | 
|  | name = p; | 
|  | } | 
|  | cvttorunes(name, strlen(name), rb, &nb, &nr, nil); | 
|  | free(p); | 
|  | rs = cleanrname(runestr(rb, nr)); | 
|  | winsetname(w, rs.r, rs.nr); | 
|  | r = runemalloc(m->ndata); | 
|  | cvttorunes(m->data, m->ndata, r, &nb, &nr, nil); | 
|  | textinsert(&w->body, 0, r, nr, TRUE); | 
|  | free(r); | 
|  | w->body.file->mod = FALSE; | 
|  | w->dirty = FALSE; | 
|  | winsettag(w); | 
|  | textscrdraw(&w->body); | 
|  | textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc); | 
|  | xfidlog(w, "new"); | 
|  | } | 
|  |  | 
|  | int | 
|  | search(Text *ct, Rune *r, uint n) | 
|  | { | 
|  | uint q, nb, maxn; | 
|  | int around; | 
|  | Rune *s, *b, *c; | 
|  |  | 
|  | if(n==0 || n>ct->file->b.nc) | 
|  | return FALSE; | 
|  | if(2*n > RBUFSIZE){ | 
|  | warning(nil, "string too long\n"); | 
|  | return FALSE; | 
|  | } | 
|  | maxn = max(2*n, RBUFSIZE); | 
|  | s = fbufalloc(); | 
|  | b = s; | 
|  | nb = 0; | 
|  | b[nb] = 0; | 
|  | around = 0; | 
|  | q = ct->q1; | 
|  | for(;;){ | 
|  | if(q >= ct->file->b.nc){ | 
|  | q = 0; | 
|  | around = 1; | 
|  | nb = 0; | 
|  | b[nb] = 0; | 
|  | } | 
|  | if(nb > 0){ | 
|  | c = runestrchr(b, r[0]); | 
|  | if(c == nil){ | 
|  | q += nb; | 
|  | nb = 0; | 
|  | b[nb] = 0; | 
|  | if(around && q>=ct->q1) | 
|  | break; | 
|  | continue; | 
|  | } | 
|  | q += (c-b); | 
|  | nb -= (c-b); | 
|  | b = c; | 
|  | } | 
|  | /* reload if buffer covers neither string nor rest of file */ | 
|  | if(nb<n && nb!=ct->file->b.nc-q){ | 
|  | nb = ct->file->b.nc-q; | 
|  | if(nb >= maxn) | 
|  | nb = maxn-1; | 
|  | bufread(&ct->file->b, q, s, nb); | 
|  | b = s; | 
|  | b[nb] = '\0'; | 
|  | } | 
|  | /* this runeeq is fishy but the null at b[nb] makes it safe */ | 
|  | if(runeeq(b, n, r, n)==TRUE){ | 
|  | if(ct->w){ | 
|  | textshow(ct, q, q+n, 1); | 
|  | winsettag(ct->w); | 
|  | }else{ | 
|  | ct->q0 = q; | 
|  | ct->q1 = q+n; | 
|  | } | 
|  | seltext = ct; | 
|  | fbuffree(s); | 
|  | return TRUE; | 
|  | } | 
|  | --nb; | 
|  | b++; | 
|  | q++; | 
|  | if(around && q>=ct->q1) | 
|  | break; | 
|  | } | 
|  | fbuffree(s); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | int | 
|  | isfilec(Rune r) | 
|  | { | 
|  | static Rune Lx[] = { '.', '-', '+', '/', ':', 0 }; | 
|  | if(isalnum(r)) | 
|  | return TRUE; | 
|  | if(runestrchr(Lx, r)) | 
|  | return TRUE; | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | /* Runestr wrapper for cleanname */ | 
|  | Runestr | 
|  | cleanrname(Runestr rs) | 
|  | { | 
|  | char *s; | 
|  | int nb, nulls; | 
|  |  | 
|  | s = runetobyte(rs.r, rs.nr); | 
|  | cleanname(s); | 
|  | cvttorunes(s, strlen(s), rs.r, &nb, &rs.nr, &nulls); | 
|  | free(s); | 
|  | return rs; | 
|  | } | 
|  |  | 
|  | Runestr | 
|  | includefile(Rune *dir, Rune *file, int nfile) | 
|  | { | 
|  | int m, n; | 
|  | char *a; | 
|  | Rune *r; | 
|  | static Rune Lslash[] = { '/', 0 }; | 
|  |  | 
|  | m = runestrlen(dir); | 
|  | a = emalloc((m+1+nfile)*UTFmax+1); | 
|  | sprint(a, "%S/%.*S", dir, nfile, file); | 
|  | n = access(a, 0); | 
|  | free(a); | 
|  | if(n < 0) | 
|  | return runestr(nil, 0); | 
|  | r = runemalloc(m+1+nfile); | 
|  | runemove(r, dir, m); | 
|  | runemove(r+m, Lslash, 1); | 
|  | runemove(r+m+1, file, nfile); | 
|  | free(file); | 
|  | return cleanrname(runestr(r, m+1+nfile)); | 
|  | } | 
|  |  | 
|  | static	Rune	*objdir; | 
|  |  | 
|  | Runestr | 
|  | includename(Text *t, Rune *r, int n) | 
|  | { | 
|  | Window *w; | 
|  | char buf[128]; | 
|  | Rune Lsysinclude[] = { '/', 's', 'y', 's', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 }; | 
|  | Rune Lusrinclude[] = { '/', 'u', 's', 'r', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 }; | 
|  | Rune Lusrlocalinclude[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l', | 
|  | '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 }; | 
|  | Rune Lusrlocalplan9include[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l', | 
|  | '/', 'p', 'l', 'a', 'n', '9', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 }; | 
|  | Runestr file; | 
|  | int i; | 
|  |  | 
|  | if(objdir==nil && objtype!=nil){ | 
|  | sprint(buf, "/%s/include", objtype); | 
|  | objdir = bytetorune(buf, &i); | 
|  | objdir = runerealloc(objdir, i+1); | 
|  | objdir[i] = '\0'; | 
|  | } | 
|  |  | 
|  | w = t->w; | 
|  | if(n==0 || r[0]=='/' || w==nil) | 
|  | goto Rescue; | 
|  | if(n>2 && r[0]=='.' && r[1]=='/') | 
|  | goto Rescue; | 
|  | file.r = nil; | 
|  | file.nr = 0; | 
|  | for(i=0; i<w->nincl && file.r==nil; i++) | 
|  | file = includefile(w->incl[i], r, n); | 
|  |  | 
|  | if(file.r == nil) | 
|  | file = includefile(Lsysinclude, r, n); | 
|  | if(file.r == nil) | 
|  | file = includefile(Lusrlocalplan9include, r, n); | 
|  | if(file.r == nil) | 
|  | file = includefile(Lusrlocalinclude, r, n); | 
|  | if(file.r == nil) | 
|  | file = includefile(Lusrinclude, r, n); | 
|  | if(file.r==nil && objdir!=nil) | 
|  | file = includefile(objdir, r, n); | 
|  | if(file.r == nil) | 
|  | goto Rescue; | 
|  | return file; | 
|  |  | 
|  | Rescue: | 
|  | return runestr(r, n); | 
|  | } | 
|  |  | 
|  | Runestr | 
|  | dirname(Text *t, Rune *r, int n) | 
|  | { | 
|  | Rune *b, c; | 
|  | uint m, nt; | 
|  | int slash; | 
|  | Runestr tmp; | 
|  |  | 
|  | b = nil; | 
|  | if(t==nil || t->w==nil) | 
|  | goto Rescue; | 
|  | nt = t->w->tag.file->b.nc; | 
|  | if(nt == 0) | 
|  | goto Rescue; | 
|  | if(n>=1 && r[0]=='/') | 
|  | goto Rescue; | 
|  | b = runemalloc(nt+n+1); | 
|  | bufread(&t->w->tag.file->b, 0, b, nt); | 
|  | slash = -1; | 
|  | for(m=0; m<nt; m++){ | 
|  | c = b[m]; | 
|  | if(c == '/') | 
|  | slash = m; | 
|  | if(c==' ' || c=='\t') | 
|  | break; | 
|  | } | 
|  | if(slash < 0) | 
|  | goto Rescue; | 
|  | runemove(b+slash+1, r, n); | 
|  | free(r); | 
|  | return cleanrname(runestr(b, slash+1+n)); | 
|  |  | 
|  | Rescue: | 
|  | free(b); | 
|  | tmp = runestr(r, n); | 
|  | if(r) | 
|  | return cleanrname(tmp); | 
|  | return tmp; | 
|  | } | 
|  |  | 
|  | int | 
|  | expandfile(Text *t, uint q0, uint q1, Expand *e) | 
|  | { | 
|  | int i, n, nname, colon, eval; | 
|  | uint amin, amax; | 
|  | Rune *r, c; | 
|  | Window *w; | 
|  | Runestr rs; | 
|  |  | 
|  | amax = q1; | 
|  | if(q1 == q0){ | 
|  | colon = -1; | 
|  | while(q1<t->file->b.nc && isfilec(c=textreadc(t, q1))){ | 
|  | if(c == ':'){ | 
|  | colon = q1; | 
|  | break; | 
|  | } | 
|  | q1++; | 
|  | } | 
|  | while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){ | 
|  | q0--; | 
|  | if(colon<0 && c==':') | 
|  | colon = q0; | 
|  | } | 
|  | /* | 
|  | * if it looks like it might begin file: , consume address chars after : | 
|  | * otherwise terminate expansion at : | 
|  | */ | 
|  | if(colon >= 0){ | 
|  | q1 = colon; | 
|  | if(colon<t->file->b.nc-1 && isaddrc(textreadc(t, colon+1))){ | 
|  | q1 = colon+1; | 
|  | while(q1<t->file->b.nc && isaddrc(textreadc(t, q1))) | 
|  | q1++; | 
|  | } | 
|  | } | 
|  | if(q1 > q0) | 
|  | if(colon >= 0){	/* stop at white space */ | 
|  | for(amax=colon+1; amax<t->file->b.nc; amax++) | 
|  | if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n') | 
|  | break; | 
|  | }else | 
|  | amax = t->file->b.nc; | 
|  | } | 
|  | amin = amax; | 
|  | e->q0 = q0; | 
|  | e->q1 = q1; | 
|  | n = q1-q0; | 
|  | if(n == 0) | 
|  | return FALSE; | 
|  | /* see if it's a file name */ | 
|  | r = runemalloc(n); | 
|  | bufread(&t->file->b, q0, r, n); | 
|  | /* first, does it have bad chars? */ | 
|  | nname = -1; | 
|  | for(i=0; i<n; i++){ | 
|  | c = r[i]; | 
|  | if(c==':' && nname<0){ | 
|  | if(q0+i+1<t->file->b.nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1)))) | 
|  | amin = q0+i; | 
|  | else | 
|  | goto Isntfile; | 
|  | nname = i; | 
|  | } | 
|  | } | 
|  | if(nname == -1) | 
|  | nname = n; | 
|  | for(i=0; i<nname; i++) | 
|  | if(!isfilec(r[i])) | 
|  | goto Isntfile; | 
|  | /* | 
|  | * See if it's a file name in <>, and turn that into an include | 
|  | * file name if so.  Should probably do it for "" too, but that's not | 
|  | * restrictive enough syntax and checking for a #include earlier on the | 
|  | * line would be silly. | 
|  | */ | 
|  | if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->b.nc && textreadc(t, q1)=='>'){ | 
|  | rs = includename(t, r, nname); | 
|  | r = rs.r; | 
|  | nname = rs.nr; | 
|  | } | 
|  | else if(amin == q0) | 
|  | goto Isfile; | 
|  | else{ | 
|  | rs = dirname(t, r, nname); | 
|  | r = rs.r; | 
|  | nname = rs.nr; | 
|  | } | 
|  | e->bname = runetobyte(r, nname); | 
|  | /* if it's already a window name, it's a file */ | 
|  | w = lookfile(r, nname); | 
|  | if(w != nil) | 
|  | goto Isfile; | 
|  | /* if it's the name of a file, it's a file */ | 
|  | if(ismtpt(e->bname) || access(e->bname, 0) < 0){ | 
|  | free(e->bname); | 
|  | e->bname = nil; | 
|  | goto Isntfile; | 
|  | } | 
|  |  | 
|  | Isfile: | 
|  | e->name = r; | 
|  | e->nname = nname; | 
|  | e->u.at = t; | 
|  | e->a0 = amin+1; | 
|  | eval = FALSE; | 
|  | address(TRUE, nil, range(-1,-1), range(0,0), t, e->a0, amax, tgetc, &eval, (uint*)&e->a1); | 
|  | return TRUE; | 
|  |  | 
|  | Isntfile: | 
|  | free(r); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | int | 
|  | expand(Text *t, uint q0, uint q1, Expand *e) | 
|  | { | 
|  | memset(e, 0, sizeof *e); | 
|  | e->agetc = tgetc; | 
|  | /* if in selection, choose selection */ | 
|  | e->jump = TRUE; | 
|  | if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ | 
|  | q0 = t->q0; | 
|  | q1 = t->q1; | 
|  | if(t->what == Tag) | 
|  | e->jump = FALSE; | 
|  | } | 
|  |  | 
|  | if(expandfile(t, q0, q1, e)) | 
|  | return TRUE; | 
|  |  | 
|  | if(q0 == q1){ | 
|  | while(q1<t->file->b.nc && isalnum(textreadc(t, q1))) | 
|  | q1++; | 
|  | while(q0>0 && isalnum(textreadc(t, q0-1))) | 
|  | q0--; | 
|  | } | 
|  | e->q0 = q0; | 
|  | e->q1 = q1; | 
|  | return q1 > q0; | 
|  | } | 
|  |  | 
|  | Window* | 
|  | lookfile(Rune *s, int n) | 
|  | { | 
|  | int i, j, k; | 
|  | Window *w; | 
|  | Column *c; | 
|  | Text *t; | 
|  |  | 
|  | /* avoid terminal slash on directories */ | 
|  | if(n>1 && s[n-1] == '/') | 
|  | --n; | 
|  | for(j=0; j<row.ncol; j++){ | 
|  | c = row.col[j]; | 
|  | for(i=0; i<c->nw; i++){ | 
|  | w = c->w[i]; | 
|  | t = &w->body; | 
|  | k = t->file->nname; | 
|  | if(k>1 && t->file->name[k-1] == '/') | 
|  | k--; | 
|  | if(runeeq(t->file->name, k, s, n)){ | 
|  | w = w->body.file->curtext->w; | 
|  | if(w->col != nil)	/* protect against race deleting w */ | 
|  | return w; | 
|  | } | 
|  | } | 
|  | } | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | Window* | 
|  | lookid(int id, int dump) | 
|  | { | 
|  | int i, j; | 
|  | Window *w; | 
|  | Column *c; | 
|  |  | 
|  | for(j=0; j<row.ncol; j++){ | 
|  | c = row.col[j]; | 
|  | for(i=0; i<c->nw; i++){ | 
|  | w = c->w[i]; | 
|  | if(dump && w->dumpid == id) | 
|  | return w; | 
|  | if(!dump && w->id == id) | 
|  | return w; | 
|  | } | 
|  | } | 
|  | return nil; | 
|  | } | 
|  |  | 
|  |  | 
|  | Window* | 
|  | openfile(Text *t, Expand *e) | 
|  | { | 
|  | Range r; | 
|  | Window *w, *ow; | 
|  | int eval, i, n; | 
|  | Rune *rp; | 
|  | Runestr rs; | 
|  | uint dummy; | 
|  |  | 
|  | r.q0 = 0; | 
|  | r.q1 = 0; | 
|  | if(e->nname == 0){ | 
|  | w = t->w; | 
|  | if(w == nil) | 
|  | return nil; | 
|  | }else{ | 
|  | w = lookfile(e->name, e->nname); | 
|  | if(w == nil && e->name[0] != '/'){ | 
|  | /* | 
|  | * Unrooted path in new window. | 
|  | * This can happen if we type a pwd-relative path | 
|  | * in the topmost tag or the column tags. | 
|  | * Most of the time plumber takes care of these, | 
|  | * but plumber might not be running or might not | 
|  | * be configured to accept plumbed directories. | 
|  | * Make the name a full path, just like we would if | 
|  | * opening via the plumber. | 
|  | */ | 
|  | n = utflen(wdir)+1+e->nname+1; | 
|  | rp = runemalloc(n); | 
|  | runesnprint(rp, n, "%s/%.*S", wdir, e->nname, e->name); | 
|  | rs = cleanrname(runestr(rp, n-1)); | 
|  | free(e->name); | 
|  | e->name = rs.r; | 
|  | e->nname = rs.nr; | 
|  | w = lookfile(e->name, e->nname); | 
|  | } | 
|  | } | 
|  | if(w){ | 
|  | t = &w->body; | 
|  | if(!t->col->safe && t->fr.maxlines==0) /* window is obscured by full-column window */ | 
|  | colgrow(t->col, t->col->w[0], 1); | 
|  | }else{ | 
|  | ow = nil; | 
|  | if(t) | 
|  | ow = t->w; | 
|  | w = makenewwindow(t); | 
|  | t = &w->body; | 
|  | winsetname(w, e->name, e->nname); | 
|  | if(textload(t, 0, e->bname, 1) >= 0) | 
|  | t->file->unread = FALSE; | 
|  | t->file->mod = FALSE; | 
|  | t->w->dirty = FALSE; | 
|  | winsettag(t->w); | 
|  | textsetselect(&t->w->tag, t->w->tag.file->b.nc, t->w->tag.file->b.nc); | 
|  | if(ow != nil){ | 
|  | for(i=ow->nincl; --i>=0; ){ | 
|  | n = runestrlen(ow->incl[i]); | 
|  | rp = runemalloc(n); | 
|  | runemove(rp, ow->incl[i], n); | 
|  | winaddincl(w, rp, n); | 
|  | } | 
|  | w->autoindent = ow->autoindent; | 
|  | }else | 
|  | w->autoindent = globalautoindent; | 
|  | xfidlog(w, "new"); | 
|  | } | 
|  | if(e->a1 == e->a0) | 
|  | eval = FALSE; | 
|  | else{ | 
|  | eval = TRUE; | 
|  | r = address(TRUE, t, range(-1,-1), range(t->q0, t->q1), e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy); | 
|  | if(r.q0 > r.q1) { | 
|  | eval = FALSE; | 
|  | warning(nil, "addresses out of order\n"); | 
|  | } | 
|  | if(eval == FALSE) | 
|  | e->jump = FALSE;	/* don't jump if invalid address */ | 
|  | } | 
|  | if(eval == FALSE){ | 
|  | r.q0 = t->q0; | 
|  | r.q1 = t->q1; | 
|  | } | 
|  | textshow(t, r.q0, r.q1, 1); | 
|  | winsettag(t->w); | 
|  | seltext = t; | 
|  | if(e->jump) | 
|  | moveto(mousectl, addpt(frptofchar(&t->fr, t->fr.p0), Pt(4, font->height-4))); | 
|  | return w; | 
|  | } | 
|  |  | 
|  | void | 
|  | new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg) | 
|  | { | 
|  | int ndone; | 
|  | Rune *a, *f; | 
|  | int na, nf; | 
|  | Expand e; | 
|  | Runestr rs; | 
|  | Window *w; | 
|  |  | 
|  | getarg(argt, FALSE, TRUE, &a, &na); | 
|  | if(a){ | 
|  | new(et, t, nil, flag1, flag2, a, na); | 
|  | if(narg == 0) | 
|  | return; | 
|  | } | 
|  | /* loop condition: *arg is not a blank */ | 
|  | for(ndone=0; ; ndone++){ | 
|  | a = findbl(arg, narg, &na); | 
|  | if(a == arg){ | 
|  | if(ndone==0 && et->col!=nil) { | 
|  | w = coladd(et->col, nil, nil, -1); | 
|  | winsettag(w); | 
|  | xfidlog(w, "new"); | 
|  | } | 
|  | break; | 
|  | } | 
|  | nf = narg-na; | 
|  | f = runemalloc(nf); | 
|  | runemove(f, arg, nf); | 
|  | rs = dirname(et, f, nf); | 
|  | memset(&e, 0, sizeof e); | 
|  | e.name = rs.r; | 
|  | e.nname = rs.nr; | 
|  | e.bname = runetobyte(rs.r, rs.nr); | 
|  | e.jump = TRUE; | 
|  | openfile(et, &e); | 
|  | free(e.name); | 
|  | free(e.bname); | 
|  | arg = skipbl(a, na, &narg); | 
|  | } | 
|  | } |