1 2 3 /* This version of microEmacs is based on the public domain C 4 * version written by Dave G. Conroy. 5 * The D programming language version is written by Walter Bright. 6 * http://www.digitalmars.com/d/ 7 * This program is in the public domain. 8 */ 9 10 /* 11 * The functions in this file implement commands that search in the forward 12 * and backward directions. There are no special characters in the search 13 * strings. Probably should have a regular expression search, or something 14 * like that. 15 * 16 * REVISION HISTORY: 17 * 18 * ? Steve Wilhite, 1-Dec-85 19 * - massive cleanup on code. 20 */ 21 22 module search; 23 24 import core.stdc.stdio; 25 import core.stdc.ctype; 26 import std..string; 27 import std.ascii; 28 import std.uni; 29 30 import ed; 31 import line; 32 import display; 33 import window; 34 import main; 35 import buffer; 36 import basic; 37 import terminal; 38 39 enum CASESENSITIVE = true; /* TRUE means case sensitive */ 40 enum WORDPREFIX = 'W' & 0x1F; // prefix to trigger word search 41 42 int Dnoask_search; 43 44 /* Returns: 45 * true if word character 46 */ 47 bool isWordChar(char c) 48 { 49 return isalnum(c) || c == '_'; 50 } 51 52 /* 53 * Search forward. Get a search string from the user, and search, beginning at 54 * ".", for the string. If found, reset the "." to be just after the match 55 * string, and [perhaps] repaint the display. Bound to "C-S". 56 */ 57 int forwsearch(bool f, int n) 58 { 59 if (winSearchPat) 60 { 61 winSearchPat.w_flag |= WFHARD; 62 winSearchPat = null; 63 } 64 65 int s; 66 if ((s = readpattern("Search: ",pat)) != TRUE) 67 return (s); 68 69 static bool notFound() 70 { 71 mlwrite("Not found"); 72 return FALSE; 73 } 74 75 bool word; 76 string pattern = pat; // pattern to match 77 if (pattern.length == 0) 78 return notFound(); 79 word = pattern[0] == WORDPREFIX; // ^D means only match words 80 if (word) 81 { 82 pattern = pattern[1 .. $]; 83 if (pattern.length == 0) 84 return notFound(); 85 } 86 87 char p0 = pattern[0]; // first char to match 88 89 LINE* clp = curwp.w_dotp; /* get pointer to current line */ 90 int cbo = curwp.w_doto; /* and offset into that line */ 91 92 char lastc; 93 94 again: 95 while (!empty(clp, cbo)) /* while not end of buffer */ 96 { 97 int c = front(clp, cbo); 98 popFront(clp, cbo); 99 if (!eq(c, p0)) 100 { 101 lastc = cast(char)c; 102 continue; 103 } 104 if (word && lastc != lastc.init && isWordChar(lastc)) 105 continue; 106 lastc = cast(char)c; 107 108 { 109 LINE* tlp = clp; 110 int tbo = cbo; /* remember where start of pattern */ 111 112 foreach (pc; pattern[1 .. $]) 113 { 114 if (empty(tlp, tbo)) 115 continue again; 116 c = front(tlp, tbo); 117 popFront(tlp, tbo); 118 119 lastc = cast(char)c; 120 121 if (!eq(c, pc)) 122 continue again; 123 } 124 125 if (word && !empty(tlp, tbo) && isWordChar(cast(char)front(tlp, tbo))) 126 { 127 continue again; 128 } 129 130 /* We've found it. It starts at clp,cbo and ends before tlp,tbo */ 131 curwp.w_dotp = tlp; 132 curwp.w_doto = tbo; 133 curwp.w_flag |= WFMOVE; 134 curwp.w_flag |= WFHARD; 135 winSearchPat = curwp; 136 return (TRUE); 137 } 138 } 139 140 return notFound(); 141 } 142 143 /* 144 * Reverse search. Get a search string from the user, and search, starting at 145 * "." and proceeding toward the front of the buffer. If found "." is left 146 * pointing at the first character of the pattern [the last character that was 147 * matched]. Bound to "C-R". 148 */ 149 int backsearch(bool f, int n) 150 { 151 if (winSearchPat) 152 { 153 winSearchPat.w_flag |= WFHARD; 154 winSearchPat = null; 155 } 156 157 int s; 158 if ((s = readpattern("Reverse search: ",pat)) != TRUE) 159 return (s); 160 161 static bool notFound() 162 { 163 mlwrite("Not found"); 164 return FALSE; 165 } 166 167 bool word; 168 string pattern = pat; // pattern to match 169 if (pattern.length == 0) 170 return notFound(); 171 word = pattern[0] == WORDPREFIX; // ^D means only match words 172 if (word) 173 { 174 pattern = pattern[1 .. $]; 175 if (pattern.length == 0) 176 return notFound(); 177 } 178 179 immutable(char)* epp = &pattern[$ - 1]; 180 181 LINE* clp = curwp.w_dotp; 182 int cbo = curwp.w_doto; 183 184 again: 185 for (;;) 186 { 187 if (atFront(clp, cbo)) 188 return notFound(); 189 190 if (word && !empty(clp, cbo) && isWordChar(cast(char)front(clp, cbo))) 191 { 192 popBack(clp, cbo); 193 continue; 194 } 195 196 popBack(clp, cbo); 197 int c = front(clp, cbo); 198 199 if (eq(c, *epp)) 200 { 201 LINE* tlp = clp; 202 int tbo = cbo; 203 auto pp = epp; 204 205 while (pp != &pattern[0]) 206 { 207 if (atFront(tlp, tbo)) 208 continue again; 209 popBack(tlp, tbo); 210 c = front(tlp, tbo); 211 212 if (!eq(c, *--pp)) 213 continue again; 214 } 215 216 if (word && !atFront(tlp, tbo) && isWordChar(cast(char)peekBack(tlp, tbo))) 217 { 218 continue again; 219 } 220 221 curwp.w_dotp = tlp; 222 curwp.w_doto = tbo; 223 curwp.w_flag |= WFMOVE; 224 curwp.w_flag |= WFHARD; 225 winSearchPat = curwp; 226 return (TRUE); 227 } 228 } 229 assert(0); 230 } 231 232 /* 233 * Compare two characters. The "bc" comes from the buffer. It has it's case 234 * folded out. The "pc" is from the pattern. 235 */ 236 237 bool eq(int bc, int pc) 238 { 239 if (CASESENSITIVE) 240 return bc == pc; 241 242 if (bc>='a' && bc<='z') 243 bc -= 0x20; 244 245 if (pc>='a' && pc<='z') 246 pc -= 0x20; 247 248 return (bc == pc); 249 } 250 251 /********************************* 252 * Replace occurrences of pat with withpat. 253 */ 254 255 int replacestring(bool f, int n) 256 { 257 return replace(FALSE); 258 } 259 260 /******************************** 261 */ 262 263 int queryreplacestring(bool f, int n) 264 { 265 return replace(TRUE); 266 } 267 268 /************************* 269 * Do the replacements. 270 * Input: 271 * query if TRUE then it's a query-search-replace 272 */ 273 274 private int replace(bool query) 275 { 276 LINE * clp; 277 int cbo; 278 LINE * tlp; 279 int tbo; 280 int c; 281 int s; 282 int numreplacements; 283 int retval; 284 LINE* dotpsave; 285 int dotosave; 286 string withpat; 287 int stop; 288 289 if (winSearchPat) 290 { 291 winSearchPat.w_flag |= WFHARD; 292 winSearchPat = null; 293 } 294 295 if ((s = readpattern("Replace: ", pat)) != TRUE) 296 return (s); /* must have search pattern */ 297 298 bool word; 299 string pattern = pat; // pattern to match 300 if (pattern.length == 0) 301 return FALSE; 302 word = pattern[0] == WORDPREFIX; // ^D means only match words 303 if (word) 304 { 305 pattern = pattern[1 .. $]; 306 if (pattern.length == 0) 307 return FALSE; 308 } 309 310 readpattern ("With: ", withpat); /* replacement pattern can be null */ 311 312 stop = FALSE; 313 retval = TRUE; 314 numreplacements = 0; 315 dotpsave = curwp.w_dotp; 316 dotosave = curwp.w_doto; /* save original position */ 317 clp = curwp.w_dotp; /* get pointer to current line */ 318 cbo = curwp.w_doto; /* and offset into that line */ 319 320 curwp.w_flag |= WFHARD; // repaint window with matches 321 winSearchPat = curwp; 322 update(); 323 324 auto p0 = pattern[0]; 325 326 char lastc; 327 328 again: 329 while (!empty(clp, cbo)) /* while not end of buffer */ 330 { 331 /* Compute c, the character at the current position */ 332 c = front(clp, cbo); 333 popFront(clp, cbo); 334 335 if (!eq(c, p0)) 336 { 337 lastc = cast(char)c; 338 continue; 339 } 340 if (word && lastc != lastc.init && isWordChar(lastc)) 341 continue; 342 lastc = cast(char)c; 343 344 { 345 tlp = clp; 346 tbo = cbo; /* remember where start of pattern */ 347 int i = 1; 348 349 foreach (pc; pattern[1 .. $]) 350 { 351 if (empty(tlp, tbo)) 352 continue again; 353 c = front(tlp, tbo); 354 popFront(tlp, tbo); 355 356 lastc = cast(char)c; 357 358 if (!eq(c, pc)) 359 continue again; 360 i++; 361 } 362 363 if (word && !empty(tlp, tbo) && isWordChar(cast(char)front(tlp, tbo))) 364 { 365 continue again; 366 } 367 368 /* We've found it. It starts before clp,cbo and ends */ 369 /* before tlp,tbo */ 370 371 /* If query, get user input about this */ 372 if (query) 373 { 374 curwp.w_dotp= clp; 375 curwp.w_doto = cbo; 376 backchar(FALSE,1); 377 mlwrite("' ' change 'n' continue '!' change rest '.' change and stop ^G abort"); 378 curwp.w_flag |= WFMOVE; 379 tryagain: 380 update(); 381 switch (getkey()) 382 { 383 case 'n': /* don't change, but continue */ 384 continue again; 385 386 /*case 'R':*/ /* enter recursive edit */ 387 case '!': /* change rest w/o asking */ 388 query = FALSE; 389 goto case; 390 391 case ' ': /* change and continue to next */ 392 break; 393 case '.': /* change and stop */ 394 stop = TRUE; 395 break; 396 case 'G' & 0x1F: /* abort */ 397 goto abortreplace; 398 default: /* illegal command */ 399 term.t_beep(); 400 goto tryagain; 401 } 402 } 403 404 /* Delete the pattern by setting the current position to */ 405 /* the start of the pattern, and deleting 'n' characters */ 406 curwp.w_flag |= WFHARD; 407 curwp.w_dotp= clp; 408 curwp.w_doto = cbo; 409 if (backchar(FALSE,1) == FALSE || 410 line_delete(i,FALSE) == FALSE) 411 goto L1; 412 413 /* 'Yank' the replacement pattern back in at dot (also */ 414 /* moving cursor past end of replacement pattern to prevent */ 415 /* recursive replaces). */ 416 foreach (wc; withpat) 417 if (line_insert(1, wc) == FALSE) 418 { 419 goto L1; 420 } 421 422 /* Take care of case where line_insert() reallocated the line */ 423 if (dotpsave == clp) 424 dotpsave = curwp.w_dotp; 425 clp = curwp.w_dotp; 426 cbo = curwp.w_doto; /* continue from end of with text */ 427 numreplacements++; 428 if (stop) 429 break; 430 } 431 } 432 433 abortreplace: 434 curwp.w_dotp = dotpsave; 435 curwp.w_doto = dotosave; /* back to original position */ 436 curwp.w_flag |= WFMOVE; 437 mlwrite("%d replacements done", numreplacements); 438 return retval; 439 440 L1: 441 if (dotpsave == clp) 442 dotpsave = curwp.w_dotp; 443 retval = FALSE; 444 goto abortreplace; 445 } 446 447 /* 448 * Read a pattern. Stash it in the external variable "pat". The "pat" is not 449 * updated if the user types in an empty line. If the user typed an empty line, 450 * and there is no old pattern, it is an error. Display the old pattern, in the 451 * style of Jeff Lomicka. There is some do-it-yourself control expansion. 452 */ 453 private int readpattern(string prompt, ref string pat) 454 { 455 if( Dnoask_search ) 456 return( pat.length != 0 ); 457 auto tpat = pat; 458 auto s = mlreply(prompt, pat, tpat); 459 if (s == TRUE) /* Specified */ 460 pat = tpat; 461 else if (s == FALSE && pat.length != 0) /* CR, but old one */ 462 s = TRUE; 463 464 return (s); 465 } 466 467 /********************************* 468 * Examine line at '.'. 469 * Returns: 470 * HASH_xxx 471 * 0 anything else 472 */ 473 474 enum 475 { 476 HASH_IF = 1, 477 HASH_ELIF = 2, 478 HASH_ELSE = 3, 479 HASH_ENDIF = 4, 480 } 481 482 static int ifhash(LINE* clp) 483 { 484 int len; 485 int i; 486 static string[] hash = ["if","elif","else","endif"]; 487 488 len = cast(int)clp.l_text.length; 489 if (len < 3 || lgetc(clp,0) != '#') 490 goto ret0; 491 for (i = 1; ; i++) 492 { 493 if (i >= len) 494 goto ret0; 495 if (!isSpace(clp.l_text[i])) 496 break; 497 } 498 for (int h = 0; h < hash.length; h++) 499 if (len - i >= hash[h].length && 500 clp.l_text[i .. i + hash[h].length] == hash[h]) 501 return h + 1; 502 ret0: 503 return 0; 504 } 505 506 /********************************* 507 * Search for the next occurence of the character at '.'. 508 * If character is a (){}[]<>, search for matching bracket. 509 * If '.' is on #if, #elif, or #else search for next #elif, #else or #endif. 510 * If '.' is on #endif, search backwards for corresponding #if. 511 */ 512 513 int search_paren(bool f, int n) 514 { 515 LINE* clp; 516 int cbo; 517 int len; 518 int i; 519 char chinc,chdec,ch; 520 int count; 521 int forward; 522 int h; 523 static char[2][] bracket = [['(',')'],['<','>'],['[',']'],['{','}']]; 524 525 clp = curwp.w_dotp; /* get pointer to current line */ 526 cbo = curwp.w_doto; /* and offset into that line */ 527 count = 0; 528 529 len = llength(clp); 530 if (cbo >= len) 531 chinc = '\n'; 532 else 533 chinc = lgetc(clp,cbo); 534 535 if (cbo == 0 && (h = ifhash(clp)) != 0) 536 { forward = h != HASH_ENDIF; 537 } 538 else 539 { 540 forward = TRUE; /* forward */ 541 h = 0; 542 chdec = chinc; 543 for (i = 0; i < bracket.length; i++) 544 if (bracket[i][0] == chinc) 545 { chdec = bracket[i][1]; 546 break; 547 } 548 for (i = 0; i < bracket.length; i++) 549 if (bracket[i][1] == chinc) 550 { chdec = bracket[i][0]; 551 forward = FALSE; /* search backwards */ 552 break; 553 } 554 } 555 556 while (1) /* while not end of buffer */ 557 { 558 if (forward) 559 { 560 if (h || cbo >= len) 561 { 562 clp = lforw(clp); 563 if (clp == curbp.b_linep) /* if end of buffer */ 564 break; 565 len = llength(clp); 566 cbo = 0; 567 } 568 else 569 cbo++; 570 } 571 else /* backward */ 572 { 573 if (h || cbo == 0) 574 { 575 clp = lback(clp); 576 if (clp == curbp.b_linep) 577 break; 578 len = llength(clp); 579 cbo = len; 580 } 581 else 582 --cbo; 583 } 584 585 if (h) 586 { int h2; 587 588 cbo = 0; 589 h2 = ifhash(clp); 590 if (h2) 591 { if (h == HASH_ENDIF) 592 { 593 if (h2 == HASH_ENDIF) 594 count++; 595 else if (h2 == HASH_IF) 596 { if (count-- == 0) 597 goto found; 598 } 599 } 600 else 601 { if (h2 == HASH_IF) 602 count++; 603 else 604 { if (count == 0) 605 goto found; 606 if (h2 == HASH_ENDIF) 607 count--; 608 } 609 } 610 } 611 } 612 else 613 { 614 ch = (cbo < len) ? lgetc(clp,cbo) : '\n'; 615 if (eq(ch,chdec)) 616 { if (count-- == 0) 617 { 618 /* We've found it */ 619 found: 620 curwp.w_dotp = clp; 621 curwp.w_doto = cbo; 622 curwp.w_flag |= WFMOVE; 623 return (TRUE); 624 } 625 } 626 else if (eq(ch,chinc)) 627 count++; 628 } 629 } 630 mlwrite("Not found"); 631 return (FALSE); 632 } 633 634 /**************************************************** 635 * Determine if index is in a URL or not. 636 */ 637 638 bool inSearch(const(char)[] s, size_t index) 639 { 640 if (curwp != winSearchPat || pat.length == 0) 641 return false; 642 643 string pattern = pat; 644 bool word = pattern[0] == WORDPREFIX; 645 if (word) 646 { 647 pattern = pattern[1 .. $]; 648 if (pattern.length == 0) 649 return false; 650 } 651 652 char p0 = pattern[0]; // first char to match 653 654 char lastc; 655 656 again: 657 for (int i = 0; i <= s.length;) 658 { 659 char front(int i) { return i < s.length ? s[i] : '\n'; } 660 661 if (index < i) 662 break; 663 664 char c = front(i); 665 ++i; 666 667 if (!eq(c, p0)) 668 { 669 lastc = cast(char)c; 670 continue; 671 } 672 if (word && lastc != lastc.init && isWordChar(lastc)) 673 continue; 674 lastc = c; 675 676 { 677 foreach (pc; pattern[1 .. $]) 678 { 679 if (i > s.length) 680 break again; 681 682 char c2 = front(i); 683 ++i; 684 685 lastc = c2; 686 687 if (!eq(c2, pc)) 688 continue again; 689 } 690 691 if (word && i <= s.length && isWordChar(front(i))) 692 { 693 continue again; 694 } 695 696 if (index < i) 697 return true; 698 } 699 } 700 701 return false; 702 } 703