history-substring-search.zsh
caa1cc70
 #!/usr/bin/env zsh
 #
 # This is a clean-room implementation of the Fish[1] shell's history search
 # feature, where you can type in any part of any previously entered command
 # and press the UP and DOWN arrow keys to cycle through the matching commands.
 #
 #-----------------------------------------------------------------------------
 # Usage
 #-----------------------------------------------------------------------------
 #
 # 1. Load this script into your interactive ZSH session:
 #
 #       % source history-substring-search.zsh
 #
 #    If you want to use the zsh-syntax-highlighting[6] script along with this
 #    script, then make sure that you load it *before* you load this script:
 #
 #       % source zsh-syntax-highlighting.zsh
 #       % source history-substring-search.zsh
 #
 # 2. Type any part of any previous command and then:
 #
 #     * Press the UP arrow key to select the nearest command that (1) contains
 #       your query and (2) is older than the current command in the command
 #       history.
 #
 #     * Press the DOWN arrow key to select the nearest command that (1)
 #       contains your query and (2) is newer than the current command in the
 #       command history.
 #
 #     * Press ^U (the Control and U keys simultaneously) to abort the search.
 #
 # 3. If a matching command spans more than one line of text, press the LEFT
 #    arrow key to move the cursor away from the end of the command, and then:
 #
 #     * Press the UP arrow key to move the cursor to the line above.  When the
 #       cursor reaches the first line of the command, pressing the UP arrow
 #       key again will cause this script to perform another search.
 #
 #     * Press the DOWN arrow key to move the cursor to the line below.  When
 #       the cursor reaches the last line of the command, pressing the DOWN
 #       arrow key again will cause this script to perform another search.
 #
 #-----------------------------------------------------------------------------
 # Configuration
 #-----------------------------------------------------------------------------
 #
 # This script defines the following global variables. You may override their
 # default values only after having loaded this script into your ZSH session.
 #
 # * HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND is a global variable that defines
 #   how the query should be highlighted inside a matching command. Its default
 #   value causes this script to highlight using bold, white text on a magenta
 #   background. See the "Character Highlighting" section in the zshzle(1) man
 #   page to learn about the kinds of values you may assign to this variable.
 #
 # * HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND is a global variable that
 #   defines how the query should be highlighted when no commands in the
 #   history match it. Its default value causes this script to highlight using
 #   bold, white text on a red background. See the "Character Highlighting"
 #   section in the zshzle(1) man page to learn about the kinds of values you
 #   may assign to this variable.
 #
 # * HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS is a global variable that defines
 #   how the command history will be searched for your query. Its default value
 #   causes this script to perform a case-insensitive search. See the "Globbing
 #   Flags" section in the zshexpn(1) man page to learn about the kinds of
 #   values you may assign to this variable.
 #
 #-----------------------------------------------------------------------------
 # History
 #-----------------------------------------------------------------------------
 #
 # This script was originally written by Peter Stephenson[2], who published it
 # to the ZSH users mailing list (thereby making it public domain) in September
 # 2009. It was later revised by Guido van Steen and released under the BSD
 # license (see below) as part of the fizsh[3] project in January 2011.
 #
 # It was later extracted from fizsh[3] release 1.0.1, refactored heavily, and
 # repackaged as both an oh-my-zsh plugin[4] and as an independently loadable
 # ZSH script[5] by Suraj N. Kurapati in 2011.
 #
 # It was further developed[4] by Guido van Steen, Suraj N. Kurapati, Sorin
 # Ionescu, and Vincent Guerci in 2011.
 #
 # [1]: http://fishshell.com
 # [2]: http://www.zsh.org/mla/users/2009/msg00818.html
 # [3]: http://sourceforge.net/projects/fizsh/
 # [4]: https://github.com/robbyrussell/oh-my-zsh/pull/215
 # [5]: https://github.com/sunaku/zsh-history-substring-search
 # [6]: https://github.com/nicoulaj/zsh-syntax-highlighting
 #
 ##############################################################################
 #
 # Copyright (c) 2009 Peter Stephenson
 # Copyright (c) 2011 Guido van Steen
 # Copyright (c) 2011 Suraj N. Kurapati
 # Copyright (c) 2011 Sorin Ionescu
 # Copyright (c) 2011 Vincent Guerci
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
 #
 #  * Redistributions of source code must retain the above copyright
 #    notice, this list of conditions and the following disclaimer.
 #
 #  * Redistributions in binary form must reproduce the above
 #    copyright notice, this list of conditions and the following
 #    disclaimer in the documentation and/or other materials provided
 #    with the distribution.
 #
 #  * Neither the name of the FIZSH nor the names of its contributors
 #    may be used to endorse or promote products derived from this
 #    software without specific prior written permission.
 #
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
 #
 ##############################################################################
 
 #-----------------------------------------------------------------------------
 # 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'
 
 #-----------------------------------------------------------------------------
 # the main ZLE widgets
 #-----------------------------------------------------------------------------
 
 function history-substring-search-up() {
   _history-substring-search-begin
 
   _history-substring-search-up-history ||
   _history-substring-search-up-buffer ||
   _history-substring-search-up-search
 
   _history-substring-search-end
 }
 
 function history-substring-search-down() {
   _history-substring-search-begin
 
   _history-substring-search-down-history ||
   _history-substring-search-down-buffer ||
   _history-substring-search-down-search
 
   _history-substring-search-end
 }
 
 zle -N history-substring-search-up
 zle -N history-substring-search-down
 
 bindkey '\e[A' history-substring-search-up
 bindkey '\e[B' history-substring-search-down
 
 #-----------------------------------------------------------------------------
 # implementation details
 #-----------------------------------------------------------------------------
 
 setopt extendedglob
 zmodload -F zsh/parameter
 
 #
 # We have to "override" some keys and widgets if the
 # zsh-syntax-highlighting plugin has not been loaded:
 #
 # https://github.com/nicoulaj/zsh-syntax-highlighting
 #
 if [[ $+functions[_zsh_highlight] -eq 0 ]]; then
   #
   # Dummy implementation of _zsh_highlight()
   # that simply removes existing highlights
   #
   function _zsh_highlight() {
     region_highlight=()
   }
 
   #
   # Remove existing highlights when the user
   # inserts printable characters into $BUFFER
   #
   function ordinary-key-press() {
     if [[ $KEYS == [[:print:]] ]]; then
       region_highlight=()
     fi
     zle .self-insert
   }
   zle -N self-insert ordinary-key-press
 
   #
   # Override ZLE widgets to invoke _zsh_highlight()
   #
   # https://github.com/nicoulaj/zsh-syntax-highlighting/blob/
   # bb7fcb79fad797a40077bebaf6f4e4a93c9d8163/zsh-syntax-highlighting.zsh#L121
   #
   #--------------8<-------------------8<-------------------8<-----------------
   #
   # Copyright (c) 2010-2011 zsh-syntax-highlighting contributors
   # All rights reserved.
   #
   # Redistribution and use in source and binary forms, with or without
   # modification, are permitted provided that the following conditions are
   # met:
   #
   #  * Redistributions of source code must retain the above copyright
   #    notice, this list of conditions and the following disclaimer.
   #
   #  * Redistributions in binary form must reproduce the above copyright
   #    notice, this list of conditions and the following disclaimer in the
   #    documentation and/or other materials provided with the distribution.
   #
   #  * Neither the name of the zsh-syntax-highlighting contributors nor the
   #    names of its contributors may be used to endorse or promote products
   #    derived from this software without specific prior written permission.
   #
   # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
   # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
   # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
   # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
   # Load ZSH module zsh/zleparameter, needed to override user defined widgets.
   zmodload zsh/zleparameter 2>/dev/null || {
     echo 'zsh-syntax-highlighting: failed loading zsh/zleparameter, exiting.' >&2
     return -1
   }
 
   # Override ZLE widgets to make them invoke _zsh_highlight.
   for event in ${${(f)"$(zle -la)"}:#(_*|orig-*|.run-help|.which-command)}; do
     if [[ "$widgets[$event]" == completion:* ]]; then
       eval "zle -C orig-$event ${${${widgets[$event]}#*:}/:/ } ; $event() { builtin zle orig-$event && _zsh_highlight } ; zle -N $event"
     else
       case $event in
         accept-and-menu-complete)
           eval "$event() { builtin zle .$event && _zsh_highlight } ; zle -N $event"
           ;;
 
         # The following widgets should NOT remove any previously
         # applied highlighting. Therefore we do not remap them.
         .forward-char|.backward-char|.up-line-or-history|.down-line-or-history)
           ;;
 
         .*)
           clean_event=$event[2,${#event}] # Remove the leading dot in the event name
           case ${widgets[$clean_event]-} in
             (completion|user):*)
               ;;
             *)
               eval "$clean_event() { builtin zle $event && _zsh_highlight } ; zle -N $clean_event"
               ;;
           esac
           ;;
         *)
           ;;
       esac
     fi
   done
   unset event clean_event
   #-------------->8------------------->8------------------->8-----------------
 fi
 
 function _history-substring-search-begin() {
   _history_substring_search_move_cursor_eol=false
   _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 [[ -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
 
     #
     # $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 "\\\)".
     #
     _history_substring_search_query_escaped=${BUFFER//(#m)[\][()|\\*?#<>~^]/\\$MATCH}
 
     #
     # Find all occurrences of the search query in the history file.
     #
     # (k) turns it an array of line numbers.
     #
     # (on) seems to remove duplicates, which are default
     #      options. They can be turned off by (ON).
     #
     _history_substring_search_matches=(${(kon)history[(R)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)*${_history_substring_search_query_escaped}*]})
 
     #
     # Define the range of values that $_history_substring_search_match_index
     # can take: [0, $_history_substring_search_matches_count_plus].
     #
     _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 $_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.
     #
     # If $_history_substring_search_match_index is equal to 0, this indicates
     # that we are beyond the end of $_history_substring_search_matches.
     #
     # 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.
     #
     # 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.
     #
     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
   fi
 }
 
 function _history-substring-search-end() {
   _history_substring_search_result=$BUFFER
 
   # move the cursor to the end of the command line
   if [[ $_history_substring_search_move_cursor_eol == true ]]; then
     CURSOR=${#BUFFER}
   fi
 
   # highlight command line using zsh-syntax-highlighting
   _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")
   fi
 
   # For debugging purposes:
   # zle -R "mn: "$_history_substring_search_match_index" m#: "${#_history_substring_search_matches}
   # read -k -t 200 && zle -U $REPLY
 
   # Exit successfully from the history-substring-search-* widgets.
   true
 }
 
 function _history-substring-search-up-buffer() {
   #
   # Check if the UP arrow was pressed to move the cursor within a multi-line
   # buffer. This amounts to three tests:
   #
   # 1. $#buflines -gt 1.
   #
   # 2. $CURSOR -ne $#BUFFER.
   #
   # 3. Check if we are on the first line of the current multi-line buffer.
   #    If so, pressing UP would amount to leaving the multi-line buffer.
   #
   #    We check this by adding an extra "x" to $LBUFFER, which makes
   #    sure that xlbuflines is always equal to the number of lines
   #    until $CURSOR (including the line with the cursor on it).
   #
   local buflines XLBUFFER xlbuflines
   buflines=(${(f)BUFFER})
   XLBUFFER=$LBUFFER"x"
   xlbuflines=(${(f)XLBUFFER})
 
   if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xlbuflines -ne 1 ]]; then
     zle up-line-or-history
     return true
   fi
 
   false
 }
 
 function _history-substring-search-down-buffer() {
   #
   # Check if the DOWN arrow was pressed to move the cursor within a multi-line
   # buffer. This amounts to three tests:
   #
   # 1. $#buflines -gt 1.
   #
   # 2. $CURSOR -ne $#BUFFER.
   #
   # 3. Check if we are on the last line of the current multi-line buffer.
   #    If so, pressing DOWN would amount to leaving the multi-line buffer.
   #
   #    We check this by adding an extra "x" to $RBUFFER, which makes
   #    sure that xrbuflines is always equal to the number of lines
   #    from $CURSOR (including the line with the cursor on it).
   #
   local buflines XRBUFFER xrbuflines
   buflines=(${(f)BUFFER})
   XRBUFFER="x"$RBUFFER
   xrbuflines=(${(f)XRBUFFER})
 
   if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xrbuflines -ne 1 ]]; then
     zle down-line-or-history
     return true
   fi
 
   false
 }
 
 function _history-substring-search-up-history() {
   #
   # Behave like up in ZSH, except clear the $BUFFER
   # when beginning of history is reached like in Fish.
   #
   if [[ -z $_history_substring_search_query ]]; then
 
     # we have reached the absolute top of history
     if [[ $HISTNO -eq 1 ]]; then
       BUFFER=
 
     # going up from somewhere below the top of history
     else
       zle up-history
     fi
 
     return true
   fi
 
   false
 }
 
 function _history-substring-search-down-history() {
   #
   # Behave like down-history in ZSH, except clear the
   # $BUFFER when end of history is reached like in Fish.
   #
   if [[ -z $_history_substring_search_query ]]; then
 
     # going down from the absolute top of history
     if [[ $HISTNO -eq 1 && -z $BUFFER ]]; then
       BUFFER=${history[1]}
       _history_substring_search_move_cursor_eol=true
 
     # going down from somewhere above the bottom of history
     else
       zle down-history
     fi
 
     return true
   fi
 
   false
 }
 
 function _history-substring-search-up-search() {
   _history_substring_search_move_cursor_eol=true
 
   #
   # Highlight matches during history-substring-up-search:
   #
   # The following constants 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_match_index is the index of the current match
   #
   # 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
   # $_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.
   #
   # 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.
   #
   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.
     #
     # 3. Make $BUFFER equal to $_history_substring_search_query.
     #
     # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND
     #    to highlight the current buffer.
     #
     (( _history_substring_search_match_index-- ))
     _history_substring_search_old_buffer=$BUFFER
     BUFFER=$_history_substring_search_query
     _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND
 
   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.
     #
     # 2. Restore $BUFFER from $_history_substring_search_old_buffer.
     #
     # 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
   fi
 }
 
 function _history-substring-search-down-search() {
   _history_substring_search_move_cursor_eol=true
 
   #
   # Highlight matches during history-substring-up-search:
   #
   # The following constants 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_match_index is the index of the current match
   #
   # 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
   # $_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.
   #
   # 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.
   #
   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.
     #
     # 2. Save the current buffer in $_history_substring_search_old_buffer, so
     #    that it can be retrieved by _history-substring-search-up-search()
     #    later.
     #
     # 3. Make $BUFFER equal to $_history_substring_search_query.
     #
     # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND
     #    to highlight the current buffer.
     #
     (( _history_substring_search_match_index++ ))
     _history_substring_search_old_buffer=$BUFFER
     BUFFER=$_history_substring_search_query
     _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND
 
   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:
     #
     # 1. Increase $_history_substring_search_match_index by 1.
     #
     # 2. Restore $BUFFER from $_history_substring_search_old_buffer.
     #
     # 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
   fi
 }
 
 # -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
 # vim: ft=zsh sw=2 ts=2 et