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