ecmd.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 = ∅ 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 }