1/* $Id: search.c,v 1.197.2.1 2007/04/19 03:15:04 dolorous Exp $ */ 2/************************************************************************** 3 * search.c * 4 * * 5 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Chris Allegretta * 6 * Copyright (C) 2005, 2006, 2007 David Lawrence Ramsey * 7 * This program is free software; you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published by * 9 * the Free Software Foundation; either version 2, or (at your option) * 10 * any later version. * 11 * * 12 * This program is distributed in the hope that it will be useful, but * 13 * WITHOUT ANY WARRANTY; without even the implied warranty of * 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 15 * General Public License for more details. * 16 * * 17 * You should have received a copy of the GNU General Public License * 18 * along with this program; if not, write to the Free Software * 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 20 * 02110-1301, USA. * 21 * * 22 **************************************************************************/ 23 24#include "proto.h" 25 26#include <string.h> 27#include <stdio.h> 28#include <unistd.h> 29#include <ctype.h> 30#include <errno.h> 31 32static bool search_last_line = FALSE; 33 /* Have we gone past the last line while searching? */ 34#if !defined(NANO_TINY) && defined(ENABLE_NANORC) 35static bool history_changed = FALSE; 36 /* Have any of the history lists changed? */ 37#endif 38#ifdef HAVE_REGEX_H 39static bool regexp_compiled = FALSE; 40 /* Have we compiled any regular expressions? */ 41 42/* Compile the regular expression regexp to see if it's valid. Return 43 * TRUE if it is, or FALSE otherwise. */ 44bool regexp_init(const char *regexp) 45{ 46 int rc; 47 48 assert(!regexp_compiled); 49 50 rc = regcomp(&search_regexp, regexp, REG_EXTENDED 51#ifndef NANO_TINY 52 | (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE) 53#endif 54 ); 55 56 if (rc != 0) { 57 size_t len = regerror(rc, &search_regexp, NULL, 0); 58 char *str = charalloc(len); 59 60 regerror(rc, &search_regexp, str, len); 61 statusbar(_("Bad regex \"%s\": %s"), regexp, str); 62 free(str); 63 64 return FALSE; 65 } 66 67 regexp_compiled = TRUE; 68 69 return TRUE; 70} 71 72/* Decompile the compiled regular expression we used in the last 73 * search, if any. */ 74void regexp_cleanup(void) 75{ 76 if (regexp_compiled) { 77 regexp_compiled = FALSE; 78 regfree(&search_regexp); 79 } 80} 81#endif 82 83/* Indicate on the statusbar that the string at str was not found by the 84 * last search. */ 85void not_found_msg(const char *str) 86{ 87 char *disp; 88 int numchars; 89 90 assert(str != NULL); 91 92 disp = display_string(str, 0, (COLS / 2) + 1, FALSE); 93 numchars = actual_x(disp, mbstrnlen(disp, COLS / 2)); 94 95 statusbar(_("\"%.*s%s\" not found"), numchars, disp, 96 (disp[numchars] == '\0') ? "" : "..."); 97 98 free(disp); 99} 100 101/* Abort the current search or replace. Clean up by displaying the main 102 * shortcut list, updating the screen if the mark was on before, and 103 * decompiling the compiled regular expression we used in the last 104 * search, if any. */ 105void search_replace_abort(void) 106{ 107 display_main_list(); 108#ifndef NANO_TINY 109 if (openfile->mark_set) 110 edit_refresh(); 111#endif 112#ifdef HAVE_REGEX_H 113 regexp_cleanup(); 114#endif 115} 116 117/* Initialize the global search and replace strings. */ 118void search_init_globals(void) 119{ 120 if (last_search == NULL) 121 last_search = mallocstrcpy(NULL, ""); 122 if (last_replace == NULL) 123 last_replace = mallocstrcpy(NULL, ""); 124} 125 126/* Set up the system variables for a search or replace. If use_answer 127 * is TRUE, only set backupstring to answer. Return -2 to run the 128 * opposite program (search -> replace, replace -> search), return -1 if 129 * the search should be canceled (due to Cancel, a blank search string, 130 * Go to Line, or a failed regcomp()), return 0 on success, and return 1 131 * on rerun calling program. 132 * 133 * replacing is TRUE if we call from do_replace(), and FALSE if called 134 * from do_search(). */ 135int search_init(bool replacing, bool use_answer) 136{ 137 int i = 0; 138 char *buf; 139 static char *backupstring = NULL; 140 /* The search string we'll be using. */ 141 142 /* If backupstring doesn't exist, initialize it to "". */ 143 if (backupstring == NULL) 144 backupstring = mallocstrcpy(NULL, ""); 145 146 /* If use_answer is TRUE, set backupstring to answer and get out. */ 147 if (use_answer) { 148 backupstring = mallocstrcpy(backupstring, answer); 149 return 0; 150 } 151 152 /* We display the search prompt below. If the user types a partial 153 * search string and then Replace or a toggle, we will return to 154 * do_search() or do_replace() and be called again. In that case, 155 * we should put the same search string back up. */ 156 157 search_init_globals(); 158 159 if (last_search[0] != '\0') { 160 char *disp = display_string(last_search, 0, COLS / 3, FALSE); 161 162 buf = charalloc(strlen(disp) + 7); 163 /* We use (COLS / 3) here because we need to see more on the 164 * line. */ 165 sprintf(buf, " [%s%s]", disp, 166 (strlenpt(last_search) > COLS / 3) ? "..." : ""); 167 free(disp); 168 } else 169 buf = mallocstrcpy(NULL, ""); 170 171 /* This is now one simple call. It just does a lot. */ 172 i = do_prompt(FALSE, 173#ifndef DISABLE_TABCOMP 174 TRUE, 175#endif 176 replacing ? replace_list : whereis_list, backupstring, 177#ifndef NANO_TINY 178 &search_history, 179#endif 180 edit_refresh, "%s%s%s%s%s%s", _("Search"), 181#ifndef NANO_TINY 182 /* TRANSLATORS: This string is just a modifier for the search 183 * prompt; no grammar is implied. */ 184 ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : 185#endif 186 "", 187#ifdef HAVE_REGEX_H 188 /* TRANSLATORS: This string is just a modifier for the search 189 * prompt; no grammar is implied. */ 190 ISSET(USE_REGEXP) ? _(" [Regexp]") : 191#endif 192 "", 193#ifndef NANO_TINY 194 /* TRANSLATORS: This string is just a modifier for the search 195 * prompt; no grammar is implied. */ 196 ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") : 197#endif 198 "", replacing ? 199#ifndef NANO_TINY 200 openfile->mark_set ? _(" (to replace) in selection") : 201#endif 202 _(" (to replace)") : "", buf); 203 204 /* Release buf now that we don't need it anymore. */ 205 free(buf); 206 207 free(backupstring); 208 backupstring = NULL; 209 210 /* Cancel any search, or just return with no previous search. */ 211 if (i == -1 || (i < 0 && last_search[0] == '\0') || 212 (!replacing && i == 0 && answer[0] == '\0')) { 213 statusbar(_("Cancelled")); 214 return -1; 215 } else { 216 switch (i) { 217 case -2: /* It's an empty string. */ 218 case 0: /* It's a new string. */ 219#ifdef HAVE_REGEX_H 220 /* Use last_search if answer is an empty string, or 221 * answer if it isn't. */ 222 if (ISSET(USE_REGEXP) && !regexp_init((i == -2) ? 223 last_search : answer)) 224 return -1; 225#endif 226 break; 227#ifndef NANO_TINY 228 case TOGGLE_CASE_KEY: 229 TOGGLE(CASE_SENSITIVE); 230 backupstring = mallocstrcpy(backupstring, answer); 231 return 1; 232 case TOGGLE_BACKWARDS_KEY: 233 TOGGLE(BACKWARDS_SEARCH); 234 backupstring = mallocstrcpy(backupstring, answer); 235 return 1; 236#endif 237#ifdef HAVE_REGEX_H 238 case NANO_REGEXP_KEY: 239 TOGGLE(USE_REGEXP); 240 backupstring = mallocstrcpy(backupstring, answer); 241 return 1; 242#endif 243 case NANO_TOOTHERSEARCH_KEY: 244 backupstring = mallocstrcpy(backupstring, answer); 245 return -2; /* Call the opposite search function. */ 246 case NANO_TOGOTOLINE_KEY: 247 do_gotolinecolumn(openfile->current->lineno, 248 openfile->placewewant + 1, TRUE, TRUE, FALSE, 249 TRUE); 250 /* Put answer up on the statusbar and 251 * fall through. */ 252 default: 253 return -1; 254 } 255 } 256 257 return 0; 258} 259 260/* Look for needle, starting at (current, current_x). If no_sameline is 261 * TRUE, skip over begin when looking for needle. begin is the line 262 * where we first started searching, at column begin_x. The return 263 * value specifies whether we found anything. If we did, set needle_len 264 * to the length of the string we found if it isn't NULL. */ 265bool findnextstr( 266#ifndef DISABLE_SPELLER 267 bool whole_word, 268#endif 269 bool no_sameline, const filestruct *begin, size_t begin_x, const 270 char *needle, size_t *needle_len) 271{ 272 size_t found_len; 273 /* The length of the match we find. */ 274 size_t current_x_find = 0; 275 /* The location in the current line of the match we find. */ 276 ssize_t current_y_find = openfile->current_y; 277 filestruct *fileptr = openfile->current; 278 const char *rev_start = fileptr->data, *found = NULL; 279 280 /* rev_start might end up 1 character before the start or after the 281 * end of the line. This won't be a problem because strstrwrapper() 282 * will return immediately and say that no match was found, and 283 * rev_start will be properly set when the search continues on the 284 * previous or next line. */ 285 rev_start += 286#ifndef NANO_TINY 287 ISSET(BACKWARDS_SEARCH) ? 288 openfile->current_x - 1 : 289#endif 290 openfile->current_x + 1; 291 292 /* Look for needle in the current line we're searching. */ 293 while (TRUE) { 294 found = strstrwrapper(fileptr->data, needle, rev_start); 295 296 /* We've found a potential match. */ 297 if (found != NULL) { 298#ifndef DISABLE_SPELLER 299 bool found_whole = FALSE; 300 /* Is this potential match a whole word? */ 301#endif 302 303 /* Set found_len to the length of the potential match. */ 304 found_len = 305#ifdef HAVE_REGEX_H 306 ISSET(USE_REGEXP) ? 307 regmatches[0].rm_eo - regmatches[0].rm_so : 308#endif 309 strlen(needle); 310 311#ifndef DISABLE_SPELLER 312 /* If we're searching for whole words, see if this potential 313 * match is a whole word. */ 314 if (whole_word) { 315 char *word = mallocstrncpy(NULL, found, found_len + 1); 316 word[found_len] = '\0'; 317 318 found_whole = is_whole_word(found - fileptr->data, 319 fileptr->data, word); 320 free(word); 321 } 322#endif 323 324 /* If we're searching for whole words and this potential 325 * match isn't a whole word, or if we're not allowed to find 326 * a match on the same line we started on and this potential 327 * match is on that line, continue searching. */ 328 if ( 329#ifndef DISABLE_SPELLER 330 (!whole_word || found_whole) && 331#endif 332 (!no_sameline || fileptr != openfile->current)) 333 break; 334 } 335 336 /* We've finished processing the file, so get out. */ 337 if (search_last_line) { 338 not_found_msg(needle); 339 return FALSE; 340 } 341 342 /* Move to the previous or next line in the file. */ 343#ifndef NANO_TINY 344 if (ISSET(BACKWARDS_SEARCH)) { 345 fileptr = fileptr->prev; 346 current_y_find--; 347 } else { 348#endif 349 fileptr = fileptr->next; 350 current_y_find++; 351#ifndef NANO_TINY 352 } 353#endif 354 355 /* We've reached the start or end of the buffer, so wrap 356 * around. */ 357 if (fileptr == NULL) { 358#ifndef NANO_TINY 359 if (ISSET(BACKWARDS_SEARCH)) { 360 fileptr = openfile->filebot; 361 current_y_find = editwinrows - 1; 362 } else { 363#endif 364 fileptr = openfile->fileage; 365 current_y_find = 0; 366#ifndef NANO_TINY 367 } 368#endif 369 statusbar(_("Search Wrapped")); 370 } 371 372 /* We've reached the original starting line. */ 373 if (fileptr == begin) 374 search_last_line = TRUE; 375 376 rev_start = fileptr->data; 377#ifndef NANO_TINY 378 if (ISSET(BACKWARDS_SEARCH)) 379 rev_start += strlen(fileptr->data); 380#endif 381 } 382 383 /* We found an instance. */ 384 current_x_find = found - fileptr->data; 385 386 /* Ensure we haven't wrapped around again! */ 387 if (search_last_line && 388#ifndef NANO_TINY 389 ((!ISSET(BACKWARDS_SEARCH) && current_x_find > begin_x) || 390 (ISSET(BACKWARDS_SEARCH) && current_x_find < begin_x)) 391#else 392 current_x_find > begin_x 393#endif 394 ) { 395 not_found_msg(needle); 396 return FALSE; 397 } 398 399 /* We've definitely found something. */ 400 openfile->current = fileptr; 401 openfile->current_x = current_x_find; 402 openfile->placewewant = xplustabs(); 403 openfile->current_y = current_y_find; 404 405 /* needle_len holds the length of needle. */ 406 if (needle_len != NULL) 407 *needle_len = found_len; 408 409 return TRUE; 410} 411 412/* Clear the flag indicating that a search reached the last line of the 413 * file. We need to do this just before a new search. */ 414void findnextstr_wrap_reset(void) 415{ 416 search_last_line = FALSE; 417} 418 419/* Search for a string. */ 420void do_search(void) 421{ 422 filestruct *fileptr = openfile->current; 423 size_t fileptr_x = openfile->current_x; 424 size_t pww_save = openfile->placewewant; 425 int i; 426 bool didfind; 427 428 i = search_init(FALSE, FALSE); 429 430 if (i == -1) 431 /* Cancel, Go to Line, blank search string, or regcomp() 432 * failed. */ 433 search_replace_abort(); 434 else if (i == -2) 435 /* Replace. */ 436 do_replace(); 437#if !defined(NANO_TINY) || defined(HAVE_REGEX_H) 438 else if (i == 1) 439 /* Case Sensitive, Backwards, or Regexp search toggle. */ 440 do_search(); 441#endif 442 443 if (i != 0) 444 return; 445 446 /* If answer is now "", copy last_search into answer. */ 447 if (answer[0] == '\0') 448 answer = mallocstrcpy(answer, last_search); 449 else 450 last_search = mallocstrcpy(last_search, answer); 451 452#ifndef NANO_TINY 453 /* If answer is not "", add this search string to the search history 454 * list. */ 455 if (answer[0] != '\0') 456 update_history(&search_history, answer); 457#endif 458 459 findnextstr_wrap_reset(); 460 didfind = findnextstr( 461#ifndef DISABLE_SPELLER 462 FALSE, 463#endif 464 FALSE, openfile->current, openfile->current_x, answer, NULL); 465 466 /* Check to see if there's only one occurrence of the string and 467 * we're on it now. */ 468 if (fileptr == openfile->current && fileptr_x == 469 openfile->current_x && didfind) { 470#ifdef HAVE_REGEX_H 471 /* Do the search again, skipping over the current line, if we're 472 * doing a bol and/or eol regex search ("^", "$", or "^$"), so 473 * that we find one only once per line. We should only end up 474 * back at the same position if the string isn't found again, in 475 * which case it's the only occurrence. */ 476 if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp, 477 last_search)) { 478 didfind = findnextstr( 479#ifndef DISABLE_SPELLER 480 FALSE, 481#endif 482 TRUE, openfile->current, 483 openfile->current_x, answer, NULL); 484 if (fileptr == openfile->current && fileptr_x == 485 openfile->current_x && !didfind) 486 statusbar(_("This is the only occurrence")); 487 } else { 488#endif 489 statusbar(_("This is the only occurrence")); 490#ifdef HAVE_REGEX_H 491 } 492#endif 493 } 494 495 openfile->placewewant = xplustabs(); 496 edit_redraw(fileptr, pww_save); 497 search_replace_abort(); 498} 499 500#ifndef NANO_TINY 501/* Search for the last string without prompting. */ 502void do_research(void) 503{ 504 filestruct *fileptr = openfile->current; 505 size_t fileptr_x = openfile->current_x; 506 size_t pww_save = openfile->placewewant; 507 bool didfind; 508 509 search_init_globals(); 510 511 if (last_search[0] != '\0') { 512#ifdef HAVE_REGEX_H 513 /* Since answer is "", use last_search! */ 514 if (ISSET(USE_REGEXP) && !regexp_init(last_search)) 515 return; 516#endif 517 518 findnextstr_wrap_reset(); 519 didfind = findnextstr( 520#ifndef DISABLE_SPELLER 521 FALSE, 522#endif 523 FALSE, openfile->current, openfile->current_x, 524 last_search, NULL); 525 526 /* Check to see if there's only one occurrence of the string and 527 * we're on it now. */ 528 if (fileptr == openfile->current && fileptr_x == 529 openfile->current_x && didfind) { 530#ifdef HAVE_REGEX_H 531 /* Do the search again, skipping over the current line, if 532 * we're doing a bol and/or eol regex search ("^", "$", or 533 * "^$"), so that we find one only once per line. We should 534 * only end up back at the same position if the string isn't 535 * found again, in which case it's the only occurrence. */ 536 if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp, 537 last_search)) { 538 didfind = findnextstr( 539#ifndef DISABLE_SPELLER 540 FALSE, 541#endif 542 TRUE, openfile->current, openfile->current_x, 543 answer, NULL); 544 if (fileptr == openfile->current && fileptr_x == 545 openfile->current_x && !didfind) 546 statusbar(_("This is the only occurrence")); 547 } else { 548#endif 549 statusbar(_("This is the only occurrence")); 550#ifdef HAVE_REGEX_H 551 } 552#endif 553 } 554 } else 555 statusbar(_("No current search pattern")); 556 557 openfile->placewewant = xplustabs(); 558 edit_redraw(fileptr, pww_save); 559 search_replace_abort(); 560} 561#endif 562 563#ifdef HAVE_REGEX_H 564int replace_regexp(char *string, bool create) 565{ 566 /* We have a split personality here. If create is FALSE, just 567 * calculate the size of the replacement line (necessary because of 568 * subexpressions \1 to \9 in the replaced text). */ 569 570 const char *c = last_replace; 571 size_t search_match_count = regmatches[0].rm_eo - 572 regmatches[0].rm_so; 573 size_t new_line_size = strlen(openfile->current->data) + 1 - 574 search_match_count; 575 576 /* Iterate through the replacement text to handle subexpression 577 * replacement using \1, \2, \3, etc. */ 578 while (*c != '\0') { 579 int num = (*(c + 1) - '0'); 580 581 if (*c != '\\' || num < 1 || num > 9 || num > 582 search_regexp.re_nsub) { 583 if (create) 584 *string++ = *c; 585 c++; 586 new_line_size++; 587 } else { 588 size_t i = regmatches[num].rm_eo - regmatches[num].rm_so; 589 590 /* Skip over the replacement expression. */ 591 c += 2; 592 593 /* But add the length of the subexpression to new_size. */ 594 new_line_size += i; 595 596 /* And if create is TRUE, append the result of the 597 * subexpression match to the new line. */ 598 if (create) { 599 strncpy(string, openfile->current->data + 600 openfile->current_x + regmatches[num].rm_so, i); 601 string += i; 602 } 603 } 604 } 605 606 if (create) 607 *string = '\0'; 608 609 return new_line_size; 610} 611#endif 612 613char *replace_line(const char *needle) 614{ 615 char *copy; 616 size_t new_line_size, search_match_count; 617 618 /* Calculate the size of the new line. */ 619#ifdef HAVE_REGEX_H 620 if (ISSET(USE_REGEXP)) { 621 search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so; 622 new_line_size = replace_regexp(NULL, FALSE); 623 } else { 624#endif 625 search_match_count = strlen(needle); 626 new_line_size = strlen(openfile->current->data) - 627 search_match_count + strlen(answer) + 1; 628#ifdef HAVE_REGEX_H 629 } 630#endif 631 632 /* Create the buffer. */ 633 copy = charalloc(new_line_size); 634 635 /* The head of the original line. */ 636 strncpy(copy, openfile->current->data, openfile->current_x); 637 638 /* The replacement text. */ 639#ifdef HAVE_REGEX_H 640 if (ISSET(USE_REGEXP)) 641 replace_regexp(copy + openfile->current_x, TRUE); 642 else 643#endif 644 strcpy(copy + openfile->current_x, answer); 645 646 /* The tail of the original line. */ 647 assert(openfile->current_x + search_match_count <= strlen(openfile->current->data)); 648 649 strcat(copy, openfile->current->data + openfile->current_x + 650 search_match_count); 651 652 return copy; 653} 654 655/* Step through each replace word and prompt user before replacing. 656 * Parameters real_current and real_current_x are needed in order to 657 * allow the cursor position to be updated when a word before the cursor 658 * is replaced by a shorter word. 659 * 660 * needle is the string to seek. We replace it with answer. Return -1 661 * if needle isn't found, else the number of replacements performed. If 662 * canceled isn't NULL, set it to TRUE if we canceled. */ 663ssize_t do_replace_loop( 664#ifndef DISABLE_SPELLER 665 bool whole_word, 666#endif 667 bool *canceled, const filestruct *real_current, size_t 668 *real_current_x, const char *needle) 669{ 670 ssize_t numreplaced = -1; 671 size_t match_len; 672 bool replaceall = FALSE; 673#ifdef HAVE_REGEX_H 674 /* The starting-line match and bol/eol regex flags. */ 675 bool begin_line = FALSE, bol_or_eol = FALSE; 676#endif 677#ifndef NANO_TINY 678 bool old_mark_set = openfile->mark_set; 679 filestruct *edittop_save = openfile->edittop, *top, *bot; 680 size_t top_x, bot_x; 681 bool right_side_up = FALSE; 682 /* TRUE if (mark_begin, mark_begin_x) is the top of the mark, 683 * FALSE if (current, current_x) is. */ 684 685 if (old_mark_set) { 686 /* If the mark is on, partition the filestruct so that it 687 * contains only the marked text, set edittop to the top of the 688 * partition, turn the mark off, and refresh the screen. */ 689 mark_order((const filestruct **)&top, &top_x, 690 (const filestruct **)&bot, &bot_x, &right_side_up); 691 filepart = partition_filestruct(top, top_x, bot, bot_x); 692 openfile->edittop = openfile->fileage; 693 openfile->mark_set = FALSE; 694 edit_refresh(); 695 } 696#endif 697 698 if (canceled != NULL) 699 *canceled = FALSE; 700 701 findnextstr_wrap_reset(); 702 while (findnextstr( 703#ifndef DISABLE_SPELLER 704 whole_word, 705#endif 706#ifdef HAVE_REGEX_H 707 /* We should find a bol and/or eol regex only once per line. If 708 * the bol_or_eol flag is set, it means that the last search 709 * found one on the beginning line, so we should skip over the 710 * beginning line when doing this search. */ 711 bol_or_eol 712#else 713 FALSE 714#endif 715 , real_current, *real_current_x, needle, &match_len)) { 716 int i = 0; 717 718#ifdef HAVE_REGEX_H 719 /* If the bol_or_eol flag is set, we've found a match on the 720 * beginning line already, and we're still on the beginning line 721 * after the search, it means that we've wrapped around, so 722 * we're done. */ 723 if (bol_or_eol && begin_line && openfile->current == 724 real_current) 725 break; 726 /* Otherwise, set the begin_line flag if we've found a match on 727 * the beginning line, reset the bol_or_eol flag, and 728 * continue. */ 729 else { 730 if (openfile->current == real_current) 731 begin_line = TRUE; 732 bol_or_eol = FALSE; 733 } 734#endif 735 736 if (!replaceall) 737 edit_refresh(); 738 739 /* Indicate that we found the search string. */ 740 if (numreplaced == -1) 741 numreplaced = 0; 742 743 if (!replaceall) { 744 size_t xpt = xplustabs(); 745 char *exp_word = display_string(openfile->current->data, 746 xpt, strnlenpt(openfile->current->data, 747 openfile->current_x + match_len) - xpt, FALSE); 748 749 curs_set(0); 750 751 do_replace_highlight(TRUE, exp_word); 752 753 i = do_yesno_prompt(TRUE, _("Replace this instance?")); 754 755 do_replace_highlight(FALSE, exp_word); 756 757 free(exp_word); 758 759 curs_set(1); 760 761 if (i == -1) { /* We canceled the replace. */ 762 if (canceled != NULL) 763 *canceled = TRUE; 764 break; 765 } 766 } 767 768#ifdef HAVE_REGEX_H 769 /* Set the bol_or_eol flag if we're doing a bol and/or eol regex 770 * replace ("^", "$", or "^$"). */ 771 if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp, 772 needle)) 773 bol_or_eol = TRUE; 774#endif 775 776 if (i > 0 || replaceall) { /* Yes, replace it!!!! */ 777 char *copy; 778 size_t length_change; 779 780 if (i == 2) 781 replaceall = TRUE; 782 783 copy = replace_line(needle); 784 785 length_change = strlen(copy) - 786 strlen(openfile->current->data); 787 788#ifndef NANO_TINY 789 /* If the mark was on and (mark_begin, mark_begin_x) was the 790 * top of it, don't change mark_begin_x. */ 791 if (!old_mark_set || !right_side_up) { 792 /* Keep mark_begin_x in sync with the text changes. */ 793 if (openfile->current == openfile->mark_begin && 794 openfile->mark_begin_x > openfile->current_x) { 795 if (openfile->mark_begin_x < openfile->current_x + 796 match_len) 797 openfile->mark_begin_x = openfile->current_x; 798 else 799 openfile->mark_begin_x += length_change; 800 } 801 } 802 803 /* If the mark was on and (current, current_x) was the top 804 * of it, don't change real_current_x. */ 805 if (!old_mark_set || right_side_up) { 806#endif 807 /* Keep real_current_x in sync with the text changes. */ 808 if (openfile->current == real_current && 809 openfile->current_x <= *real_current_x) { 810 if (*real_current_x < 811 openfile->current_x + match_len) 812 *real_current_x = openfile->current_x + 813 match_len; 814 *real_current_x += length_change; 815 } 816#ifndef NANO_TINY 817 } 818#endif 819 820 /* Set the cursor at the last character of the replacement 821 * text, so searching will resume after the replacement 822 * text. Note that current_x might be set to (size_t)-1 823 * here. */ 824#ifndef NANO_TINY 825 if (!ISSET(BACKWARDS_SEARCH)) 826#endif 827 openfile->current_x += match_len + length_change - 1; 828 829 /* Cleanup. */ 830 openfile->totsize += length_change; 831 free(openfile->current->data); 832 openfile->current->data = copy; 833 834 if (!replaceall) { 835#ifdef ENABLE_COLOR 836 /* If color syntaxes are available and turned on, we 837 * need to call edit_refresh(). */ 838 if (openfile->colorstrings != NULL && 839 !ISSET(NO_COLOR_SYNTAX)) 840 edit_refresh(); 841 else 842#endif 843 update_line(openfile->current, openfile->current_x); 844 } 845 846 set_modified(); 847 numreplaced++; 848 } 849 } 850 851#ifndef NANO_TINY 852 if (old_mark_set) { 853 /* If the mark was on, unpartition the filestruct so that it 854 * contains all the text again, set edittop back to what it was 855 * before, turn the mark back on, and refresh the screen. */ 856 unpartition_filestruct(&filepart); 857 openfile->edittop = edittop_save; 858 openfile->mark_set = TRUE; 859 edit_refresh(); 860 } 861#endif 862 863 /* If the NO_NEWLINES flag isn't set, and text has been added to the 864 * magicline, make a new magicline. */ 865 if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0') 866 new_magicline(); 867 868 return numreplaced; 869} 870 871/* Replace a string. */ 872void do_replace(void) 873{ 874 filestruct *edittop_save, *begin; 875 size_t begin_x, pww_save; 876 ssize_t numreplaced; 877 int i; 878 879 if (ISSET(VIEW_MODE)) { 880 print_view_warning(); 881 search_replace_abort(); 882 return; 883 } 884 885 i = search_init(TRUE, FALSE); 886 if (i == -1) { 887 /* Cancel, Go to Line, blank search string, or regcomp() 888 * failed. */ 889 search_replace_abort(); 890 return; 891 } else if (i == -2) { 892 /* No Replace. */ 893 do_search(); 894 return; 895 } else if (i == 1) 896 /* Case Sensitive, Backwards, or Regexp search toggle. */ 897 do_replace(); 898 899 if (i != 0) 900 return; 901 902 /* If answer is not "", add answer to the search history list and 903 * copy answer into last_search. */ 904 if (answer[0] != '\0') { 905#ifndef NANO_TINY 906 update_history(&search_history, answer); 907#endif 908 last_search = mallocstrcpy(last_search, answer); 909 } 910 911 last_replace = mallocstrcpy(last_replace, ""); 912 913 i = do_prompt(FALSE, 914#ifndef DISABLE_TABCOMP 915 TRUE, 916#endif 917 replace_list_2, last_replace, 918#ifndef NANO_TINY 919 &replace_history, 920#endif 921 edit_refresh, _("Replace with")); 922 923#ifndef NANO_TINY 924 /* Add this replace string to the replace history list. i == 0 925 * means that the string is not "". */ 926 if (i == 0) 927 update_history(&replace_history, answer); 928#endif 929 930 if (i != 0 && i != -2) { 931 if (i == -1) { /* Cancel. */ 932 if (last_replace[0] != '\0') 933 answer = mallocstrcpy(answer, last_replace); 934 statusbar(_("Cancelled")); 935 } 936 search_replace_abort(); 937 return; 938 } 939 940 last_replace = mallocstrcpy(last_replace, answer); 941 942 /* Save where we are. */ 943 edittop_save = openfile->edittop; 944 begin = openfile->current; 945 begin_x = openfile->current_x; 946 pww_save = openfile->placewewant; 947 948 numreplaced = do_replace_loop( 949#ifndef DISABLE_SPELLER 950 FALSE, 951#endif 952 NULL, begin, &begin_x, last_search); 953 954 /* Restore where we were. */ 955 openfile->edittop = edittop_save; 956 openfile->current = begin; 957 openfile->current_x = begin_x; 958 openfile->placewewant = pww_save; 959 960 edit_refresh(); 961 962 if (numreplaced >= 0) 963 statusbar(P_("Replaced %lu occurrence", 964 "Replaced %lu occurrences", (unsigned long)numreplaced), 965 (unsigned long)numreplaced); 966 967 search_replace_abort(); 968} 969 970/* Go to the specified line and column, or ask for them if interactive 971 * is TRUE. Save the x-coordinate and y-coordinate if save_pos is TRUE. 972 * Update the screen afterwards if allow_update is TRUE. Note that both 973 * the line and column numbers should be one-based. */ 974void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer, 975 bool interactive, bool save_pos, bool allow_update) 976{ 977 if (interactive) { 978 char *ans = mallocstrcpy(NULL, answer); 979 980 /* Ask for the line and column. */ 981 int i = do_prompt(FALSE, 982#ifndef DISABLE_TABCOMP 983 TRUE, 984#endif 985 gotoline_list, use_answer ? ans : "", 986#ifndef NANO_TINY 987 NULL, 988#endif 989 edit_refresh, _("Enter line number, column number")); 990 991 free(ans); 992 993 /* Cancel, or Enter with blank string. */ 994 if (i < 0) { 995 statusbar(_("Cancelled")); 996 display_main_list(); 997 return; 998 } 999 1000 if (i == NANO_TOOTHERWHEREIS_KEY) { 1001 /* Keep answer up on the statusbar. */ 1002 search_init(TRUE, TRUE); 1003 1004 do_search(); 1005 return; 1006 } 1007 1008 /* Do a bounds check. Display a warning on an out-of-bounds 1009 * line or column number only if we hit Enter at the statusbar 1010 * prompt. */ 1011 if (!parse_line_column(answer, &line, &column) || line < 1 || 1012 column < 1) { 1013 if (i == 0) 1014 statusbar(_("Come on, be reasonable")); 1015 display_main_list(); 1016 return; 1017 } 1018 } else { 1019 if (line < 1) 1020 line = openfile->current->lineno; 1021 1022 if (column < 1) 1023 column = openfile->placewewant + 1; 1024 } 1025 1026 for (openfile->current = openfile->fileage; 1027 openfile->current != openfile->filebot && line > 1; line--) 1028 openfile->current = openfile->current->next; 1029 1030 openfile->current_x = actual_x(openfile->current->data, column - 1); 1031 openfile->placewewant = column - 1; 1032 1033 /* Put the top line of the edit window in range of the current line. 1034 * If save_pos is TRUE, don't change the cursor position when doing 1035 * it. */ 1036 edit_update(save_pos ? NONE : CENTER); 1037 1038 /* If allow_update is TRUE, update the screen. */ 1039 if (allow_update) 1040 edit_refresh(); 1041 1042 display_main_list(); 1043} 1044 1045/* Go to the specified line and column, asking for them beforehand. */ 1046void do_gotolinecolumn_void(void) 1047{ 1048 do_gotolinecolumn(openfile->current->lineno, 1049 openfile->placewewant + 1, FALSE, TRUE, FALSE, TRUE); 1050} 1051 1052#ifndef DISABLE_SPELLER 1053/* Go to the line with the number specified in pos_line, the 1054 * x-coordinate specified in pos_x, the y-coordinate specified in pos_y, 1055 * and the place we want specified in pos_pww. */ 1056void do_gotopos(ssize_t pos_line, size_t pos_x, ssize_t pos_y, size_t 1057 pos_pww) 1058{ 1059 /* Since do_gotolinecolumn() resets the x-coordinate but not the 1060 * y-coordinate, set the coordinates up this way. */ 1061 openfile->current_y = pos_y; 1062 do_gotolinecolumn(pos_line, pos_x + 1, FALSE, FALSE, TRUE, TRUE); 1063 1064 /* Set the rest of the coordinates up. */ 1065 openfile->placewewant = pos_pww; 1066 update_line(openfile->current, pos_x); 1067} 1068#endif 1069 1070#ifndef NANO_TINY 1071/* Search for a match to one of the two characters in bracket_set. If 1072 * reverse is TRUE, search backwards for the leftmost bracket. 1073 * Otherwise, search forwards for the rightmost bracket. Return TRUE if 1074 * we found a match, and FALSE otherwise. */ 1075bool find_bracket_match(bool reverse, const char *bracket_set) 1076{ 1077 filestruct *fileptr = openfile->current; 1078 const char *rev_start = NULL, *found = NULL; 1079 ssize_t current_y_find = openfile->current_y; 1080 1081 assert(mbstrlen(bracket_set) == 2); 1082 1083 /* rev_start might end up 1 character before the start or after the 1084 * end of the line. This won't be a problem because we'll skip over 1085 * it below in that case, and rev_start will be properly set when 1086 * the search continues on the previous or next line. */ 1087 rev_start = reverse ? fileptr->data + (openfile->current_x - 1) : 1088 fileptr->data + (openfile->current_x + 1); 1089 1090 /* Look for either of the two characters in bracket_set. rev_start 1091 * can be 1 character before the start or after the end of the line. 1092 * In either case, just act as though no match is found. */ 1093 while (TRUE) { 1094 found = ((rev_start > fileptr->data && *(rev_start - 1) == 1095 '\0') || rev_start < fileptr->data) ? NULL : (reverse ? 1096 mbrevstrpbrk(fileptr->data, bracket_set, rev_start) : 1097 mbstrpbrk(rev_start, bracket_set)); 1098 1099 /* We've found a potential match. */ 1100 if (found != NULL) 1101 break; 1102 1103 if (reverse) { 1104 fileptr = fileptr->prev; 1105 current_y_find--; 1106 } else { 1107 fileptr = fileptr->next; 1108 current_y_find++; 1109 } 1110 1111 /* We've reached the start or end of the buffer, so get out. */ 1112 if (fileptr == NULL) 1113 return FALSE; 1114 1115 rev_start = fileptr->data; 1116 if (reverse) 1117 rev_start += strlen(fileptr->data); 1118 } 1119 1120 /* We've definitely found something. */ 1121 openfile->current = fileptr; 1122 openfile->current_x = found - fileptr->data; 1123 openfile->placewewant = xplustabs(); 1124 openfile->current_y = current_y_find; 1125 1126 return TRUE; 1127} 1128 1129/* Search for a match to the bracket at the current cursor position, if 1130 * there is one. */ 1131void do_find_bracket(void) 1132{ 1133 filestruct *current_save; 1134 size_t current_x_save, pww_save; 1135 const char *ch; 1136 /* The location in matchbrackets of the bracket at the current 1137 * cursor position. */ 1138 int ch_len; 1139 /* The length of ch in bytes. */ 1140 const char *wanted_ch; 1141 /* The location in matchbrackets of the bracket complementing 1142 * the bracket at the current cursor position. */ 1143 int wanted_ch_len; 1144 /* The length of wanted_ch in bytes. */ 1145 char *bracket_set; 1146 /* The pair of characters in ch and wanted_ch. */ 1147 size_t i; 1148 /* Generic loop variable. */ 1149 size_t matchhalf; 1150 /* The number of single-byte characters in one half of 1151 * matchbrackets. */ 1152 size_t mbmatchhalf; 1153 /* The number of multibyte characters in one half of 1154 * matchbrackets. */ 1155 size_t count = 1; 1156 /* The initial bracket count. */ 1157 bool reverse; 1158 /* The direction we search. */ 1159 char *found_ch; 1160 /* The character we find. */ 1161 1162 assert(mbstrlen(matchbrackets) % 2 == 0); 1163 1164 ch = openfile->current->data + openfile->current_x; 1165 1166 if (ch == '\0' || (ch = mbstrchr(matchbrackets, ch)) == NULL) { 1167 statusbar(_("Not a bracket")); 1168 return; 1169 } 1170 1171 /* Save where we are. */ 1172 current_save = openfile->current; 1173 current_x_save = openfile->current_x; 1174 pww_save = openfile->placewewant; 1175 1176 /* If we're on an opening bracket, which must be in the first half 1177 * of matchbrackets, we want to search forwards for a closing 1178 * bracket. If we're on a closing bracket, which must be in the 1179 * second half of matchbrackets, we want to search backwards for an 1180 * opening bracket. */ 1181 matchhalf = 0; 1182 mbmatchhalf = mbstrlen(matchbrackets) / 2; 1183 1184 for (i = 0; i < mbmatchhalf; i++) 1185 matchhalf += parse_mbchar(matchbrackets + matchhalf, NULL, 1186 NULL); 1187 1188 reverse = ((ch - matchbrackets) >= matchhalf); 1189 1190 /* If we're on an opening bracket, set wanted_ch to the character 1191 * that's matchhalf characters after ch. If we're on a closing 1192 * bracket, set wanted_ch to the character that's matchhalf 1193 * characters before ch. */ 1194 wanted_ch = ch; 1195 1196 while (mbmatchhalf > 0) { 1197 if (reverse) 1198 wanted_ch = matchbrackets + move_mbleft(matchbrackets, 1199 wanted_ch - matchbrackets); 1200 else 1201 wanted_ch += move_mbright(wanted_ch, 0); 1202 1203 mbmatchhalf--; 1204 } 1205 1206 ch_len = parse_mbchar(ch, NULL, NULL); 1207 wanted_ch_len = parse_mbchar(wanted_ch, NULL, NULL); 1208 1209 /* Fill bracket_set in with the values of ch and wanted_ch. */ 1210 bracket_set = charalloc((mb_cur_max() * 2) + 1); 1211 strncpy(bracket_set, ch, ch_len); 1212 strncpy(bracket_set + ch_len, wanted_ch, wanted_ch_len); 1213 null_at(&bracket_set, ch_len + wanted_ch_len); 1214 1215 found_ch = charalloc(mb_cur_max() + 1); 1216 1217 while (TRUE) { 1218 if (find_bracket_match(reverse, bracket_set)) { 1219 /* If we found an identical bracket, increment count. If we 1220 * found a complementary bracket, decrement it. */ 1221 parse_mbchar(openfile->current->data + openfile->current_x, 1222 found_ch, NULL); 1223 count += (strncmp(found_ch, ch, ch_len) == 0) ? 1 : -1; 1224 1225 /* If count is zero, we've found a matching bracket. Update 1226 * the screen and get out. */ 1227 if (count == 0) { 1228 edit_redraw(current_save, pww_save); 1229 break; 1230 } 1231 } else { 1232 /* We didn't find either an opening or closing bracket. 1233 * Indicate this, restore where we were, and get out. */ 1234 statusbar(_("No matching bracket")); 1235 openfile->current = current_save; 1236 openfile->current_x = current_x_save; 1237 openfile->placewewant = pww_save; 1238 break; 1239 } 1240 } 1241 1242 /* Clean up. */ 1243 free(bracket_set); 1244 free(found_ch); 1245} 1246 1247#ifdef ENABLE_NANORC 1248/* Indicate whether any of the history lists have changed. */ 1249bool history_has_changed(void) 1250{ 1251 return history_changed; 1252} 1253#endif 1254 1255/* Initialize the search and replace history lists. */ 1256void history_init(void) 1257{ 1258 search_history = make_new_node(NULL); 1259 search_history->data = mallocstrcpy(NULL, ""); 1260 searchage = search_history; 1261 searchbot = search_history; 1262 1263 replace_history = make_new_node(NULL); 1264 replace_history->data = mallocstrcpy(NULL, ""); 1265 replaceage = replace_history; 1266 replacebot = replace_history; 1267} 1268 1269/* Set the current position in the history list h to the bottom. */ 1270void history_reset(const filestruct *h) 1271{ 1272 if (h == search_history) 1273 search_history = searchbot; 1274 else if (h == replace_history) 1275 replace_history = replacebot; 1276} 1277 1278/* Return the first node containing the first len characters of the 1279 * string s in the history list, starting at h_start and ending at 1280 * h_end, or NULL if there isn't one. */ 1281filestruct *find_history(const filestruct *h_start, const filestruct 1282 *h_end, const char *s, size_t len) 1283{ 1284 const filestruct *p; 1285 1286 for (p = h_start; p != h_end->next && p != NULL; p = p->next) { 1287 if (strncmp(s, p->data, len) == 0) 1288 return (filestruct *)p; 1289 } 1290 1291 return NULL; 1292} 1293 1294/* Update a history list. h should be the current position in the 1295 * list. */ 1296void update_history(filestruct **h, const char *s) 1297{ 1298 filestruct **hage = NULL, **hbot = NULL, *p; 1299 1300 assert(h != NULL && s != NULL); 1301 1302 if (*h == search_history) { 1303 hage = &searchage; 1304 hbot = &searchbot; 1305 } else if (*h == replace_history) { 1306 hage = &replaceage; 1307 hbot = &replacebot; 1308 } 1309 1310 assert(hage != NULL && hbot != NULL); 1311 1312 /* If this string is already in the history, delete it. */ 1313 p = find_history(*hage, *hbot, s, (size_t)-1); 1314 1315 if (p != NULL) { 1316 filestruct *foo, *bar; 1317 1318 /* If the string is at the beginning, move the beginning down to 1319 * the next string. */ 1320 if (p == *hage) 1321 *hage = (*hage)->next; 1322 1323 /* Delete the string. */ 1324 foo = p; 1325 bar = p->next; 1326 unlink_node(foo); 1327 delete_node(foo); 1328 if (bar != NULL) 1329 renumber(bar); 1330 } 1331 1332 /* If the history is full, delete the beginning entry to make room 1333 * for the new entry at the end. We assume that MAX_SEARCH_HISTORY 1334 * is greater than zero. */ 1335 if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) { 1336 filestruct *foo = *hage; 1337 1338 *hage = (*hage)->next; 1339 unlink_node(foo); 1340 delete_node(foo); 1341 renumber(*hage); 1342 } 1343 1344 /* Add the new entry to the end. */ 1345 (*hbot)->data = mallocstrcpy((*hbot)->data, s); 1346 splice_node(*hbot, make_new_node(*hbot), (*hbot)->next); 1347 *hbot = (*hbot)->next; 1348 (*hbot)->data = mallocstrcpy(NULL, ""); 1349 1350#ifdef ENABLE_NANORC 1351 /* Indicate that the history's been changed. */ 1352 history_changed = TRUE; 1353#endif 1354 1355 /* Set the current position in the list to the bottom. */ 1356 *h = *hbot; 1357} 1358 1359/* Move h to the string in the history list just before it, and return 1360 * that string. If there isn't one, don't move h and return NULL. */ 1361char *get_history_older(filestruct **h) 1362{ 1363 assert(h != NULL); 1364 1365 if ((*h)->prev == NULL) 1366 return NULL; 1367 1368 *h = (*h)->prev; 1369 1370 return (*h)->data; 1371} 1372 1373/* Move h to the string in the history list just after it, and return 1374 * that string. If there isn't one, don't move h and return NULL. */ 1375char *get_history_newer(filestruct **h) 1376{ 1377 assert(h != NULL); 1378 1379 if ((*h)->next == NULL) 1380 return NULL; 1381 1382 *h = (*h)->next; 1383 1384 return (*h)->data; 1385} 1386 1387#ifndef DISABLE_TABCOMP 1388/* Move h to the next string that's a tab completion of the string s, 1389 * looking at only the first len characters of s, and return that 1390 * string. If there isn't one, or if len is 0, don't move h and return 1391 * s. */ 1392char *get_history_completion(filestruct **h, const char *s, size_t len) 1393{ 1394 assert(s != NULL); 1395 1396 if (len > 0) { 1397 filestruct *hage = NULL, *hbot = NULL, *p; 1398 1399 assert(h != NULL); 1400 1401 if (*h == search_history) { 1402 hage = searchage; 1403 hbot = searchbot; 1404 } else if (*h == replace_history) { 1405 hage = replaceage; 1406 hbot = replacebot; 1407 } 1408 1409 assert(hage != NULL && hbot != NULL); 1410 1411 /* Search the history list from the current position to the 1412 * bottom for a match of len characters. Skip over an exact 1413 * match. */ 1414 p = find_history((*h)->next, hbot, s, len); 1415 1416 while (p != NULL && strcmp(p->data, s) == 0) 1417 p = find_history(p->next, hbot, s, len); 1418 1419 if (p != NULL) { 1420 *h = p; 1421 return (*h)->data; 1422 } 1423 1424 /* Search the history list from the top to the current position 1425 * for a match of len characters. Skip over an exact match. */ 1426 p = find_history(hage, *h, s, len); 1427 1428 while (p != NULL && strcmp(p->data, s) == 0) 1429 p = find_history(p->next, *h, s, len); 1430 1431 if (p != NULL) { 1432 *h = p; 1433 return (*h)->data; 1434 } 1435 } 1436 1437 /* If we're here, we didn't find a match, we didn't find an inexact 1438 * match, or len is 0. Return s. */ 1439 return (char *)s; 1440} 1441#endif /* !DISABLE_TABCOMP */ 1442#endif /* !NANO_TINY */ 1443