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