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