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