diff --git a/src/global.c b/src/global.c index 041b09c2..b028af53 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) { } @@ -637,6 +643,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 = @@ -823,6 +831,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); @@ -1280,6 +1295,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 5c25de0b..224c71bb 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 /* Enumeration types. */ typedef enum { @@ -534,7 +539,8 @@ enum SHOW_CURSOR, LINE_NUMBERS, NO_PAUSES, - AT_BLANKS + AT_BLANKS, + 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 3519a548..cd51773a 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -43,7 +43,7 @@ int do_statusbar_mouse(void) if (click_row == 0 && click_col >= start_col) { statusbar_x = actual_x(answer, get_statusbar_page_start(start_col, start_col + - statusbar_xplustabs()) + click_col - start_col); + statusbar_xplustabs(), 0) + click_col - start_col); update_the_statusbar(); } } @@ -385,12 +385,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; } @@ -401,25 +402,45 @@ void reinit_statusbar_x(void) statusbar_x = HIGHEST_POSITIVE; } +int found_ = TRUE; + /* Redraw the promptbar and place the cursor at the right spot. */ void update_the_statusbar(void) { - size_t base = strlenpt(prompt) + 2; + /*const char *found_msg = (ISSET(INCREMENTAL_SEARCH) && + currmenu == MWHEREIS && + found_ == FALSE) ? " [NOT FOUND]" : "";*/ + char *found_msg = ""; + if (ISSET(INCREMENTAL_SEARCH) && currmenu == MWHEREIS) { + if (found_ == FALSE) + found_msg = " [NOT_FOUND]"; + else if (found_ == -1) + found_msg = " [BAD_REGEX]"; + } + + const size_t found_msg_len = strlenpt(found_msg); + size_t old_base = strlenpt(prompt) + 2;// + found_msg_len; + size_t base = strlenpt(prompt) + found_msg_len; size_t the_page, end_page, column; char *expanded; - the_page = get_statusbar_page_start(base, base + strnlenpt(answer, statusbar_x)); - end_page = get_statusbar_page_start(base, base + strlenpt(answer) - 1); + the_page = get_statusbar_page_start(old_base, + old_base + strnlenpt(answer, statusbar_x), + found_msg_len - 2); + end_page = get_statusbar_page_start(old_base, + old_base + strlenpt(answer) - 1, + found_msg_len - 2); /* Color the promptbar over its full width. */ wattron(bottomwin, interface_color_pair[TITLE_BAR]); blank_statusbar(); + mvwaddstr(bottomwin, 0, COLS - found_msg_len, found_msg); mvwaddstr(bottomwin, 0, 0, prompt); waddch(bottomwin, ':'); waddch(bottomwin, (the_page == 0) ? ' ' : '<'); - expanded = display_string(answer, the_page, COLS - base - 1, FALSE); + expanded = display_string(answer, the_page, COLS - old_base - 1, FALSE); waddstr(bottomwin, expanded); free(expanded); @@ -432,8 +453,8 @@ void update_the_statusbar(void) wrefresh(bottomwin); /* Place the cursor at statusbar_x in the answer. */ - column = base + statusbar_xplustabs(); - wmove(bottomwin, 0, column - get_statusbar_page_start(base, column)); + column = old_base + statusbar_xplustabs(); + wmove(bottomwin, 0, column - get_statusbar_page_start(old_base, column, found_msg_len - 2)); wnoutrefresh(bottomwin); } @@ -574,6 +595,11 @@ functionptrtype acquire_an_answer(int *actual, bool allow_tabs, update_the_statusbar(); + if (ISSET(INCREMENTAL_SEARCH) && currmenu == MWHEREIS) + found_ = advance_to(answer, FROM_START); + + update_the_statusbar(); + #if defined(ENABLE_HISTORIES) && defined(ENABLE_TABCOMP) last_kbinput = kbinput; #endif @@ -607,6 +633,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, ...) { + found_ = TRUE; va_list ap; int retval; functionptrtype func = NULL; diff --git a/src/proto.h b/src/proto.h index 26f7d6d8..b629b610 100644 --- a/src/proto.h +++ b/src/proto.h @@ -462,7 +462,7 @@ void do_statusbar_next_word(void); #endif void do_statusbar_verbatim_input(void); size_t statusbar_xplustabs(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 reinit_statusbar_x(void); void update_the_statusbar(void); int do_prompt(bool allow_tabs, bool allow_files, @@ -493,6 +493,7 @@ void do_findprevious(void); void do_findnext(void); #endif 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); @@ -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 23187335..82509a94 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..a8ffd995 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 *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 +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,18 +105,111 @@ void search_replace_abort(void) regexp_cleanup(); } +/* 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(); +} + +bool found = FALSE; + /* Whether last incremental search was found. */ + + +/* 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, and FALSE otherwise. */ +int advance_to(char *needle, int modus) +{ + //fprintf(stderr, "%d [%s]\n", modus, needle); + //return TRUE; + 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; + + 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) { + reposition_cursor(starting_line, starting_x, FALSE); + + /* Do not search if user is typing fast. */ + wtimeout(topwin, 250); + //timeout(250); + //total_refresh(); + //halfdelay(2); + //refresh(); + //total_refresh(); + //wrefresh(topwin); + //noecho(); + got_char = wgetch(topwin); + //halfdelay(0); + 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); + + 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); @@ -115,12 +224,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]") : "", @@ -129,7 +246,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. */ @@ -137,6 +256,10 @@ void search_init(bool replacing, bool keep_the_answer) statusbar(_("Cancelled")); search_replace_abort(); free(thedefault); + + if (ISSET(INCREMENTAL_SEARCH)) + reposition_cursor(starting_line, starting_x, TRUE); + return; } @@ -154,21 +277,29 @@ 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 + 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(); + //fprintf(stderr, "Answer: %s\n", answer); + + openfile->placewewant = xplustabs(); search_replace_abort(); return; } func = func_from_key(&i); + didnext = FALSE; /* If we're here, one of the five toggles was pressed, or * a shortcut was executed. */ @@ -180,6 +311,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, @@ -188,6 +324,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); } } @@ -237,7 +378,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 +535,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. */