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