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