/* Look for needle, starting at (current, current_x). begin is the line * where we first started searching, at column begin_x. The return * value specifies whether we found anything. If we did, set needle_len * to the length of the string we found if it isn't NULL. */ bool findnextstr( #ifndef DISABLE_SPELLER bool whole_word_only, #endif const filestruct *begin, size_t begin_x, const char *needle, size_t *needle_len) { size_t found_len; /* The length of the match we find. */ size_t current_x_find = 0; /* The location in the current line of the match we find. */ ssize_t current_y_find = openfile->current_y; filestruct *fileptr = openfile->current; const char *rev_start = fileptr->data, *found = NULL; time_t lastkbcheck = time(NULL); /* rev_start might end up 1 character before the start or after the * end of the line. This won't be a problem because strstrwrapper() * will return immediately and say that no match was found, and * rev_start will be properly set when the search continues on the * previous or next line. */ rev_start += #ifndef NANO_TINY ISSET(BACKWARDS_SEARCH) ? ((openfile->current_x == 0) ? -1 : move_mbleft(fileptr->data, openfile->current_x)) : #endif move_mbright(fileptr->data, openfile->current_x); /* Look for needle in the current line we're searching. */ enable_nodelay(); while (TRUE) { if (time(NULL) - lastkbcheck > 1) { int input = parse_kbinput(edit); lastkbcheck = time(NULL); if (input && func_from_key(&input) == do_cancel) { statusbar(_("Cancelled")); return FALSE; } } found = strstrwrapper(fileptr->data, needle, rev_start); /* We've found a potential match. */ if (found != NULL) { #ifndef DISABLE_SPELLER bool found_whole = FALSE; /* Is this potential match a whole word? */ #endif /* Set found_len to the length of the potential match. */ found_len = #ifdef HAVE_REGEX_H ISSET(USE_REGEXP) ? regmatches[0].rm_eo - regmatches[0].rm_so : #endif strlen(needle); #ifndef DISABLE_SPELLER /* If we're searching for whole words, see if this potential * match is a whole word. */ if (whole_word_only) { char *word = mallocstrncpy(NULL, found, found_len + 1); word[found_len] = '\0'; found_whole = is_whole_word(found - fileptr->data, fileptr->data, word); free(word); } /* If we're searching for whole words and this potential * match isn't a whole word, continue searching. */ if (!whole_word_only || found_whole) #endif break; } if (search_last_line) { /* We've finished processing the file, so get out. */ not_found_msg(needle); disable_nodelay(); return FALSE; } /* Move to the previous or next line in the file. */ #ifndef NANO_TINY if (ISSET(BACKWARDS_SEARCH)) { fileptr = fileptr->prev; current_y_find--; } else { #endif fileptr = fileptr->next; current_y_find++; #ifndef NANO_TINY } #endif if (fileptr == NULL) { /* We've reached the start or end of the buffer, so wrap around. */ #ifndef NANO_TINY if (ISSET(BACKWARDS_SEARCH)) { fileptr = openfile->filebot; current_y_find = editwinrows - 1; } else { #endif fileptr = openfile->fileage; current_y_find = 0; #ifndef NANO_TINY } #endif statusbar(_("Search Wrapped")); } if (fileptr == begin) /* We've reached the original starting line. */ search_last_line = TRUE; rev_start = fileptr->data; #ifndef NANO_TINY if (ISSET(BACKWARDS_SEARCH)) rev_start += strlen(fileptr->data); #endif } /* We found an instance. */ current_x_find = found - fileptr->data; /* Ensure we haven't wrapped around again! */ if (search_last_line && #ifndef NANO_TINY ((!ISSET(BACKWARDS_SEARCH) && current_x_find > begin_x) || (ISSET(BACKWARDS_SEARCH) && current_x_find < begin_x)) #else current_x_find > begin_x #endif ) { not_found_msg(needle); disable_nodelay(); return FALSE; } disable_nodelay(); /* We've definitely found something. */ openfile->current = fileptr; openfile->current_x = current_x_find; openfile->placewewant = xplustabs(); openfile->current_y = current_y_find; /* needle_len holds the length of needle. */ if (needle_len != NULL) *needle_len = found_len; return TRUE; }
/* Go to the specified line and column, or ask for them if interactive * is TRUE. Save the x-coordinate and y-coordinate if save_pos is TRUE. * Update the screen afterwards if allow_update is TRUE. Note that both * the line and column numbers should be one-based. */ void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer, bool interactive, bool save_pos, bool allow_update) { if (interactive) { char *ans = mallocstrcpy(NULL, answer); functionptrtype func; /* Ask for the line and column. */ int i = do_prompt(FALSE, #ifndef DISABLE_TABCOMP TRUE, #endif MGOTOLINE, use_answer ? ans : "", #ifndef DISABLE_HISTORIES NULL, #endif /* TRANSLATORS: This is a prompt. */ edit_refresh, _("Enter line number, column number")); free(ans); /* Cancel, or Enter with blank string. */ if (i < 0) { statusbar(_("Cancelled")); display_main_list(); return; } func = func_from_key(&i); if (func == gototext_void) { /* Keep answer up on the statusbar. */ search_init(TRUE, TRUE); do_search(); return; } /* Do a bounds check. Display a warning on an out-of-bounds * line or column number only if we hit Enter at the statusbar * prompt. */ if (!parse_line_column(answer, &line, &column) || line < 1 || column < 1) { if (i == 0) statusbar(_("Invalid line or column number")); display_main_list(); return; } } else { if (line < 1) line = openfile->current->lineno; if (column < 1) column = openfile->placewewant + 1; } for (openfile->current = openfile->fileage; openfile->current != openfile->filebot && line > 1; line--) openfile->current = openfile->current->next; openfile->current_x = actual_x(openfile->current->data, column - 1); openfile->placewewant = column - 1; /* Put the top line of the edit window in range of the current line. * If save_pos is TRUE, don't change the cursor position when doing * it. */ edit_update(save_pos ? NONE : CENTER); /* If allow_update is TRUE, update the screen. */ if (allow_update) { edit_refresh(); display_main_list(); } }
/* Set up the system variables for a search or replace. If use_answer * is TRUE, only set backupstring to answer. Return -2 to run the * opposite program (search -> replace, replace -> search), return -1 if * the search should be canceled (due to Cancel, a blank search string, * Go to Line, or a failed regcomp()), return 0 on success, and return 1 * on rerun calling program. * * replacing is TRUE if we call from do_replace(), and FALSE if called * from do_search(). */ int search_init(bool replacing, bool use_answer) { int i = 0; char *buf; static char *backupstring = NULL; /* The search string we'll be using. */ /* If backupstring doesn't exist, initialize it to "". */ if (backupstring == NULL) backupstring = mallocstrcpy(NULL, ""); /* If use_answer is TRUE, set backupstring to answer and get out. */ if (use_answer) { backupstring = mallocstrcpy(backupstring, answer); return 0; } /* We display the search prompt below. If the user types a partial * search string and then Replace or a toggle, we will return to * do_search() or do_replace() and be called again. In that case, * we should put the same search string back up. */ focusing = TRUE; if (last_search[0] != '\0') { char *disp = display_string(last_search, 0, COLS / 3, FALSE); buf = charalloc(strlen(disp) + 7); /* We use (COLS / 3) here because we need to see more on the line. */ sprintf(buf, " [%s%s]", disp, (strlenpt(last_search) > COLS / 3) ? "..." : ""); free(disp); } else buf = mallocstrcpy(NULL, ""); /* This is now one simple call. It just does a lot. */ i = do_prompt(FALSE, #ifndef DISABLE_TABCOMP TRUE, #endif replacing ? MREPLACE : MWHEREIS, backupstring, #ifndef DISABLE_HISTORIES &search_history, #endif /* TRANSLATORS: This is the main search prompt. */ edit_refresh, "%s%s%s%s%s%s", _("Search"), #ifndef NANO_TINY /* TRANSLATORS: The next three strings are modifiers of the search prompt. */ ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : #endif "", #ifdef HAVE_REGEX_H ISSET(USE_REGEXP) ? _(" [Regexp]") : #endif "", #ifndef NANO_TINY ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") : #endif "", replacing ? #ifndef NANO_TINY /* TRANSLATORS: The next two strings are modifiers of the search prompt. */ openfile->mark_set ? _(" (to replace) in selection") : #endif _(" (to replace)") : "", buf); fflush(stderr); /* Release buf now that we don't need it anymore. */ free(buf); free(backupstring); backupstring = NULL; /* Cancel any search, or just return with no previous search. */ if (i == -1 || (i < 0 && *last_search == '\0') || (!replacing && i == 0 && *answer == '\0')) { statusbar(_("Cancelled")); return -1; } else { functionptrtype func = func_from_key(&i); if (i == -2 || i == 0 ) { #ifdef HAVE_REGEX_H /* Use last_search if answer is an empty string, or * answer if it isn't. */ if (ISSET(USE_REGEXP) && !regexp_init((i == -2) ? last_search : answer)) return -1; #endif ; #ifndef NANO_TINY } else if (func == case_sens_void) { TOGGLE(CASE_SENSITIVE); backupstring = mallocstrcpy(backupstring, answer); return 1; } else if (func == backwards_void) { TOGGLE(BACKWARDS_SEARCH); backupstring = mallocstrcpy(backupstring, answer); return 1; #endif #ifdef HAVE_REGEX_H } else if (func == regexp_void) { TOGGLE(USE_REGEXP); backupstring = mallocstrcpy(backupstring, answer); return 1; #endif } else if (func == do_replace || func == flip_replace_void) { backupstring = mallocstrcpy(backupstring, answer); return -2; /* Call the opposite search function. */ } else if (func == do_gotolinecolumn_void) { do_gotolinecolumn(openfile->current->lineno, openfile->placewewant + 1, TRUE, TRUE); /* Put answer up on the statusbar and * fall through. */ return 3; } else { return -1; } } return 0; }
/* Our main file browser function. path is the tilde-expanded path we * start browsing from. */ std::string do_browser(std::string path, DIR *dir) { std::string retval; bool old_const_update = ISSET(CONST_UPDATE); std::string prev_dir; /* The directory we were in before backing up to "..". */ std::string ans; /* The last answer the user typed at the statusbar prompt. */ size_t old_selected; /* The selected file we had before the current selected file. */ curs_set(0); blank_statusbar(); bottombars(MBROWSER); wnoutrefresh(bottomwin); UNSET(CONST_UPDATE); change_browser_directory: /* We go here after we select a new directory. */ path = get_full_path(path); assert(path.length() > 0 && path.back() == '/'); /* Get the file list, and set longest and width in the process. */ browser_init(path, dir); /* Sort the file list. */ std::sort(filelist.begin(), filelist.end(), sort_directories); /* If prev_dir isn't empty, select the directory saved in it, and then blow it away. */ if (prev_dir != "") { browser_select_filename(prev_dir); prev_dir = ""; /* Otherwise, select the first file or directory in the list. */ } else { selected = 0; } old_selected = (size_t)-1; titlebar(path); Key *kbinput = nullptr; while (true) { struct stat st; size_t fileline = selected / width; /* The line number the selected file is on. */ std::string new_path; /* The path we switch to at the "Go to Directory" prompt. */ if (kbinput) { delete kbinput; kbinput = nullptr; } /* Display the file list if we don't have a key, or if the * selected file has changed, and set width in the process. */ if (old_selected != selected) { browser_refresh(); } old_selected = selected; // Deal with the keyboard input kbinput = new Key(get_kbinput(edit)); auto func = func_from_key(*kbinput); if (func == total_refresh) { total_redraw(); } else if (func == do_help_void) { do_help_void(); curs_set(0); /* Search for a filename. */ } else if (func == do_search) { curs_set(1); do_filesearch(); curs_set(0); /* Search for another filename. */ } else if (func == do_research) { do_fileresearch(); } else if (func == do_page_up) { if (selected >= (editwinrows + fileline % editwinrows) * width) { selected -= (editwinrows + fileline % editwinrows) * width; } else { selected = 0; } } else if (func == do_page_down) { selected += (editwinrows - fileline % editwinrows) * width; if (selected > filelist.size() - 1) { selected = filelist.size() - 1; } } else if (func == do_first_file) { selected = 0; } else if (func == do_last_file) { selected = filelist.size() - 1; } else if (func == goto_dir_void) { /* Go to a specific directory. */ curs_set(1); std::shared_ptr<Key> key; PromptResult i = do_prompt(true, false, MGOTODIR, key, ans.c_str(), NULL, browser_refresh, _("Go To Directory")); curs_set(0); bottombars(MBROWSER); /* If the directory begins with a newline (i.e. an * encoded null), treat it as though it's blank. */ if (i == PROMPT_ABORTED || i == PROMPT_BLANK_STRING || answer.front() == '\n') { /* We canceled. Indicate that on the statusbar, and * blank out ans, since we're done with it. */ statusbar(_("Cancelled")); ans = ""; func = nullptr; continue; } else if (i != PROMPT_ENTER_PRESSED) { /* Put back the "Go to Directory" key and save * answer in ans, so that the file list is displayed * again, the prompt is displayed again, and what we * typed before at the prompt is displayed again. */ ans = answer; func = goto_dir_void; continue; } /* We have a directory. Blank out ans, since we're done with it. */ ans = ""; /* Convert newlines to nulls, just before we go to the directory. */ sunder(answer); new_path = real_dir_from_tilde(answer); if (new_path == "") { new_path = path + answer; } dir = opendir(new_path); if (dir == NULL) { /* We can't open this directory for some reason. Complain. */ statusbar(_("Error reading %s: %s"), answer.c_str(), strerror(errno)); beep(); func = nullptr; continue; } /* Start over again with the new path value. */ path = new_path; goto change_browser_directory; } else if (func == do_up_void) { if (selected >= width) { selected -= width; } } else if (func == do_left) { if (selected > 0) { selected--; } } else if (func == do_down_void) { if (selected + width <= filelist.size() - 1) { selected += width; } } else if (func == do_right) { if (selected < filelist.size() - 1) { selected++; } } else if (func == do_enter_void) { /* We can't move up from "/". */ if (filelist[selected] == "/..") { statusbar(_("Can't move up a directory")); beep(); func = nullptr; continue; } if (stat(filelist[selected], &st) == -1) { /* We can't open this file for some reason. Complain. */ statusbar(_("Error reading %s: %s"), filelist[selected].c_str(), strerror(errno)); beep(); func = nullptr; continue; } if (!S_ISDIR(st.st_mode)) { /* We've successfully opened a file, we're done, so get out. */ retval = filelist[selected]; func = nullptr; break; } else if (tail(filelist[selected]) == "..") { /* We've successfully opened the parent directory, * save the current directory in prev_dir, so that * we can easily return to it by hitting Enter. */ prev_dir = striponedir(filelist[selected]); } dir = opendir(filelist[selected].c_str()); if (dir == NULL) { /* We can't open this directory for some reason. Complain. */ statusbar(_("Error reading %s: %s"), filelist[selected].c_str(), strerror(errno)); beep(); func = nullptr; continue; } path = filelist[selected]; /* Start over again with the new path value. */ goto change_browser_directory; } else if (func == do_exit) { /* Exit from the file browser. */ break; } func = nullptr; } if (kbinput) { delete kbinput; } titlebar(NULL); edit_refresh(); curs_set(1); if (old_const_update) { SET(CONST_UPDATE); } filelist.clear(); return retval; }