diff options
Diffstat (limited to 'plugins/history-substring-search/history-substring-search.zsh')
-rw-r--r-- | plugins/history-substring-search/history-substring-search.zsh | 510 |
1 files changed, 342 insertions, 168 deletions
diff --git a/plugins/history-substring-search/history-substring-search.zsh b/plugins/history-substring-search/history-substring-search.zsh index 3b8afd317..a791cc4da 100644 --- a/plugins/history-substring-search/history-substring-search.zsh +++ b/plugins/history-substring-search/history-substring-search.zsh @@ -6,6 +6,8 @@ # Copyright (c) 2011 Suraj N. Kurapati # Copyright (c) 2011 Sorin Ionescu # Copyright (c) 2011 Vincent Guerci +# Copyright (c) 2016 Geza Lore +# Copyright (c) 2017 Bengt Brodersen # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -38,12 +40,30 @@ ############################################################################## #----------------------------------------------------------------------------- -# configuration variables +# declare global configuration variables #----------------------------------------------------------------------------- -HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='bg=magenta,fg=white,bold' -HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='bg=red,fg=white,bold' -HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS='i' +typeset -g HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='bg=magenta,fg=white,bold' +typeset -g HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='bg=red,fg=white,bold' +typeset -g HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS='i' +typeset -g HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE='' +typeset -g HISTORY_SUBSTRING_SEARCH_FUZZY='' + +#----------------------------------------------------------------------------- +# declare internal global variables +#----------------------------------------------------------------------------- + +typeset -g BUFFER MATCH MBEGIN MEND CURSOR +typeset -g _history_substring_search_refresh_display +typeset -g _history_substring_search_query_highlight +typeset -g _history_substring_search_result +typeset -g _history_substring_search_query +typeset -g -a _history_substring_search_query_parts +typeset -g -a _history_substring_search_raw_matches +typeset -g -i _history_substring_search_raw_match_index +typeset -g -a _history_substring_search_matches +typeset -g -i _history_substring_search_match_index +typeset -g -A _history_substring_search_unique_filter #----------------------------------------------------------------------------- # the main ZLE widgets @@ -180,62 +200,104 @@ _history-substring-search-begin() { _history_substring_search_query_highlight= # - # Continue using the previous $_history_substring_search_result by default, - # unless the current query was cleared or a new/different query was entered. + # If the buffer is the same as the previously displayed history substring + # search result, then just keep stepping through the match list. Otherwise + # start a new search. # - if [[ -z $BUFFER || $BUFFER != $_history_substring_search_result ]]; then - # - # For the purpose of highlighting we will also keep - # a version without doubly-escaped meta characters. - # - _history_substring_search_query=$BUFFER + if [[ -n $BUFFER && $BUFFER == ${_history_substring_search_result:-} ]]; then + return; + fi + # + # Clear the previous result. + # + _history_substring_search_result='' + + if [[ -z $BUFFER ]]; then # - # $BUFFER contains the text that is in the command-line currently. - # we put an extra "\\" before meta characters such as "\(" and "\)", - # so that they become "\\\(" and "\\\)". + # If the buffer is empty, we will just act like up-history/down-history + # in ZSH, so we do not need to actually search the history. This should + # speed things up a little. # - _history_substring_search_query_escaped=${BUFFER//(#m)[\][()|\\*?#<>~^]/\\$MATCH} + _history_substring_search_query= + _history_substring_search_query_parts=() + _history_substring_search_raw_matches=() + else # - # Find all occurrences of the search query in the history file. - # - # (k) returns the "keys" (history index numbers) instead of the values - # (Oa) reverses the order, because (R) returns results reversed. + # For the purpose of highlighting we keep a copy of the original + # query string. # - _history_substring_search_matches=(${(kOa)history[(R)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)*${_history_substring_search_query_escaped}*]}) + _history_substring_search_query=$BUFFER # - # Define the range of values that $_history_substring_search_match_index - # can take: [0, $_history_substring_search_matches_count_plus]. + # compose search pattern # - _history_substring_search_matches_count=$#_history_substring_search_matches - _history_substring_search_matches_count_plus=$(( _history_substring_search_matches_count + 1 )) - _history_substring_search_matches_count_sans=$(( _history_substring_search_matches_count - 1 )) + if [[ -n $HISTORY_SUBSTRING_SEARCH_FUZZY ]]; then + # + # `=` split string in arguments + # + _history_substring_search_query_parts=(${=_history_substring_search_query}) + else + _history_substring_search_query_parts=(${==_history_substring_search_query}) + fi # - # If $_history_substring_search_match_index is equal to - # $_history_substring_search_matches_count_plus, this indicates that we - # are beyond the beginning of $_history_substring_search_matches. + # Escape and join query parts with wildcard character '*' as seperator + # `(j:CHAR:)` join array to string with CHAR as seperator # - # If $_history_substring_search_match_index is equal to 0, this indicates - # that we are beyond the end of $_history_substring_search_matches. + local search_pattern="*${(j:*:)_history_substring_search_query_parts[@]//(#m)[\][()|\\*?#<>~^]/\\$MATCH}*" + # - # If we have initially pressed "up" we have to initialize - # $_history_substring_search_match_index to - # $_history_substring_search_matches_count_plus so that it will be - # decreased to $_history_substring_search_matches_count. + # Find all occurrences of the search pattern in the history file. # - # If we have initially pressed "down" we have to initialize - # $_history_substring_search_match_index to - # $_history_substring_search_matches_count so that it will be increased to - # $_history_substring_search_matches_count_plus. + # (k) returns the "keys" (history index numbers) instead of the values + # (R) returns values in reverse older, so the index of the youngest + # matching history entry is at the head of the list. # - if [[ $WIDGET == history-substring-search-down ]]; then - _history_substring_search_match_index=$_history_substring_search_matches_count - else - _history_substring_search_match_index=$_history_substring_search_matches_count_plus - fi + _history_substring_search_raw_matches=(${(k)history[(R)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)${search_pattern}]}) + fi + + # + # In order to stay as responsive as possible, we will process the raw + # matches lazily (when the user requests the next match) to choose items + # that need to be displayed to the user. + # _history_substring_search_raw_match_index holds the index of the last + # unprocessed entry in _history_substring_search_raw_matches. Any items + # that need to be displayed will be added to + # _history_substring_search_matches. + # + # We use an associative array (_history_substring_search_unique_filter) as + # a 'set' data structure to ensure uniqueness of the results if desired. + # If an entry (key) is in the set (non-empty value), then we have already + # added that entry to _history_substring_search_matches. + # + _history_substring_search_raw_match_index=0 + _history_substring_search_matches=() + _history_substring_search_unique_filter=() + + # + # If $_history_substring_search_match_index is equal to + # $#_history_substring_search_matches + 1, this indicates that we + # are beyond the end of $_history_substring_search_matches and that we + # have also processed all entries in + # _history_substring_search_raw_matches. + # + # If $#_history_substring_search_match_index is equal to 0, this indicates + # that we are beyond the beginning of $_history_substring_search_matches. + # + # If we have initially pressed "up" we have to initialize + # $_history_substring_search_match_index to 0 so that it will be + # incremented to 1. + # + # If we have initially pressed "down" we have to initialize + # $_history_substring_search_match_index to 1 so that it will be + # decremented to 0. + # + if [[ $WIDGET == history-substring-search-down ]]; then + _history_substring_search_match_index=1 + else + _history_substring_search_match_index=0 fi } @@ -255,16 +317,21 @@ _history-substring-search-end() { _zsh_highlight # highlight the search query inside the command line - if [[ -n $_history_substring_search_query_highlight && -n $_history_substring_search_query ]]; then - # - # The following expression yields a variable $MBEGIN, which - # indicates the begin position + 1 of the first occurrence - # of _history_substring_search_query_escaped in $BUFFER. - # - : ${(S)BUFFER##(#m$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)($_history_substring_search_query##)} - local begin=$(( MBEGIN - 1 )) - local end=$(( begin + $#_history_substring_search_query )) - region_highlight+=("$begin $end $_history_substring_search_query_highlight") + if [[ -n $_history_substring_search_query_highlight ]]; then + # highlight first matching query parts + local highlight_start_index=0 + local highlight_end_index=0 + local query_part + for query_part in $_history_substring_search_query_parts; do + local escaped_query_part=${query_part//(#m)[\][()|\\*?#<>~^]/\\$MATCH} + # (i) get index of pattern + local query_part_match_index="${${BUFFER:$highlight_start_index}[(i)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)${escaped_query_part}]}" + if [[ $query_part_match_index -le ${#BUFFER:$highlight_start_index} ]]; then + highlight_start_index=$(( $highlight_start_index + $query_part_match_index )) + highlight_end_index=$(( $highlight_start_index + ${#query_part} )) + region_highlight+=("$(($highlight_start_index - 1)) $(($highlight_end_index - 1)) $_history_substring_search_query_highlight") + fi + done fi # For debugging purposes: @@ -378,12 +445,143 @@ _history-substring-search-down-history() { return 1 } +_history_substring_search_process_raw_matches() { + # + # Process more outstanding raw matches and append any matches that need to + # be displayed to the user to _history_substring_search_matches. + # Return whether there were any more results appended. + # + + # + # While we have more raw matches. Process them to see if there are any more + # matches that need to be displayed to the user. + # + while [[ $_history_substring_search_raw_match_index -lt $#_history_substring_search_raw_matches ]]; do + # + # Move on to the next raw entry and get its history index. + # + _history_substring_search_raw_match_index+=1 + local index=${_history_substring_search_raw_matches[$_history_substring_search_raw_match_index]} + + # + # If HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set to a non-empty value, + # then ensure that only unique matches are presented to the user. + # When HIST_IGNORE_ALL_DUPS is set, ZSH already ensures a unique history, + # so in this case we do not need to do anything. + # + if [[ ! -o HIST_IGNORE_ALL_DUPS && -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then + # + # Get the actual history entry at the new index, and check if we have + # already added it to _history_substring_search_matches. + # + local entry=${history[$index]} + + if [[ -z ${_history_substring_search_unique_filter[$entry]} ]]; then + # + # This is a new unique entry. Add it to the filter and append the + # index to _history_substring_search_matches. + # + _history_substring_search_unique_filter[$entry]=1 + _history_substring_search_matches+=($index) + + # + # Indicate that we did find a match. + # + return 0 + fi + + else + # + # Just append the new history index to the processed matches. + # + _history_substring_search_matches+=($index) + + # + # Indicate that we did find a match. + # + return 0 + fi + + done + + # + # We are beyond the end of the list of raw matches. Indicate that no + # more matches are available. + # + return 1 +} + +_history-substring-search-has-next() { + # + # Predicate function that returns whether any more older matches are + # available. + # + + if [[ $_history_substring_search_match_index -lt $#_history_substring_search_matches ]]; then + # + # We did not reach the end of the processed list, so we do have further + # matches. + # + return 0 + + else + # + # We are at the end of the processed list. Try to process further + # unprocessed matches. _history_substring_search_process_raw_matches + # returns whether any more matches were available, so just return + # that result. + # + _history_substring_search_process_raw_matches + return $? + fi +} + +_history-substring-search-has-prev() { + # + # Predicate function that returns whether any more younger matches are + # available. + # + + if [[ $_history_substring_search_match_index -gt 1 ]]; then + # + # We did not reach the beginning of the processed list, so we do have + # further matches. + # + return 0 + + else + # + # We are at the beginning of the processed list. We do not have any more + # matches. + # + return 1 + fi +} + +_history-substring-search-found() { + # + # A match is available. The index of the match is held in + # $_history_substring_search_match_index + # + # 1. Make $BUFFER equal to the matching history entry. + # + # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND + # to highlight the current buffer. + # + BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] + _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND +} + _history-substring-search-not-found() { # - # Nothing matched the search query, so put it back into the $BUFFER while - # highlighting it accordingly so the user can revise it and search again. + # No more matches are available. + # + # 1. Make $BUFFER equal to $_history_substring_search_query so the user can + # revise it and search again. + # + # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND + # to highlight the current buffer. # - _history_substring_search_old_buffer=$BUFFER BUFFER=$_history_substring_search_query _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND } @@ -392,91 +590,84 @@ _history-substring-search-up-search() { _history_substring_search_refresh_display=1 # - # Highlight matches during history-substring-up-search: + # Select history entry during history-substring-down-search: # - # The following constants have been initialized in + # The following variables have been initialized in # _history-substring-search-up/down-search(): # - # $_history_substring_search_matches is the current list of matches - # $_history_substring_search_matches_count is the current number of matches - # $_history_substring_search_matches_count_plus is the current number of matches + 1 - # $_history_substring_search_matches_count_sans is the current number of matches - 1 + # $_history_substring_search_matches is the current list of matches that + # need to be displayed to the user. # $_history_substring_search_match_index is the index of the current match + # that is being displayed to the user. # # The range of values that $_history_substring_search_match_index can take - # is: [0, $_history_substring_search_matches_count_plus]. A value of 0 - # indicates that we are beyond the end of + # is: [0, $#_history_substring_search_matches + 1]. A value of 0 + # indicates that we are beyond the beginning of # $_history_substring_search_matches. A value of - # $_history_substring_search_matches_count_plus indicates that we are beyond - # the beginning of $_history_substring_search_matches. + # $#_history_substring_search_matches + 1 indicates that we are beyond + # the end of $_history_substring_search_matches and that we have also + # processed all entries in _history_substring_search_raw_matches. + # + # If $_history_substring_search_match_index equals + # $#_history_substring_search_matches and + # $_history_substring_search_raw_match_index is not greater than + # $#_history_substring_search_raw_matches, then we need to further process + # $_history_substring_search_raw_matches to see if there are any more + # entries that need to be displayed to the user. # # In _history-substring-search-up-search() the initial value of - # $_history_substring_search_match_index is - # $_history_substring_search_matches_count_plus. This value is set in - # _history-substring-search-begin(). _history-substring-search-up-search() - # will initially decrease it to $_history_substring_search_matches_count. + # $_history_substring_search_match_index is 0. This value is set in + # _history-substring-search-begin(). _history-substring-search-up-search() + # will initially increment it to 1. # - if [[ $_history_substring_search_match_index -ge 2 ]]; then - # - # Highlight the next match: - # - # 1. Decrease the value of $_history_substring_search_match_index. - # - # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND - # to highlight the current buffer. - # - (( _history_substring_search_match_index-- )) - BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] - _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND - elif [[ $_history_substring_search_match_index -eq 1 ]]; then - # - # We will move beyond the end of $_history_substring_search_matches: - # - # 1. Decrease the value of $_history_substring_search_match_index. - # - # 2. Save the current buffer in $_history_substring_search_old_buffer, - # so that it can be retrieved by - # _history-substring-search-down-search() later. + if [[ $_history_substring_search_match_index -gt $#_history_substring_search_matches ]]; then # - # 3. Make $BUFFER equal to $_history_substring_search_query. + # We are beyond the end of $_history_substring_search_matches. This + # can only happen if we have also exhausted the unprocessed matches in + # _history_substring_search_raw_matches. # - # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND - # to highlight the current buffer. + # 1. Update display to indicate search not found. # - (( _history_substring_search_match_index-- )) _history-substring-search-not-found + return + fi - elif [[ $_history_substring_search_match_index -eq $_history_substring_search_matches_count_plus ]]; then - # - # We were beyond the beginning of $_history_substring_search_matches but - # UP makes us move back to $_history_substring_search_matches: - # - # 1. Decrease the value of $_history_substring_search_match_index. + if _history-substring-search-has-next; then # - # 2. Restore $BUFFER from $_history_substring_search_old_buffer. + # We do have older matches. # - # 3. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND - # to highlight the current buffer. + # 1. Move index to point to the next match. + # 2. Update display to indicate search found. # - (( _history_substring_search_match_index-- )) - BUFFER=$_history_substring_search_old_buffer - _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND + _history_substring_search_match_index+=1 + _history-substring-search-found else # - # We are at the beginning of history and there are no further matches. + # We do not have older matches. # + # 1. Move the index beyond the end of + # _history_substring_search_matches. + # 2. Update display to indicate search not found. + # + _history_substring_search_match_index+=1 _history-substring-search-not-found - return fi # # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from # history should be matched, make sure the new and old results are different. - # But when HIST_IGNORE_ALL_DUPS is set, ZSH already ensures a unique history. # - if [[ ! -o HIST_IGNORE_ALL_DUPS && -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then + # However, if the HIST_IGNORE_ALL_DUPS shell option, or + # HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set, then we already have a + # unique history, so in this case we do not need to do anything. + # + if [[ -o HIST_IGNORE_ALL_DUPS || -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then + return + fi + + if [[ -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then # # Repeat the current search so that a different (unique) match is found. # @@ -488,92 +679,75 @@ _history-substring-search-down-search() { _history_substring_search_refresh_display=1 # - # Highlight matches during history-substring-up-search: + # Select history entry during history-substring-down-search: # - # The following constants have been initialized in + # The following variables have been initialized in # _history-substring-search-up/down-search(): # - # $_history_substring_search_matches is the current list of matches - # $_history_substring_search_matches_count is the current number of matches - # $_history_substring_search_matches_count_plus is the current number of matches + 1 - # $_history_substring_search_matches_count_sans is the current number of matches - 1 + # $_history_substring_search_matches is the current list of matches that + # need to be displayed to the user. # $_history_substring_search_match_index is the index of the current match + # that is being displayed to the user. # # The range of values that $_history_substring_search_match_index can take - # is: [0, $_history_substring_search_matches_count_plus]. A value of 0 - # indicates that we are beyond the end of + # is: [0, $#_history_substring_search_matches + 1]. A value of 0 + # indicates that we are beyond the beginning of # $_history_substring_search_matches. A value of - # $_history_substring_search_matches_count_plus indicates that we are beyond - # the beginning of $_history_substring_search_matches. + # $#_history_substring_search_matches + 1 indicates that we are beyond + # the end of $_history_substring_search_matches and that we have also + # processed all entries in _history_substring_search_raw_matches. # # In _history-substring-search-down-search() the initial value of - # $_history_substring_search_match_index is - # $_history_substring_search_matches_count. This value is set in - # _history-substring-search-begin(). - # _history-substring-search-down-search() will initially increase it to - # $_history_substring_search_matches_count_plus. + # $_history_substring_search_match_index is 1. This value is set in + # _history-substring-search-begin(). _history-substring-search-down-search() + # will initially decrement it to 0. # - if [[ $_history_substring_search_match_index -le $_history_substring_search_matches_count_sans ]]; then - # - # Highlight the next match: - # - # 1. Increase $_history_substring_search_match_index by 1. - # - # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND - # to highlight the current buffer. - # - (( _history_substring_search_match_index++ )) - BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] - _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND - elif [[ $_history_substring_search_match_index -eq $_history_substring_search_matches_count ]]; then - # - # We will move beyond the beginning of $_history_substring_search_matches: - # - # 1. Increase $_history_substring_search_match_index by 1. + if [[ $_history_substring_search_match_index -lt 1 ]]; then # - # 2. Save the current buffer in $_history_substring_search_old_buffer, so - # that it can be retrieved by _history-substring-search-up-search() - # later. + # We are beyond the beginning of $_history_substring_search_matches. # - # 3. Make $BUFFER equal to $_history_substring_search_query. + # 1. Update display to indicate search not found. # - # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND - # to highlight the current buffer. - # - (( _history_substring_search_match_index++ )) _history-substring-search-not-found + return + fi - elif [[ $_history_substring_search_match_index -eq 0 ]]; then - # - # We were beyond the end of $_history_substring_search_matches but DOWN - # makes us move back to the $_history_substring_search_matches: + if _history-substring-search-has-prev; then # - # 1. Increase $_history_substring_search_match_index by 1. + # We do have younger matches. # - # 2. Restore $BUFFER from $_history_substring_search_old_buffer. + # 1. Move index to point to the previous match. + # 2. Update display to indicate search found. # - # 3. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND - # to highlight the current buffer. - # - (( _history_substring_search_match_index++ )) - BUFFER=$_history_substring_search_old_buffer - _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND + _history_substring_search_match_index+=-1 + _history-substring-search-found else # - # We are at the end of history and there are no further matches. + # We do not have younger matches. + # + # 1. Move the index beyond the beginning of + # _history_substring_search_matches. + # 2. Update display to indicate search not found. # + _history_substring_search_match_index+=-1 _history-substring-search-not-found - return fi # # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from # history should be matched, make sure the new and old results are different. - # But when HIST_IGNORE_ALL_DUPS is set, ZSH already ensures a unique history. # - if [[ ! -o HIST_IGNORE_ALL_DUPS && -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then + # However, if the HIST_IGNORE_ALL_DUPS shell option, or + # HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set, then we already have a + # unique history, so in this case we do not need to do anything. + # + if [[ -o HIST_IGNORE_ALL_DUPS || -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then + return + fi + + if [[ -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then # # Repeat the current search so that a different (unique) match is found. # |