From 0661d4431f53161042d999f2efac9cf4eec2b89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Diego=20Aur=C3=A9lio=20Mesquita?= Date: Thu, 8 Feb 2018 00:44:27 -0200 Subject: [PATCH] Implement incremental search. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marco Diego Aurélio Mesquita --- src/global.c | 17 ++++++ src/nano.h | 8 ++- src/prompt.c | 49 +++++++++++++---- src/proto.h | 7 ++- src/rcfile.c | 3 +- src/search.c | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 231 insertions(+), 21 deletions(-) diff --git a/src/global.c b/src/global.c index 089d530f..43bdd729 100644 --- a/src/global.c +++ b/src/global.c @@ -266,6 +266,12 @@ size_t length_of_list(int menu) #define TOGETHER FALSE /* Empty functions, for the most part corresponding to toggles. */ +void inc_search_void(void) +{ +} +void do_research_void(void) +{ +} void case_sens_void(void) { } @@ -636,6 +642,8 @@ void shortcut_init(void) #endif const char *case_gist = N_("Toggle the case sensitivity of the search"); + const char *inc_gist = + N_("Toggle incremental search"); const char *reverse_gist = N_("Reverse the direction of the search"); const char *regexp_gist = @@ -824,6 +832,13 @@ void shortcut_init(void) add_to_funcs(flip_replace, MREPLACE, N_("No Replace"), WITHORSANS(whereis_gist), BLANKAFTER, VIEW); + add_to_funcs(inc_search_void, MWHEREIS, + N_("Incremental"), WITHORSANS(inc_gist), TOGETHER, VIEW); + + add_to_funcs(do_research_void, MWHEREIS, + whereisnext_tag, WITHORSANS(whereisnext_gist), TOGETHER, VIEW); + + #ifdef ENABLE_JUSTIFY add_to_funcs(do_full_justify, MWHEREIS, fulljustify_tag, WITHORSANS(fulljustify_gist), TOGETHER, NOVIEW); @@ -1286,6 +1301,8 @@ void shortcut_init(void) add_to_sclist(MWHEREIS|MREPLACE, "M-R", 0, regexp_void, 0); add_to_sclist(MWHEREIS|MREPLACE, "M-B", 0, backwards_void, 0); add_to_sclist(MWHEREIS|MREPLACE, "^R", 0, flip_replace, 0); + add_to_sclist(MWHEREIS, "M-I", 0, inc_search_void, 0); + add_to_sclist(MWHEREIS, "M-W", 0, do_research_void, 0); add_to_sclist(MWHEREIS|MREPLACE|MREPLACEWITH|MGOTOLINE|MFINDINHELP, "^Y", 0, to_first_line, 0); add_to_sclist(MWHEREIS|MREPLACE|MREPLACEWITH|MGOTOLINE|MFINDINHELP, "^V", 0, to_last_line, 0); #ifdef ENABLE_JUSTIFY diff --git a/src/nano.h b/src/nano.h index e05ae523..66b0e8b6 100644 --- a/src/nano.h +++ b/src/nano.h @@ -141,6 +141,11 @@ #define JUSTFIND 0 #define REPLACING 1 #define INREGION 2 +#define STEPWISE 3 + +/* Modes for incremental search. */ +#define FROM_START 1 +#define FROM_HERE 2 #ifdef ENABLE_COLOR #define USE_THE_DEFAULT -1 @@ -544,7 +549,8 @@ enum LINE_NUMBERS, NO_PAUSES, AT_BLANKS, - AFTER_ENDS + AFTER_ENDS, + INCREMENTAL_SEARCH }; /* Flags for the menus in which a given function should be present. */ diff --git a/src/prompt.c b/src/prompt.c index a07019cf..dcbd16b8 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -23,6 +23,8 @@ #include +static int incr_status = TRUE; + /* Status of incremental search while typing. */ static char *prompt = NULL; /* The prompt string used for statusbar questions. */ static size_t typing_x = HIGHEST_POSITIVE; @@ -43,7 +45,7 @@ int do_statusbar_mouse(void) if (click_row == 0 && click_col >= start_col) typing_x = actual_x(answer, get_statusbar_page_start(start_col, start_col + - strnlenpt(answer, typing_x)) + click_col - start_col); + strnlenpt(answer, typing_x), 0) + click_col - start_col); } return retval; @@ -361,12 +363,13 @@ void do_statusbar_uncut_text(void) * displayed in the statusbar when the cursor is at the given column, * with the available room for the answer starting at base. Note that * (0 <= column - get_statusbar_page_start(column) < COLS). */ -size_t get_statusbar_page_start(size_t base, size_t column) +size_t get_statusbar_page_start(size_t base, size_t column, size_t right_margin) { - if (column == base || column < COLS - 1) + size_t margin = COLS - right_margin; + if (column == base || column < margin - 1) return 0; - else if (COLS > base + 2) - return column - base - 1 - (column - base - 1) % (COLS - base - 2); + else if (margin > base + 2) + return column - base - 1 - (column - base - 1) % (margin - base - 2); else return column - 2; } @@ -380,17 +383,35 @@ void put_cursor_at_end_of_answer(void) /* Redraw the promptbar and place the cursor at the right spot. */ void draw_the_promptbar(void) { + char *found_msg = ""; + int color_pair = interface_color_pair[TITLE_BAR]; + if (ISSET(INCREMENTAL_SEARCH) && currmenu == MWHEREIS) { + if (incr_status == FALSE) { + found_msg = " NOT_FOUND "; + color_pair = interface_color_pair[ERROR_MESSAGE]; + } else if (incr_status == -1) { + found_msg = " BAD_REGEX "; + color_pair = interface_color_pair[ERROR_MESSAGE]; + } + } + + const size_t found_msg_len = strlenpt(found_msg); size_t base = strlenpt(prompt) + 2; size_t the_page, end_page, column; char *expanded; - the_page = get_statusbar_page_start(base, base + strnlenpt(answer, typing_x)); - end_page = get_statusbar_page_start(base, base + strlenpt(answer) - 1); + the_page = get_statusbar_page_start(base, + base + strnlenpt(answer, typing_x), + found_msg_len - 2); + end_page = get_statusbar_page_start(base, + base + strlenpt(answer) - 1, + found_msg_len - 2); /* Color the promptbar over its full width. */ - wattron(bottomwin, interface_color_pair[TITLE_BAR]); + wattron(bottomwin, color_pair); blank_statusbar(); + mvwaddstr(bottomwin, 0, COLS - found_msg_len, found_msg); mvwaddstr(bottomwin, 0, 0, prompt); waddch(bottomwin, ':'); waddch(bottomwin, (the_page == 0) ? ' ' : '<'); @@ -401,7 +422,7 @@ void draw_the_promptbar(void) waddch(bottomwin, (the_page >= end_page) ? ' ' : '>'); - wattroff(bottomwin, interface_color_pair[TITLE_BAR]); + wattroff(bottomwin, color_pair); /* Work around a cursor-misplacement bug in VTEs. */ wmove(bottomwin, 0, 0); @@ -409,7 +430,9 @@ void draw_the_promptbar(void) /* Place the cursor at the right spot. */ column = base + strnlenpt(answer, typing_x); - wmove(bottomwin, 0, column - get_statusbar_page_start(base, column)); + wmove(bottomwin, 0, column - get_statusbar_page_start(base, + column, + found_msg_len - 2)); wnoutrefresh(bottomwin); } @@ -565,6 +588,11 @@ functionptrtype acquire_an_answer(int *actual, bool allow_tabs, if (finished) break; + draw_the_promptbar(); + + if (ISSET(INCREMENTAL_SEARCH) && currmenu == MWHEREIS) + incr_status = advance_to(answer, FROM_START); + #if defined(ENABLE_HISTORIES) && defined(ENABLE_TABCOMP) last_kbinput = kbinput; #endif @@ -598,6 +626,7 @@ int do_prompt(bool allow_tabs, bool allow_files, int menu, const char *curranswer, filestruct **history_list, void (*refresh_func)(void), const char *msg, ...) { + incr_status = TRUE; va_list ap; int retval; functionptrtype func = NULL; diff --git a/src/proto.h b/src/proto.h index b2edf4d0..72f90a59 100644 --- a/src/proto.h +++ b/src/proto.h @@ -461,7 +461,7 @@ void do_statusbar_prev_word(void); void do_statusbar_next_word(void); #endif void do_statusbar_verbatim_input(void); -size_t get_statusbar_page_start(size_t start_col, size_t column); +size_t get_statusbar_page_start(size_t start_col, size_t column, size_t right_margin); void put_cursor_at_end_of_answer(void); void add_or_remove_pipe_symbol_from_answer(void); int do_prompt(bool allow_tabs, bool allow_files, @@ -491,6 +491,8 @@ void do_findprevious(void); void do_findnext(void); #endif void not_found_msg(const char *str); +void do_research(void); +int advance_to(char *needle, int modus); void go_looking(void); ssize_t do_replace_loop(const char *needle, bool whole_word_only, const filestruct *real_current, size_t *real_current_x); @@ -691,6 +693,9 @@ void do_credits(void); #endif /* These are just name definitions. */ +void do_cancel(void); +void inc_search_void(void); +void do_research_void(void); void case_sens_void(void); void regexp_void(void); void backwards_void(void); diff --git a/src/rcfile.c b/src/rcfile.c index 42fc84a1..25b0be29 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -98,7 +98,8 @@ static const rcoption rcopts[] = { {"casesensitive", CASE_SENSITIVE}, {"cut", CUT_FROM_CURSOR}, /* deprecated form, remove end of 2018 */ {"cutfromcursor", CUT_FROM_CURSOR}, - {"justifytrim", TRIM_BLANKS}, /* deprecated form, remove end of 2018 */ + {"incremental", INCREMENTAL_SEARCH}, + {"justifytrim", TRIM_BLANKS}, /* deprecated form, remove in 2020 */ {"locking", LOCKING}, {"matchbrackets", 0}, {"noconvert", NO_CONVERT}, diff --git a/src/search.c b/src/search.c index 79b315b2..c6fa7c82 100644 --- a/src/search.c +++ b/src/search.c @@ -30,10 +30,17 @@ static bool came_full_circle = FALSE; /* Have we reached the starting line again while searching? */ static bool have_compiled_regexp = FALSE; /* Whether we have compiled a regular expression for the search. */ +static bool found = FALSE; + /* Whether last incremental search was found. */ +static bool search_performed = FALSE; + /* Whether last incremental search was performed (in case user types too fast. */ +static filestruct *starting_line; +static size_t starting_x; + /* The position where the user started an incremental search. */ /* Compile the given regular expression and store it in search_regexp. * Return TRUE if the expression is valid, and FALSE otherwise. */ -bool regexp_init(const char *regexp) +bool regexp_init(const char *regexp, bool should_alert) { int value = regcomp(&search_regexp, fixbounds(regexp), NANO_REG_EXTENDED | (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE)); @@ -44,7 +51,8 @@ bool regexp_init(const char *regexp) char *str = charalloc(len); regerror(value, &search_regexp, str, len); - statusline(ALERT, _("Bad regex \"%s\": %s"), regexp, str); + if (should_alert) + statusline(ALERT, _("Bad regex \"%s\": %s"), regexp, str); free(str); return FALSE; @@ -63,24 +71,126 @@ void tidy_up_after_search(void) regfree(&search_regexp); have_compiled_regexp = FALSE; } +} + +/* Highlights len cols of text from current cursor position. */ +void highlight(bool active, size_t len) +{ + size_t from_col = xplustabs(); + size_t to_col = strnlenpt(openfile->current->data, + openfile->current_x + len); + + edit_refresh(); + if (len) + spotlight(active, from_col, to_col); +} + +/* Abort the current search or replace. Clean up by displaying the main + * shortcut list, updating the screen if the mark was on before, and + * decompiling the compiled regular expression we used in the last + * search, if any. */ +void search_replace_abort(void) +{ #ifndef NANO_TINY if (openfile->mark) refresh_needed = TRUE; #endif } +/* Reposition the cursor */ +void reposition_cursor(filestruct *was_current, size_t was_current_x, bool refresh) +{ + openfile->current = was_current; + openfile->current_x = was_current_x; + if (refresh) + edit_refresh(); +} + +/* Search for the needle and highlight it, or beep when not found. + * Possible modes are: + * - FROM_START: search from the original position; + * - FROM_HERE: search for the next occurrence. + * Return TRUE if found, -1 on error and FALSE otherwise. */ +int advance_to(char *needle, int modus) +{ + filestruct *was_line = openfile->current; + size_t was_x = openfile->current_x; + bool didfind; + size_t len; + int got_char; + + static bool just_updated = TRUE; + search_performed = FALSE; + + if (needle[0] == '\0') { + if(!just_updated) + reposition_cursor(starting_line, starting_x, TRUE); + just_updated = TRUE; + return TRUE; + } + + just_updated = FALSE; + + if (modus == FROM_START /*&& FALSE*/) { + reposition_cursor(starting_line, starting_x, FALSE); + + /* Do not search if user is typing fast. */ + wtimeout(topwin, 250); + got_char = wgetch(topwin); + wtimeout(topwin, 0); + if(got_char != ERR) { + put_back(got_char); + return TRUE; + } + } + + if (ISSET(USE_REGEXP) && !regexp_init(needle, FALSE)) { + reposition_cursor(was_line, was_x, FALSE); + highlight(FALSE, 0); + beep(); + return -1; + } + + came_full_circle = FALSE; + didfind = findnextstr(needle, FALSE, STEPWISE, &len, TRUE, + openfile->current, openfile->current_x); + + search_performed = TRUE; + + if (didfind) + highlight(TRUE, len); + else { + reposition_cursor(was_line, was_x, FALSE); + highlight(FALSE, 0); + beep(); + } + + found = didfind; + return didfind; +} + /* Prepare the prompt and ask the user what to search for. Keep looping * as long as the user presses a toggle, and only take action and exit * when is pressed or a non-toggle shortcut was executed. */ void search_init(bool replacing, bool keep_the_answer) { + static int initial_incremental = -1; + if(initial_incremental == -1) + initial_incremental = ISSET(INCREMENTAL_SEARCH); + + found = TRUE; + char *thedefault; /* What will be searched for when the user typed nothing. */ + bool didnext = FALSE; /* When starting a new search, clear the current answer. */ if (!keep_the_answer) answer = mallocstrcpy(answer, NULL); + starting_line = openfile->current; + starting_x = openfile->current_x; + /* If something was searched for earlier, include it in the prompt. */ if (*last_search != '\0') { char *disp = display_string(last_search, 0, COLS / 3, FALSE); @@ -95,12 +205,20 @@ void search_init(bool replacing, bool keep_the_answer) while (TRUE) { functionptrtype func; + + const char *incremental_msg = (initial_incremental == ISSET(INCREMENTAL_SEARCH)) + ? NULL + : ISSET(INCREMENTAL_SEARCH) ? " [Incremental]" + : " [Non-incremental]"; + /* Ask the user what to search for (or replace). */ int i = do_prompt(FALSE, FALSE, inhelp ? MFINDINHELP : (replacing ? MREPLACE : MWHEREIS), answer, &search_history, /* TRANSLATORS: This is the main search prompt. */ - edit_refresh, "%s%s%s%s%s%s", _("Search"), + edit_refresh, "%s%s%s%s%s%s%s", _("Search"), + /* TRANSLATORS: The next three modify the search prompt. */ + (incremental_msg && !replacing)? incremental_msg : "", /* TRANSLATORS: The next three modify the search prompt. */ ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : "", ISSET(USE_REGEXP) ? _(" [Regexp]") : "", @@ -109,7 +227,9 @@ void search_init(bool replacing, bool keep_the_answer) /* TRANSLATORS: The next two modify the search prompt. */ openfile->mark ? _(" (to replace) in selection") : #endif - _(" (to replace)") : "", thedefault); + _(" (to replace)") : "", ISSET(INCREMENTAL_SEARCH) ? "" : thedefault); + + highlight(FALSE, 0); /* If the search was cancelled, or we have a blank answer and * nothing was searched for yet during this session, get out. */ @@ -117,6 +237,10 @@ void search_init(bool replacing, bool keep_the_answer) statusbar(_("Cancelled")); tidy_up_after_search(); free(thedefault); + + if (ISSET(INCREMENTAL_SEARCH)) + reposition_cursor(starting_line, starting_x, TRUE); + return; } @@ -133,19 +257,37 @@ void search_init(bool replacing, bool keep_the_answer) /* When not doing a regular-expression search, just search; * otherwise compile the search string, and only search when * the expression is valid. */ - if (!ISSET(USE_REGEXP) || regexp_init(last_search)) { + if (!ISSET(USE_REGEXP) || regexp_init(last_search, FALSE)) { if (replacing) ask_for_replacement(); - else + else if (!ISSET(INCREMENTAL_SEARCH) && !didnext) go_looking(); } + if (replacing) + ask_for_replacement(); + else if (!ISSET(INCREMENTAL_SEARCH) && !didnext) + go_looking(); + else if (ISSET(INCREMENTAL_SEARCH) && !found) + reposition_cursor(starting_line, starting_x, TRUE); + else if (!didnext && answer[0] != '\0' && !found) + go_looking(); + else if (ISSET(INCREMENTAL_SEARCH) && !search_performed) { + /* Enter was pressed but no search was performed. */ + /* Probable cause is user typed too fast. */ + go_looking(); + } + + openfile->placewewant = xplustabs(); + search_replace_abort(); + tidy_up_after_search(); free(thedefault); return; } func = func_from_key(&i); + didnext = FALSE; /* If we're here, one of the five toggles was pressed, or * a shortcut was executed. */ @@ -157,6 +299,11 @@ void search_init(bool replacing, bool keep_the_answer) TOGGLE(USE_REGEXP); } else if (func == flip_replace) { replacing = !replacing; + } else if (func == inc_search_void) { + TOGGLE(INCREMENTAL_SEARCH); + } else if (func == do_research_void) { + found = advance_to(answer, FROM_HERE); + didnext = TRUE; } else { if (func == flip_goto) do_gotolinecolumn(openfile->current->lineno, @@ -165,6 +312,11 @@ void search_init(bool replacing, bool keep_the_answer) free(thedefault); return; } + + if (ISSET(INCREMENTAL_SEARCH) && !replacing && !didnext) + found = advance_to(answer, FROM_START); + else if (!didnext) + reposition_cursor(starting_line, starting_x, TRUE); } } @@ -214,7 +366,7 @@ int findnextstr(const char *needle, bool whole_word_only, int modus, input = parse_kbinput(NULL); } - if (++feedback > 0) + if (++feedback > 0 && modus != STEPWISE) /* TRANSLATORS: This is shown when searching takes * more than half a second. */ statusbar(_("Searching...")); @@ -355,7 +507,7 @@ void do_research(void) return; } - if (ISSET(USE_REGEXP) && !regexp_init(last_search)) + if (ISSET(USE_REGEXP) && !regexp_init(last_search, TRUE)) return; /* Use the search-menu key bindings, to allow cancelling. */ -- 2.11.0