From 6173096be56f51d696bdd060be09e1cf2cf17690 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 | 6 +++ src/prompt.c | 3 ++ src/proto.h | 3 ++ src/rcfile.c | 1 + src/search.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 142 insertions(+), 7 deletions(-) diff --git a/src/global.c b/src/global.c index b13a65ac..53d6f31c 100644 --- a/src/global.c +++ b/src/global.c @@ -267,6 +267,12 @@ size_t length_of_list(int menu) #define TOGETHER FALSE /* Just throw this here. */ +void inc_search_void(void) +{ +} +void do_research_void(void) +{ +} void case_sens_void(void) { } @@ -635,6 +641,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 = @@ -821,6 +829,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); @@ -1278,6 +1293,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 44d71949..d00e5d04 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 SEARCH_FROM_START 1 +#define SEARCH_NEXT 2 /* Enumeration types. */ typedef enum { @@ -491,6 +496,7 @@ enum enum { DONTUSE, + INCREMENTAL_SEARCH, CASE_SENSITIVE, CONSTANT_SHOW, NO_HELP, diff --git a/src/prompt.c b/src/prompt.c index 025fd203..ae184977 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -570,6 +570,9 @@ functionptrtype acquire_an_answer(int *actual, bool allow_tabs, if (finished) break; + if (ISSET(INCREMENTAL_SEARCH) && currmenu == MWHEREIS) + search_text(answer, SEARCH_FROM_START); + update_the_statusbar(); #if defined(ENABLE_HISTORIES) && defined(ENABLE_TABCOMP) diff --git a/src/proto.h b/src/proto.h index 26f7d6d8..853af40d 100644 --- a/src/proto.h +++ b/src/proto.h @@ -493,6 +493,7 @@ void do_findprevious(void); void do_findnext(void); #endif void do_research(void); +void search_text(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); @@ -692,6 +693,8 @@ void do_credits(void); /* 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 b14192d5..08fb4211 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -94,6 +94,7 @@ static const rcoption rcopts[] = { {"casesensitive", CASE_SENSITIVE}, {"cut", CUT_FROM_CURSOR}, /* deprecated form, remove in 2020 */ {"cutfromcursor", CUT_FROM_CURSOR}, + {"incremental", INCREMENTAL_SEARCH}, {"justifytrim", TRIM_BLANKS}, /* deprecated form, remove in 2020 */ {"locking", LOCKING}, {"matchbrackets", 0}, diff --git a/src/search.c b/src/search.c index 4dff2f57..50534990 100644 --- a/src/search.c +++ b/src/search.c @@ -30,10 +30,13 @@ static bool came_full_circle = FALSE; /* Have we reached the starting line again while searching? */ static bool regexp_compiled = FALSE; /* Have we compiled any regular expressions? */ +static filestruct *saved_current; +static size_t saved_x; + /* Position to be restored because of 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 +47,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; @@ -76,6 +80,18 @@ void not_found_msg(const char *str) free(disp); } +/* 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 @@ -89,6 +105,61 @@ void search_replace_abort(void) regexp_cleanup(); } +/* Repostion 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; + openfile->placewewant = xplustabs(); + if(refresh) + edit_refresh(); +} + +/* Searches, highlights (or beeps) and positions the cursor. + * Possible modes are: + * - SEARCH_NEXT: searches for next occurrence or + * - SEARCH_FROM_START: search from the saved position. + * Return true if found, false otherwise. */ +void search_text(char *needle, int modus) +{ + filestruct *was_current = openfile->current; + size_t was_x = openfile->current_x; + /* Initial position for incremental search */ + + bool skipone = (modus == SEARCH_NEXT); + + bool didfind, was_full_circle = came_full_circle; + size_t len; + + if(modus == SEARCH_FROM_START) + reposition_cursor(saved_current, saved_x, FALSE); + + if (needle[0] == '\0') { + reposition_cursor(saved_current, saved_x, TRUE); + highlight(FALSE, 0); + return; + } + + if (ISSET(USE_REGEXP) && !regexp_init(needle, FALSE)) + return; + + came_full_circle = FALSE; + didfind = findnextstr(needle, FALSE, STEPWISE, &len, skipone, + openfile->current, openfile->current_x); + came_full_circle = was_full_circle; + + if (ISSET(USE_REGEXP)) + regexp_cleanup(); + + if (didfind) + highlight(TRUE, len); + else { + if(modus == SEARCH_FROM_START) + reposition_cursor(was_current, was_x, FALSE); + beep(); + } +} + /* 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. */ @@ -101,6 +172,9 @@ void search_init(bool replacing, bool keep_the_answer) if (!keep_the_answer) answer = mallocstrcpy(answer, NULL); + saved_current = openfile->current; + saved_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); @@ -120,7 +194,9 @@ void search_init(bool replacing, bool keep_the_answer) 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. */ + (ISSET(INCREMENTAL_SEARCH) && !replacing)? _(" [Incremental]") : "", /* TRANSLATORS: The next three modify the search prompt. */ ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : "", ISSET(USE_REGEXP) ? _(" [Regexp]") : "", @@ -129,7 +205,7 @@ 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); /* If the search was cancelled, or we have a blank answer and * nothing was searched for yet during this session, get out. */ @@ -137,6 +213,10 @@ void search_init(bool replacing, bool keep_the_answer) statusbar(_("Cancelled")); search_replace_abort(); free(thedefault); + + if(ISSET(INCREMENTAL_SEARCH)) + reposition_cursor(saved_current, saved_x, TRUE); + return; } @@ -154,13 +234,15 @@ void search_init(bool replacing, bool keep_the_answer) /* If doing a regular-expression search, compile the * search string, and get out when it's invalid. */ - if (ISSET(USE_REGEXP) && !regexp_init(last_search)) { + if (ISSET(USE_REGEXP) && !regexp_init(last_search, TRUE)) { search_replace_abort(); return; } if (replacing) ask_for_replacement(); + else if (ISSET(INCREMENTAL_SEARCH)) + highlight(FALSE, 0); else go_looking(); @@ -180,6 +262,17 @@ void search_init(bool replacing, bool keep_the_answer) TOGGLE(USE_REGEXP); } else if (func == flip_replace) { replacing = !replacing; + if (ISSET(INCREMENTAL_SEARCH)) { + reposition_cursor(saved_current, saved_x, TRUE); + if (!replacing) + search_text(answer, FALSE); + } + } else if (func == do_research_void) { + search_text(answer, SEARCH_NEXT); + } else if (func == inc_search_void) { + TOGGLE(INCREMENTAL_SEARCH); + if (!ISSET(INCREMENTAL_SEARCH)) + reposition_cursor(saved_current, saved_x, TRUE); } else { if (func == flip_goto) do_gotolinecolumn(openfile->current->lineno, @@ -188,6 +281,14 @@ void search_init(bool replacing, bool keep_the_answer) free(thedefault); return; } + + /* Handle some cases where we have to search again. */ + if(ISSET(INCREMENTAL_SEARCH) && (func == case_sens_void || + func == backwards_void || + func == regexp_void || + func == inc_search_void)) { + search_text(answer, SEARCH_FROM_START); + } } } @@ -237,7 +338,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...")); @@ -394,7 +495,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. */ @@ -420,6 +521,10 @@ void go_looking(void) didfind = findnextstr(last_search, FALSE, JUSTFIND, NULL, TRUE, openfile->current, openfile->current_x); + /* Unmark highlighted text. */ + if (didfind && ISSET(INCREMENTAL_SEARCH)) + highlight(FALSE, 0); + /* If we found something, and we're back at the exact same spot * where we started searching, then this is the only occurrence. */ if (didfind == 1 && openfile->current == was_current && -- 2.11.0