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