#!/usr/bin/env zsh ############################################################################## # # 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 #----------------------------------------------------------------------------- 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 } 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 #----------------------------------------------------------------------------- # implementation details #----------------------------------------------------------------------------- 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 any existing highlights when the # user inserts printable characters into $BUFFER. # _zsh_highlight() { if [[ $KEYS == [[:print:]] ]]; then region_highlight=() fi } # # The following snippet was taken from the zsh-syntax-highlighting project: # # https://github.com/zsh-users/zsh-syntax-highlighting/blob/56b134f5d62ae3d4e66c7f52bd0cc2595f9b305b/zsh-syntax-highlighting.zsh#L126-161 # # 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. # #--------------8<-------------------8<-------------------8<----------------- # Rebind all ZLE widgets to make them invoke _zsh_highlights. _zsh_highlight_bind_widgets() { # 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.' >&2 return 1 } # Override ZLE widgets to make them invoke _zsh_highlight. local cur_widget for cur_widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|run-help|which-command|beep|yank*)}; do case $widgets[$cur_widget] in # Already rebound event: do nothing. user:$cur_widget|user:_zsh_highlight_widget_*);; # User defined widget: override and rebind old one with prefix "orig-". user:*) eval "zle -N orig-$cur_widget ${widgets[$cur_widget]#*:}; \ _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; # Completion widget: override and rebind old one with prefix "orig-". completion:*) eval "zle -C orig-$cur_widget ${${widgets[$cur_widget]#*:}/:/ }; \ _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; # Builtin widget: override and make it call the builtin ".widget". builtin) eval "_zsh_highlight_widget_$cur_widget() { builtin zle .$cur_widget -- \"\$@\" && _zsh_highlight }; \ zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; # Default: unhandled case. *) echo "zsh-syntax-highlighting: unhandled ZLE widget '$cur_widget'" >&2 ;; esac done } #-------------->8------------------->8------------------->8----------------- _zsh_highlight_bind_widgets fi _history-substring-search-begin() { setopt localoptions extendedglob _history_substring_search_refresh_display= _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) returns the "keys" (history index numbers) instead of the values # (Oa) reverses the order, because (R) returns results reversed. # _history_substring_search_matches=(${(kOa)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 } _history-substring-search-end() { setopt localoptions extendedglob _history_substring_search_result=$BUFFER # the search was successful so display the result properly by clearing away # existing highlights and moving the cursor to the end of the result buffer if [[ $_history_substring_search_refresh_display -eq 1 ]]; then region_highlight=() 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. return 0 } _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 0 fi return 1 } _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 0 fi return 1 } _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-line-or-history fi return 0 fi return 1 } _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_refresh_display=1 # going down from somewhere above the bottom of history else zle down-line-or-history fi return 0 fi return 1 } _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. # _history_substring_search_old_buffer=$BUFFER BUFFER=$_history_substring_search_query _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND } _history-substring-search-up-search() { _history_substring_search_refresh_display=1 # # 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-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 else # # We are at the beginning of history and there are no further matches. # _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 # # Repeat the current search so that a different (unique) match is found. # _history-substring-search-up-search fi } _history-substring-search-down-search() { _history_substring_search_refresh_display=1 # # 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-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 else # # We are at the end of history and there are no further matches. # _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 # # Repeat the current search so that a different (unique) match is found. # _history-substring-search-down-search fi } # -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- # vim: ft=zsh sw=2 ts=2 et