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