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 if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){ 417 ; /* move to self; no-op */ 418 }else 419 editerror("move overlaps itself"); 420 } 421 422 int 423 m_cmd(Text *t, Cmd *cp) 424 { 425 Address dot, addr2; 426 427 mkaddr(&dot, t->file); 428 addr2 = cmdaddress(cp->mtaddr, dot, 0); 429 if(cp->cmdc == 'm') 430 move(t->file, addr2); 431 else 432 copy(t->file, addr2); 433 return TRUE; 434 } 435 436 int 437 p_cmd(Text *t, Cmd*) 438 { 439 return pdisplay(t->file); 440 } 441 442 int 443 s_cmd(Text *t, Cmd *cp) 444 { 445 int i, j, k, c, m, n, nrp, didsub; 446 long p1, op, delta; 447 String *buf; 448 Rangeset *rp; 449 char *err; 450 Rune *rbuf; 451 452 n = cp->num; 453 op= -1; 454 if(rxcompile(cp->re->r) == FALSE) 455 editerror("bad regexp in s command"); 456 nrp = 0; 457 rp = nil; 458 delta = 0; 459 didsub = FALSE; 460 for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){ 461 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */ 462 if(sel.r[0].q0 == op){ 463 p1++; 464 continue; 465 } 466 p1 = sel.r[0].q1+1; 467 }else 468 p1 = sel.r[0].q1; 469 op = sel.r[0].q1; 470 if(--n>0) 471 continue; 472 nrp++; 473 rp = erealloc(rp, nrp*sizeof(Rangeset)); 474 rp[nrp-1] = sel; 475 } 476 rbuf = fbufalloc(); 477 buf = allocstring(0); 478 for(m=0; m<nrp; m++){ 479 buf->n = 0; 480 buf->r[0] = L'\0'; 481 sel = rp[m]; 482 for(i = 0; i<cp->text->n; i++) 483 if((c = cp->text->r[i])=='\\' && i<cp->text->n-1){ 484 c = cp->text->r[++i]; 485 if('1'<=c && c<='9') { 486 j = c-'0'; 487 if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){ 488 err = "replacement string too long"; 489 goto Err; 490 } 491 bufread(t->file, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0); 492 for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++) 493 Straddc(buf, rbuf[k]); 494 }else 495 Straddc(buf, c); 496 }else if(c!='&') 497 Straddc(buf, c); 498 else{ 499 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){ 500 err = "right hand side too long in substitution"; 501 goto Err; 502 } 503 bufread(t->file, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0); 504 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++) 505 Straddc(buf, rbuf[k]); 506 } 507 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n); 508 delta -= sel.r[0].q1-sel.r[0].q0; 509 delta += buf->n; 510 didsub = 1; 511 if(!cp->flag) 512 break; 513 } 514 free(rp); 515 freestring(buf); 516 fbuffree(rbuf); 517 if(!didsub && nest==0) 518 editerror("no substitution"); 519 t->q0 = addr.r.q0; 520 t->q1 = addr.r.q1; 521 return TRUE; 522 523 Err: 524 free(rp); 525 freestring(buf); 526 fbuffree(rbuf); 527 editerror(err); 528 return FALSE; 529 } 530 531 int 532 u_cmd(Text *t, Cmd *cp) 533 { 534 int n, oseq, flag; 535 536 n = cp->num; 537 flag = TRUE; 538 if(n < 0){ 539 n = -n; 540 flag = FALSE; 541 } 542 oseq = -1; 543 while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){ 544 oseq = t->file->seq; 545 undo(t, nil, nil, flag, 0, nil, 0); 546 } 547 return TRUE; 548 } 549 550 int 551 w_cmd(Text *t, Cmd *cp) 552 { 553 Rune *r; 554 File *f; 555 556 f = t->file; 557 if(f->seq == seq) 558 editerror("can't write file with pending modifications"); 559 r = cmdname(f, cp->text, FALSE); 560 if(r == nil) 561 editerror("no name specified for 'w' command"); 562 putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r)); 563 /* r is freed by putfile */ 564 return TRUE; 565 } 566 567 int 568 x_cmd(Text *t, Cmd *cp) 569 { 570 if(cp->re) 571 looper(t->file, cp, cp->cmdc=='x'); 572 else 573 linelooper(t->file, cp); 574 return TRUE; 575 } 576 577 int 578 X_cmd(Text*, Cmd *cp) 579 { 580 filelooper(cp, cp->cmdc=='X'); 581 return TRUE; 582 } 583 584 void 585 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state) 586 { 587 Rune *r, *s; 588 int n; 589 Runestr dir; 590 Window *w; 591 592 r = skipbl(cr, ncr, &n); 593 if(n == 0) 594 editerror("no command specified for %c", cmd); 595 w = nil; 596 if(state == Inserting){ 597 w = t->w; 598 t->q0 = addr.r.q0; 599 t->q1 = addr.r.q1; 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 } 630 631 int 632 pipe_cmd(Text *t, Cmd *cp) 633 { 634 runpipe(t, cp->cmdc, cp->text->r, cp->text->n, Inserting); 635 return TRUE; 636 } 637 638 long 639 nlcount(Text *t, long q0, long q1) 640 { 641 long nl; 642 Rune *buf; 643 int i, nbuf; 644 645 buf = fbufalloc(); 646 nbuf = 0; 647 i = nl = 0; 648 while(q0 < q1){ 649 if(i == nbuf){ 650 nbuf = q1-q0; 651 if(nbuf > RBUFSIZE) 652 nbuf = RBUFSIZE; 653 bufread(t->file, q0, buf, nbuf); 654 i = 0; 655 } 656 if(buf[i++] == '\n') 657 nl++; 658 q0++; 659 } 660 fbuffree(buf); 661 return nl; 662 } 663 664 void 665 printposn(Text *t, int charsonly) 666 { 667 long l1, l2; 668 669 if (t != nil && t->file != nil && t->file->name != nil) 670 warning(nil, "%.*S:", t->file->nname, t->file->name); 671 if(!charsonly){ 672 l1 = 1+nlcount(t, 0, addr.r.q0); 673 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1); 674 /* check if addr ends with '\n' */ 675 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n') 676 --l2; 677 warning(nil, "%lud", l1); 678 if(l2 != l1) 679 warning(nil, ",%lud", l2); 680 warning(nil, "\n"); 681 return; 682 } 683 warning(nil, "#%d", addr.r.q0); 684 if(addr.r.q1 != addr.r.q0) 685 warning(nil, ",#%d", addr.r.q1); 686 warning(nil, "\n"); 687 } 688 689 int 690 eq_cmd(Text *t, Cmd *cp) 691 { 692 int charsonly; 693 694 switch(cp->text->n){ 695 case 0: 696 charsonly = FALSE; 697 break; 698 case 1: 699 if(cp->text->r[0] == '#'){ 700 charsonly = TRUE; 701 break; 702 } 703 default: 704 SET(charsonly); 705 editerror("newline expected"); 706 } 707 printposn(t, charsonly); 708 return TRUE; 709 } 710 711 int 712 nl_cmd(Text *t, Cmd *cp) 713 { 714 Address a; 715 File *f; 716 717 f = t->file; 718 if(cp->addr == 0){ 719 /* First put it on newline boundaries */ 720 mkaddr(&a, f); 721 addr = lineaddr(0, a, -1); 722 a = lineaddr(0, a, 1); 723 addr.r.q1 = a.r.q1; 724 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){ 725 mkaddr(&a, f); 726 addr = lineaddr(1, a, 1); 727 } 728 } 729 textshow(t, addr.r.q0, addr.r.q1, 1); 730 return TRUE; 731 } 732 733 int 734 append(File *f, Cmd *cp, long p) 735 { 736 if(cp->text->n > 0) 737 eloginsert(f, p, cp->text->r, cp->text->n); 738 f->curtext->q0 = p; 739 f->curtext->q1 = p; 740 return TRUE; 741 } 742 743 int 744 pdisplay(File *f) 745 { 746 long p1, p2; 747 int np; 748 Rune *buf; 749 750 p1 = addr.r.q0; 751 p2 = addr.r.q1; 752 if(p2 > f->nc) 753 p2 = f->nc; 754 buf = fbufalloc(); 755 while(p1 < p2){ 756 np = p2-p1; 757 if(np>RBUFSIZE-1) 758 np = RBUFSIZE-1; 759 bufread(f, p1, buf, np); 760 buf[np] = L'\0'; 761 warning(nil, "%S", buf); 762 p1 += np; 763 } 764 fbuffree(buf); 765 f->curtext->q0 = addr.r.q0; 766 f->curtext->q1 = addr.r.q1; 767 return TRUE; 768 } 769 770 void 771 pfilename(File *f) 772 { 773 int dirty; 774 Window *w; 775 776 w = f->curtext->w; 777 /* same check for dirty as in settag, but we know ncache==0 */ 778 dirty = !w->isdir && !w->isscratch && f->mod; 779 warning(nil, "%c%c%c %.*S\n", " '"[dirty], 780 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name); 781 } 782 783 void 784 loopcmd(File *f, Cmd *cp, Range *rp, long nrp) 785 { 786 long i; 787 788 for(i=0; i<nrp; i++){ 789 f->curtext->q0 = rp[i].q0; 790 f->curtext->q1 = rp[i].q1; 791 cmdexec(f->curtext, cp); 792 } 793 } 794 795 void 796 looper(File *f, Cmd *cp, int xy) 797 { 798 long p, op, nrp; 799 Range r, tr; 800 Range *rp; 801 802 r = addr.r; 803 op= xy? -1 : r.q0; 804 nest++; 805 if(rxcompile(cp->re->r) == FALSE) 806 editerror("bad regexp in %c command", cp->cmdc); 807 nrp = 0; 808 rp = nil; 809 for(p = r.q0; p<=r.q1; ){ 810 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */ 811 if(xy || op>r.q1) 812 break; 813 tr.q0 = op, tr.q1 = r.q1; 814 p = r.q1+1; /* exit next loop */ 815 }else{ 816 if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */ 817 if(sel.r[0].q0==op){ 818 p++; 819 continue; 820 } 821 p = sel.r[0].q1+1; 822 }else 823 p = sel.r[0].q1; 824 if(xy) 825 tr = sel.r[0]; 826 else 827 tr.q0 = op, tr.q1 = sel.r[0].q0; 828 } 829 op = sel.r[0].q1; 830 nrp++; 831 rp = erealloc(rp, nrp*sizeof(Range)); 832 rp[nrp-1] = tr; 833 } 834 loopcmd(f, cp->cmd, rp, nrp); 835 free(rp); 836 --nest; 837 } 838 839 void 840 linelooper(File *f, Cmd *cp) 841 { 842 long nrp, p; 843 Range r, linesel; 844 Address a, a3; 845 Range *rp; 846 847 nest++; 848 nrp = 0; 849 rp = nil; 850 r = addr.r; 851 a3.f = f; 852 a3.r.q0 = a3.r.q1 = r.q0; 853 a = lineaddr(0, a3, 1); 854 linesel = a.r; 855 for(p = r.q0; p<r.q1; p = a3.r.q1){ 856 a3.r.q0 = a3.r.q1; 857 if(p!=r.q0 || linesel.q1==p){ 858 a = lineaddr(1, a3, 1); 859 linesel = a.r; 860 } 861 if(linesel.q0 >= r.q1) 862 break; 863 if(linesel.q1 >= r.q1) 864 linesel.q1 = r.q1; 865 if(linesel.q1 > linesel.q0) 866 if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){ 867 a3.r = linesel; 868 nrp++; 869 rp = erealloc(rp, nrp*sizeof(Range)); 870 rp[nrp-1] = linesel; 871 continue; 872 } 873 break; 874 } 875 loopcmd(f, cp->cmd, rp, nrp); 876 free(rp); 877 --nest; 878 } 879 880 struct Looper 881 { 882 Cmd *cp; 883 int XY; 884 Window **w; 885 int nw; 886 } loopstruct; /* only one; X and Y can't nest */ 887 888 void 889 alllooper(Window *w, void *v) 890 { 891 Text *t; 892 struct Looper *lp; 893 Cmd *cp; 894 895 lp = v; 896 cp = lp->cp; 897 // if(w->isscratch || w->isdir) 898 // return; 899 t = &w->body; 900 /* only use this window if it's the current window for the file */ 901 if(t->file->curtext != t) 902 return; 903 // if(w->nopen[QWevent] > 0) 904 // return; 905 /* no auto-execute on files without names */ 906 if(cp->re==nil && t->file->nname==0) 907 return; 908 if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){ 909 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*)); 910 lp->w[lp->nw++] = w; 911 } 912 } 913 914 void 915 alllocker(Window *w, void *v) 916 { 917 if(v) 918 incref(w); 919 else 920 winclose(w); 921 } 922 923 void 924 filelooper(Cmd *cp, int XY) 925 { 926 int i; 927 928 if(Glooping++) 929 editerror("can't nest %c command", "YX"[XY]); 930 nest++; 931 932 loopstruct.cp = cp; 933 loopstruct.XY = XY; 934 if(loopstruct.w) /* error'ed out last time */ 935 free(loopstruct.w); 936 loopstruct.w = nil; 937 loopstruct.nw = 0; 938 allwindows(alllooper, &loopstruct); 939 /* 940 * add a ref to all windows to keep safe windows accessed by X 941 * that would not otherwise have a ref to hold them up during 942 * the shenanigans. note this with globalincref so that any 943 * newly created windows start with an extra reference. 944 */ 945 allwindows(alllocker, (void*)1); 946 globalincref = 1; 947 for(i=0; i<loopstruct.nw; i++) 948 cmdexec(&loopstruct.w[i]->body, cp->cmd); 949 allwindows(alllocker, (void*)0); 950 globalincref = 0; 951 free(loopstruct.w); 952 loopstruct.w = nil; 953 954 --Glooping; 955 --nest; 956 } 957 958 void 959 nextmatch(File *f, String *r, long p, int sign) 960 { 961 if(rxcompile(r->r) == FALSE) 962 editerror("bad regexp in command address"); 963 if(sign >= 0){ 964 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel)) 965 editerror("no match for regexp"); 966 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){ 967 if(++p>f->nc) 968 p = 0; 969 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel)) 970 editerror("address"); 971 } 972 }else{ 973 if(!rxbexecute(f->curtext, p, &sel)) 974 editerror("no match for regexp"); 975 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){ 976 if(--p<0) 977 p = f->nc; 978 if(!rxbexecute(f->curtext, p, &sel)) 979 editerror("address"); 980 } 981 } 982 } 983 984 File *matchfile(String*); 985 Address charaddr(long, Address, int); 986 Address lineaddr(long, Address, int); 987 988 Address 989 cmdaddress(Addr *ap, Address a, int sign) 990 { 991 File *f = a.f; 992 Address a1, a2; 993 994 do{ 995 switch(ap->type){ 996 case 'l': 997 case '#': 998 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign); 999 break; 1000 1001 case '.': 1002 mkaddr(&a, f); 1003 break; 1004 1005 case '$': 1006 a.r.q0 = a.r.q1 = f->nc; 1007 break; 1008 1009 case '\'': 1010 editerror("can't handle '"); 1011 // a.r = f->mark; 1012 break; 1013 1014 case '?': 1015 sign = -sign; 1016 if(sign == 0) 1017 sign = -1; 1018 /* fall through */ 1019 case '/': 1020 nextmatch(f, ap->re, sign>=0? a.r.q1 : a.r.q0, sign); 1021 a.r = sel.r[0]; 1022 break; 1023 1024 case '"': 1025 f = matchfile(ap->re); 1026 mkaddr(&a, f); 1027 break; 1028 1029 case '*': 1030 a.r.q0 = 0, a.r.q1 = f->nc; 1031 return a; 1032 1033 case ',': 1034 case ';': 1035 if(ap->left) 1036 a1 = cmdaddress(ap->left, a, 0); 1037 else 1038 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0; 1039 if(ap->type == ';'){ 1040 f = a1.f; 1041 a = a1; 1042 f->curtext->q0 = a1.r.q0; 1043 f->curtext->q1 = a1.r.q1; 1044 } 1045 if(ap->next) 1046 a2 = cmdaddress(ap->next, a, 0); 1047 else 1048 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->nc; 1049 if(a1.f != a2.f) 1050 editerror("addresses in different files"); 1051 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1; 1052 if(a.r.q1 < a.r.q0) 1053 editerror("addresses out of order"); 1054 return a; 1055 1056 case '+': 1057 case '-': 1058 sign = 1; 1059 if(ap->type == '-') 1060 sign = -1; 1061 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-') 1062 a = lineaddr(1L, a, sign); 1063 break; 1064 default: 1065 error("cmdaddress"); 1066 return a; 1067 } 1068 }while(ap = ap->next); /* assign = */ 1069 return a; 1070 } 1071 1072 struct Tofile{ 1073 File *f; 1074 String *r; 1075 }; 1076 1077 void 1078 alltofile(Window *w, void *v) 1079 { 1080 Text *t; 1081 struct Tofile *tp; 1082 1083 tp = v; 1084 if(tp->f != nil) 1085 return; 1086 if(w->isscratch || w->isdir) 1087 return; 1088 t = &w->body; 1089 /* only use this window if it's the current window for the file */ 1090 if(t->file->curtext != t) 1091 return; 1092 // if(w->nopen[QWevent] > 0) 1093 // return; 1094 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname)) 1095 tp->f = t->file; 1096 } 1097 1098 File* 1099 tofile(String *r) 1100 { 1101 struct Tofile t; 1102 String rr; 1103 1104 rr.r = skipbl(r->r, r->n, &rr.n); 1105 t.f = nil; 1106 t.r = &rr; 1107 allwindows(alltofile, &t); 1108 if(t.f == nil) 1109 editerror("no such file\"%S\"", rr.r); 1110 return t.f; 1111 } 1112 1113 void 1114 allmatchfile(Window *w, void *v) 1115 { 1116 struct Tofile *tp; 1117 Text *t; 1118 1119 tp = v; 1120 if(w->isscratch || w->isdir) 1121 return; 1122 t = &w->body; 1123 /* only use this window if it's the current window for the file */ 1124 if(t->file->curtext != t) 1125 return; 1126 // if(w->nopen[QWevent] > 0) 1127 // return; 1128 if(filematch(w->body.file, tp->r)){ 1129 if(tp->f != nil) 1130 editerror("too many files match \"%S\"", tp->r->r); 1131 tp->f = w->body.file; 1132 } 1133 } 1134 1135 File* 1136 matchfile(String *r) 1137 { 1138 struct Tofile tf; 1139 1140 tf.f = nil; 1141 tf.r = r; 1142 allwindows(allmatchfile, &tf); 1143 1144 if(tf.f == nil) 1145 editerror("no file matches \"%S\"", r->r); 1146 return tf.f; 1147 } 1148 1149 int 1150 filematch(File *f, String *r) 1151 { 1152 char *buf; 1153 Rune *rbuf; 1154 Window *w; 1155 int match, i, dirty; 1156 Rangeset s; 1157 1158 /* compile expr first so if we get an error, we haven't allocated anything */ 1159 if(rxcompile(r->r) == FALSE) 1160 editerror("bad regexp in file match"); 1161 buf = fbufalloc(); 1162 w = f->curtext->w; 1163 /* same check for dirty as in settag, but we know ncache==0 */ 1164 dirty = !w->isdir && !w->isscratch && f->mod; 1165 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty], 1166 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name); 1167 rbuf = bytetorune(buf, &i); 1168 fbuffree(buf); 1169 match = rxexecute(nil, rbuf, 0, i, &s); 1170 free(rbuf); 1171 return match; 1172 } 1173 1174 Address 1175 charaddr(long l, Address addr, int sign) 1176 { 1177 if(sign == 0) 1178 addr.r.q0 = addr.r.q1 = l; 1179 else if(sign < 0) 1180 addr.r.q1 = addr.r.q0 -= l; 1181 else if(sign > 0) 1182 addr.r.q0 = addr.r.q1 += l; 1183 if(addr.r.q0<0 || addr.r.q1>addr.f->nc) 1184 editerror("address out of range"); 1185 return addr; 1186 } 1187 1188 Address 1189 lineaddr(long l, Address addr, int sign) 1190 { 1191 int n; 1192 int c; 1193 File *f = addr.f; 1194 Address a; 1195 long p; 1196 1197 a.f = f; 1198 if(sign >= 0){ 1199 if(l == 0){ 1200 if(sign==0 || addr.r.q1==0){ 1201 a.r.q0 = a.r.q1 = 0; 1202 return a; 1203 } 1204 a.r.q0 = addr.r.q1; 1205 p = addr.r.q1-1; 1206 }else{ 1207 if(sign==0 || addr.r.q1==0){ 1208 p = 0; 1209 n = 1; 1210 }else{ 1211 p = addr.r.q1-1; 1212 n = textreadc(f->curtext, p++)=='\n'; 1213 } 1214 while(n < l){ 1215 if(p >= f->nc) 1216 editerror("address out of range"); 1217 if(textreadc(f->curtext, p++) == '\n') 1218 n++; 1219 } 1220 a.r.q0 = p; 1221 } 1222 while(p < f->nc && textreadc(f->curtext, p++)!='\n') 1223 ; 1224 a.r.q1 = p; 1225 }else{ 1226 p = addr.r.q0; 1227 if(l == 0) 1228 a.r.q1 = addr.r.q0; 1229 else{ 1230 for(n = 0; n<l; ){ /* always runs once */ 1231 if(p == 0){ 1232 if(++n != l) 1233 editerror("address out of range"); 1234 }else{ 1235 c = textreadc(f->curtext, p-1); 1236 if(c != '\n' || ++n != l) 1237 p--; 1238 } 1239 } 1240 a.r.q1 = p; 1241 if(p > 0) 1242 p--; 1243 } 1244 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */ 1245 p--; 1246 a.r.q0 = p; 1247 } 1248 return a; 1249 } 1250 1251 struct Filecheck 1252 { 1253 File *f; 1254 Rune *r; 1255 int nr; 1256 }; 1257 1258 void 1259 allfilecheck(Window *w, void *v) 1260 { 1261 struct Filecheck *fp; 1262 File *f; 1263 1264 fp = v; 1265 f = w->body.file; 1266 if(w->body.file == fp->f) 1267 return; 1268 if(runeeq(fp->r, fp->nr, f->name, f->nname)) 1269 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r); 1270 } 1271 1272 Rune* 1273 cmdname(File *f, String *str, int set) 1274 { 1275 Rune *r, *s; 1276 int n; 1277 struct Filecheck fc; 1278 Runestr newname; 1279 1280 r = nil; 1281 n = str->n; 1282 s = str->r; 1283 if(n == 0){ 1284 /* no name; use existing */ 1285 if(f->nname == 0) 1286 return nil; 1287 r = runemalloc(f->nname+1); 1288 runemove(r, f->name, f->nname); 1289 return r; 1290 } 1291 s = skipbl(s, n, &n); 1292 if(n == 0) 1293 goto Return; 1294 1295 if(s[0] == '/'){ 1296 r = runemalloc(n+1); 1297 runemove(r, s, n); 1298 }else{ 1299 newname = dirname(f->curtext, runestrdup(s), n); 1300 n = newname.nr; 1301 r = runemalloc(n+1); /* NUL terminate */ 1302 runemove(r, newname.r, n); 1303 free(newname.r); 1304 } 1305 fc.f = f; 1306 fc.r = r; 1307 fc.nr = n; 1308 allwindows(allfilecheck, &fc); 1309 if(f->nname == 0) 1310 set = TRUE; 1311 1312 Return: 1313 if(set && !runeeq(r, n, f->name, f->nname)){ 1314 filemark(f); 1315 f->mod = TRUE; 1316 f->curtext->w->dirty = TRUE; 1317 winsetname(f->curtext->w, r, n); 1318 } 1319 return r; 1320 } 1321