history-substring-search.zsh
29692 bytes
1#!/usr/bin/env zsh
2##############################################################################
3#
4# Copyright (c) 2009 Peter Stephenson
5# Copyright (c) 2011 Guido van Steen
6# Copyright (c) 2011 Suraj N. Kurapati
7# Copyright (c) 2011 Sorin Ionescu
8# Copyright (c) 2011 Vincent Guerci
9# Copyright (c) 2016 Geza Lore
10# Copyright (c) 2017 Bengt Brodersen
11# All rights reserved.
12#
13# Redistribution and use in source and binary forms, with or without
14# modification, are permitted provided that the following conditions are met:
15#
16# * Redistributions of source code must retain the above copyright
17# notice, this list of conditions and the following disclaimer.
18#
19# * Redistributions in binary form must reproduce the above
20# copyright notice, this list of conditions and the following
21# disclaimer in the documentation and/or other materials provided
22# with the distribution.
23#
24# * Neither the name of the FIZSH nor the names of its contributors
25# may be used to endorse or promote products derived from this
26# software without specific prior written permission.
27#
28# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
32# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38# POSSIBILITY OF SUCH DAMAGE.
39#
40##############################################################################
41
42#-----------------------------------------------------------------------------
43# declare global configuration variables
44#-----------------------------------------------------------------------------
45
46: ${HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='bg=magenta,fg=white,bold'}
47: ${HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='bg=red,fg=white,bold'}
48: ${HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS='i'}
49: ${HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE=''}
50: ${HISTORY_SUBSTRING_SEARCH_FUZZY=''}
51: ${HISTORY_SUBSTRING_SEARCH_PREFIXED=''}
52
53#-----------------------------------------------------------------------------
54# declare internal global variables
55#-----------------------------------------------------------------------------
56
57typeset -g BUFFER MATCH MBEGIN MEND CURSOR
58typeset -g _history_substring_search_refresh_display
59typeset -g _history_substring_search_query_highlight
60typeset -g _history_substring_search_result
61typeset -g _history_substring_search_query
62typeset -g -a _history_substring_search_query_parts
63typeset -g -a _history_substring_search_raw_matches
64typeset -g -i _history_substring_search_raw_match_index
65typeset -g -a _history_substring_search_matches
66typeset -g -i _history_substring_search_match_index
67typeset -g -A _history_substring_search_unique_filter
68typeset -g -i _history_substring_search_zsh_5_9
69
70#-----------------------------------------------------------------------------
71# the main ZLE widgets
72#-----------------------------------------------------------------------------
73
74history-substring-search-up() {
75 _history-substring-search-begin
76
77 _history-substring-search-up-history ||
78 _history-substring-search-up-buffer ||
79 _history-substring-search-up-search
80
81 _history-substring-search-end
82}
83
84history-substring-search-down() {
85 _history-substring-search-begin
86
87 _history-substring-search-down-history ||
88 _history-substring-search-down-buffer ||
89 _history-substring-search-down-search
90
91 _history-substring-search-end
92}
93
94zle -N history-substring-search-up
95zle -N history-substring-search-down
96
97#-----------------------------------------------------------------------------
98# implementation details
99#-----------------------------------------------------------------------------
100
101zmodload -F zsh/parameter
102autoload -Uz is-at-least
103
104if is-at-least 5.9 $ZSH_VERSION; then
105 _history_substring_search_zsh_5_9=1
106fi
107
108#
109# We have to "override" some keys and widgets if the
110# zsh-syntax-highlighting plugin has not been loaded:
111#
112# https://github.com/nicoulaj/zsh-syntax-highlighting
113#
114if [[ $+functions[_zsh_highlight] -eq 0 ]]; then
115 #
116 # Dummy implementation of _zsh_highlight() that
117 # simply removes any existing highlights when the
118 # user inserts printable characters into $BUFFER.
119 #
120 _zsh_highlight() {
121 if [[ $KEYS == [[:print:]] ]]; then
122 region_highlight=()
123 fi
124 }
125
126 #
127 # Check if $1 denotes the name of a callable function, i.e. it is fully
128 # defined or it is marked for autoloading and autoloading it at the first
129 # call to it will succeed. In particular, if $1 has been marked for
130 # autoloading but is not available in $fpath, then it will return 1 (false).
131 #
132 # This is based on the zsh-syntax-highlighting plugin.
133 #
134 _history-substring-search-function-callable() {
135 if (( ${+functions[$1]} )) && ! [[ "$functions[$1]" == *"builtin autoload -X"* ]]; then
136 return 0 # already fully loaded
137 else
138 # "$1" is either an autoload stub, or not a function at all.
139 # We expect 'autoload +X' to return non-zero if it fails to fully load
140 # the function.
141 ( autoload -U +X -- "$1" 2>/dev/null )
142 return $?
143 fi
144 }
145
146 #
147 # The zsh-syntax-highlighting plugin uses zle-line-pre-redraw hook instead
148 # of the legacy "bind all widgets" if 1) zsh has the memo= feature (added in
149 # version 5.9) and 2) add-zle-hook-widget is available.
150 #
151 if [[ $_history_substring_search_zsh_5_9 -eq 1 ]] && _history-substring-search-function-callable add-zle-hook-widget; then
152 #
153 # The following code is based on the zsh-syntax-highlighting plugin.
154 #
155 autoload -U add-zle-hook-widget
156
157 _history-substring-search-zle-line-finish() {
158 #
159 # Reset $WIDGET since the 'main' highlighter depends on it.
160 #
161 # Since $WIDGET is declared by zle as read-only in this function's scope,
162 # a nested function is required in order to shadow its built-in value;
163 # see "User-defined widgets" in zshall.
164 #
165 () {
166 local -h -r WIDGET=zle-line-finish
167 _zsh_highlight
168 }
169 }
170
171 _history-substring-search-zle-line-pre-redraw() {
172 #
173 # If the zsh-syntax-highlighting plugin has been loaded (after our plugin
174 # plugin, otherwise this hook wouldn't be called), remove our hooks.
175 #
176 if [[ $+ZSH_HIGHLIGHT_VERSION -eq 1 ]]; then
177 autoload -U add-zle-hook-widget
178 add-zle-hook-widget -d zle-line-pre-redraw _history-substring-search-zle-line-pre-redraw
179 add-zle-hook-widget -d zle-line-finish _history-substring-search-zle-line-finish
180 return 0
181 fi
182 #
183 # Set $? to 0 for _zsh_highlight. Without this, subsequent
184 # zle-line-pre-redraw hooks won't run, since add-zle-hook-widget happens to
185 # call us with $? == 1 in the common case.
186 #
187 true && _zsh_highlight "$@"
188 }
189
190 if [[ -o zle ]]; then
191 add-zle-hook-widget zle-line-pre-redraw _history-substring-search-zle-line-pre-redraw
192 add-zle-hook-widget zle-line-finish _history-substring-search-zle-line-finish
193 fi
194 else
195 #
196 # The following snippet was taken from the zsh-syntax-highlighting project:
197 # https://github.com/zsh-users/zsh-syntax-highlighting/blob/56b134f5d62ae3d4e66c7f52bd0cc2595f9b305b/zsh-syntax-highlighting.zsh#L126-161
198 #
199 # SPDX-SnippetBegin
200 # SPDX-License-Identifier: BSD-3-Clause
201 # SPDX-SnippetCopyrightText: 2010-2011 zsh-syntax-highlighting contributors
202 #--------------8<-------------------8<-------------------8<-----------------
203 # Rebind all ZLE widgets to make them invoke _zsh_highlights.
204 _zsh_highlight_bind_widgets()
205 {
206 # Load ZSH module zsh/zleparameter, needed to override user defined widgets.
207 zmodload zsh/zleparameter 2>/dev/null || {
208 echo 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' >&2
209 return 1
210 }
211
212 # Override ZLE widgets to make them invoke _zsh_highlight.
213 local cur_widget
214 for cur_widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|run-help|which-command|beep|yank*)}; do
215 case $widgets[$cur_widget] in
216
217 # Already rebound event: do nothing.
218 user:$cur_widget|user:_zsh_highlight_widget_*);;
219
220 # User defined widget: override and rebind old one with prefix "orig-".
221 user:*) eval "zle -N orig-$cur_widget ${widgets[$cur_widget]#*:}; \
222 _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \
223 zle -N $cur_widget _zsh_highlight_widget_$cur_widget";;
224
225 # Completion widget: override and rebind old one with prefix "orig-".
226 completion:*) eval "zle -C orig-$cur_widget ${${widgets[$cur_widget]#*:}/:/ }; \
227 _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \
228 zle -N $cur_widget _zsh_highlight_widget_$cur_widget";;
229
230 # Builtin widget: override and make it call the builtin ".widget".
231 builtin) eval "_zsh_highlight_widget_$cur_widget() { builtin zle .$cur_widget -- \"\$@\" && _zsh_highlight }; \
232 zle -N $cur_widget _zsh_highlight_widget_$cur_widget";;
233
234 # Default: unhandled case.
235 *) echo "zsh-syntax-highlighting: unhandled ZLE widget '$cur_widget'" >&2 ;;
236 esac
237 done
238 }
239 #-------------->8------------------->8------------------->8-----------------
240 # SPDX-SnippetEnd
241
242 _zsh_highlight_bind_widgets
243 fi
244
245 unfunction _history-substring-search-function-callable
246fi
247
248_history-substring-search-begin() {
249 setopt localoptions extendedglob
250
251 _history_substring_search_refresh_display=
252 _history_substring_search_query_highlight=
253
254 #
255 # If the buffer is the same as the previously displayed history substring
256 # search result, then just keep stepping through the match list. Otherwise
257 # start a new search.
258 #
259 if [[ -n $BUFFER && $BUFFER == ${_history_substring_search_result:-} ]]; then
260 return;
261 fi
262
263 #
264 # Clear the previous result.
265 #
266 _history_substring_search_result=''
267
268 if [[ -z $BUFFER ]]; then
269 #
270 # If the buffer is empty, we will just act like up-history/down-history
271 # in ZSH, so we do not need to actually search the history. This should
272 # speed things up a little.
273 #
274 _history_substring_search_query=
275 _history_substring_search_query_parts=()
276 _history_substring_search_raw_matches=()
277
278 else
279 #
280 # For the purpose of highlighting we keep a copy of the original
281 # query string.
282 #
283 _history_substring_search_query=$BUFFER
284
285 #
286 # compose search pattern
287 #
288 if [[ -n $HISTORY_SUBSTRING_SEARCH_FUZZY ]]; then
289 #
290 # `=` split string in arguments
291 #
292 _history_substring_search_query_parts=(${=_history_substring_search_query})
293 else
294 _history_substring_search_query_parts=(${==_history_substring_search_query})
295 fi
296
297 #
298 # Escape and join query parts with wildcard character '*' as separator
299 # `(j:CHAR:)` join array to string with CHAR as separator
300 #
301 local search_pattern="${(j:*:)_history_substring_search_query_parts[@]//(#m)[\][()|\\*?#<>~^]/\\$MATCH}*"
302
303 #
304 # Support anchoring history search to the beginning of the command
305 #
306 if [[ -z $HISTORY_SUBSTRING_SEARCH_PREFIXED ]]; then
307 search_pattern="*${search_pattern}"
308 fi
309
310 #
311 # Find all occurrences of the search pattern in the history file.
312 #
313 # (k) returns the "keys" (history index numbers) instead of the values
314 # (R) returns values in reverse older, so the index of the youngest
315 # matching history entry is at the head of the list.
316 #
317 _history_substring_search_raw_matches=(${(k)history[(R)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)${search_pattern}]})
318 fi
319
320 #
321 # In order to stay as responsive as possible, we will process the raw
322 # matches lazily (when the user requests the next match) to choose items
323 # that need to be displayed to the user.
324 # _history_substring_search_raw_match_index holds the index of the last
325 # unprocessed entry in _history_substring_search_raw_matches. Any items
326 # that need to be displayed will be added to
327 # _history_substring_search_matches.
328 #
329 # We use an associative array (_history_substring_search_unique_filter) as
330 # a 'set' data structure to ensure uniqueness of the results if desired.
331 # If an entry (key) is in the set (non-empty value), then we have already
332 # added that entry to _history_substring_search_matches.
333 #
334 _history_substring_search_raw_match_index=0
335 _history_substring_search_matches=()
336 _history_substring_search_unique_filter=()
337
338 #
339 # If $_history_substring_search_match_index is equal to
340 # $#_history_substring_search_matches + 1, this indicates that we
341 # are beyond the end of $_history_substring_search_matches and that we
342 # have also processed all entries in
343 # _history_substring_search_raw_matches.
344 #
345 # If $#_history_substring_search_match_index is equal to 0, this indicates
346 # that we are beyond the beginning of $_history_substring_search_matches.
347 #
348 # If we have initially pressed "up" we have to initialize
349 # $_history_substring_search_match_index to 0 so that it will be
350 # incremented to 1.
351 #
352 # If we have initially pressed "down" we have to initialize
353 # $_history_substring_search_match_index to 1 so that it will be
354 # decremented to 0.
355 #
356 if [[ $WIDGET == history-substring-search-down ]]; then
357 _history_substring_search_match_index=1
358 else
359 _history_substring_search_match_index=0
360 fi
361}
362
363_history-substring-search-end() {
364 setopt localoptions extendedglob
365
366 local highlight_memo=
367 _history_substring_search_result=$BUFFER
368
369 if [[ $_history_substring_search_zsh_5_9 -eq 1 ]]; then
370 highlight_memo='memo=history-substring-search'
371 fi
372
373 # the search was successful so display the result properly by clearing away
374 # existing highlights and moving the cursor to the end of the result buffer
375 if [[ $_history_substring_search_refresh_display -eq 1 ]]; then
376 if [[ -n $highlight_memo ]]; then
377 region_highlight=( "${(@)region_highlight:#*${highlight_memo}*}" )
378 else
379 region_highlight=()
380 fi
381 CURSOR=${#BUFFER}
382 fi
383
384 # highlight command line using zsh-syntax-highlighting
385 _zsh_highlight
386
387 # highlight the search query inside the command line
388 if [[ -n $_history_substring_search_query_highlight ]]; then
389 # highlight first matching query parts
390 local highlight_start_index=0
391 local highlight_end_index=0
392 local query_part
393 for query_part in $_history_substring_search_query_parts; do
394 local escaped_query_part=${query_part//(#m)[\][()|\\*?#<>~^]/\\$MATCH}
395 # (i) get index of pattern
396 local query_part_match_index="${${BUFFER:$highlight_start_index}[(i)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)${escaped_query_part}]}"
397 if [[ $query_part_match_index -le ${#BUFFER:$highlight_start_index} ]]; then
398 highlight_start_index=$(( $highlight_start_index + $query_part_match_index ))
399 highlight_end_index=$(( $highlight_start_index + ${#query_part} ))
400 region_highlight+=(
401 "$(($highlight_start_index - 1)) $(($highlight_end_index - 1)) ${_history_substring_search_query_highlight}${highlight_memo:+,$highlight_memo}"
402 )
403 fi
404 done
405 fi
406
407 # For debugging purposes:
408 # zle -R "mn: "$_history_substring_search_match_index" m#: "${#_history_substring_search_matches}
409 # read -k -t 200 && zle -U -- "$REPLY"
410
411 #
412 # When this function returns, z-sy-h runs its line-pre-redraw hook. It has no
413 # logic for determining highlight priority, when two different memo= marked
414 # region highlights overlap; instead, it always prioritises itself. Below is
415 # a workaround for dealing with it.
416 #
417 if [[ $_history_substring_search_zsh_5_9 -eq 1 ]]; then
418 zle -R
419 #
420 # After line redraw with desired highlight, wait for timeout or user input
421 # before removing search highlight and exiting. This ensures no highlights
422 # are left lingering after search is finished.
423 #
424 read -k -t ${HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_TIMEOUT:-1} && zle -U -- "$REPLY"
425 region_highlight=( "${(@)region_highlight:#*${highlight_memo}*}" )
426 fi
427
428 # Exit successfully from the history-substring-search-* widgets.
429 return 0
430}
431
432_history-substring-search-up-buffer() {
433 #
434 # Check if the UP arrow was pressed to move the cursor within a multi-line
435 # buffer. This amounts to three tests:
436 #
437 # 1. $#buflines -gt 1.
438 #
439 # 2. $CURSOR -ne $#BUFFER.
440 #
441 # 3. Check if we are on the first line of the current multi-line buffer.
442 # If so, pressing UP would amount to leaving the multi-line buffer.
443 #
444 # We check this by adding an extra "x" to $LBUFFER, which makes
445 # sure that xlbuflines is always equal to the number of lines
446 # until $CURSOR (including the line with the cursor on it).
447 #
448 local buflines XLBUFFER xlbuflines
449 buflines=(${(f)BUFFER})
450 XLBUFFER=$LBUFFER"x"
451 xlbuflines=(${(f)XLBUFFER})
452
453 if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xlbuflines -ne 1 ]]; then
454 zle up-line-or-history
455 return 0
456 fi
457
458 return 1
459}
460
461_history-substring-search-down-buffer() {
462 #
463 # Check if the DOWN arrow was pressed to move the cursor within a multi-line
464 # buffer. This amounts to three tests:
465 #
466 # 1. $#buflines -gt 1.
467 #
468 # 2. $CURSOR -ne $#BUFFER.
469 #
470 # 3. Check if we are on the last line of the current multi-line buffer.
471 # If so, pressing DOWN would amount to leaving the multi-line buffer.
472 #
473 # We check this by adding an extra "x" to $RBUFFER, which makes
474 # sure that xrbuflines is always equal to the number of lines
475 # from $CURSOR (including the line with the cursor on it).
476 #
477 local buflines XRBUFFER xrbuflines
478 buflines=(${(f)BUFFER})
479 XRBUFFER="x"$RBUFFER
480 xrbuflines=(${(f)XRBUFFER})
481
482 if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xrbuflines -ne 1 ]]; then
483 zle down-line-or-history
484 return 0
485 fi
486
487 return 1
488}
489
490_history-substring-search-up-history() {
491 #
492 # Behave like up in ZSH, except clear the $BUFFER
493 # when beginning of history is reached like in Fish.
494 #
495 if [[ -z $_history_substring_search_query ]]; then
496
497 # we have reached the absolute top of history
498 if [[ $HISTNO -eq 1 ]]; then
499 BUFFER=
500
501 # going up from somewhere below the top of history
502 else
503 zle up-line-or-history
504 fi
505
506 return 0
507 fi
508
509 return 1
510}
511
512_history-substring-search-down-history() {
513 #
514 # Behave like down-history in ZSH, except clear the
515 # $BUFFER when end of history is reached like in Fish.
516 #
517 if [[ -z $_history_substring_search_query ]]; then
518
519 # going down from the absolute top of history
520 if [[ $HISTNO -eq 1 && -z $BUFFER ]]; then
521 BUFFER=${history[1]}
522 _history_substring_search_refresh_display=1
523
524 # going down from somewhere above the bottom of history
525 else
526 zle down-line-or-history
527 fi
528
529 return 0
530 fi
531
532 return 1
533}
534
535_history_substring_search_process_raw_matches() {
536 #
537 # Process more outstanding raw matches and append any matches that need to
538 # be displayed to the user to _history_substring_search_matches.
539 # Return whether there were any more results appended.
540 #
541
542 #
543 # While we have more raw matches. Process them to see if there are any more
544 # matches that need to be displayed to the user.
545 #
546 while [[ $_history_substring_search_raw_match_index -lt $#_history_substring_search_raw_matches ]]; do
547 #
548 # Move on to the next raw entry and get its history index.
549 #
550 _history_substring_search_raw_match_index+=1
551 local index=${_history_substring_search_raw_matches[$_history_substring_search_raw_match_index]}
552
553 #
554 # If HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set to a non-empty value,
555 # then ensure that only unique matches are presented to the user.
556 # When HIST_IGNORE_ALL_DUPS is set, ZSH already ensures a unique history,
557 # so in this case we do not need to do anything.
558 #
559 if [[ ! -o HIST_IGNORE_ALL_DUPS && -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then
560 #
561 # Get the actual history entry at the new index, and check if we have
562 # already added it to _history_substring_search_matches.
563 #
564 local entry=${history[$index]}
565
566 if [[ -z ${_history_substring_search_unique_filter[$entry]} ]]; then
567 #
568 # This is a new unique entry. Add it to the filter and append the
569 # index to _history_substring_search_matches.
570 #
571 _history_substring_search_unique_filter[$entry]=1
572 _history_substring_search_matches+=($index)
573
574 #
575 # Indicate that we did find a match.
576 #
577 return 0
578 fi
579
580 else
581 #
582 # Just append the new history index to the processed matches.
583 #
584 _history_substring_search_matches+=($index)
585
586 #
587 # Indicate that we did find a match.
588 #
589 return 0
590 fi
591
592 done
593
594 #
595 # We are beyond the end of the list of raw matches. Indicate that no
596 # more matches are available.
597 #
598 return 1
599}
600
601_history-substring-search-has-next() {
602 #
603 # Predicate function that returns whether any more older matches are
604 # available.
605 #
606
607 if [[ $_history_substring_search_match_index -lt $#_history_substring_search_matches ]]; then
608 #
609 # We did not reach the end of the processed list, so we do have further
610 # matches.
611 #
612 return 0
613
614 else
615 #
616 # We are at the end of the processed list. Try to process further
617 # unprocessed matches. _history_substring_search_process_raw_matches
618 # returns whether any more matches were available, so just return
619 # that result.
620 #
621 _history_substring_search_process_raw_matches
622 return $?
623 fi
624}
625
626_history-substring-search-has-prev() {
627 #
628 # Predicate function that returns whether any more younger matches are
629 # available.
630 #
631
632 if [[ $_history_substring_search_match_index -gt 1 ]]; then
633 #
634 # We did not reach the beginning of the processed list, so we do have
635 # further matches.
636 #
637 return 0
638
639 else
640 #
641 # We are at the beginning of the processed list. We do not have any more
642 # matches.
643 #
644 return 1
645 fi
646}
647
648_history-substring-search-found() {
649 #
650 # A match is available. The index of the match is held in
651 # $_history_substring_search_match_index
652 #
653 # 1. Make $BUFFER equal to the matching history entry.
654 #
655 # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND
656 # to highlight the current buffer.
657 #
658 BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]]
659 _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND
660}
661
662_history-substring-search-not-found() {
663 #
664 # No more matches are available.
665 #
666 # 1. Make $BUFFER equal to $_history_substring_search_query so the user can
667 # revise it and search again.
668 #
669 # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND
670 # to highlight the current buffer.
671 #
672 BUFFER=$_history_substring_search_query
673 _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND
674}
675
676_history-substring-search-up-search() {
677 _history_substring_search_refresh_display=1
678
679 #
680 # Select history entry during history-substring-down-search:
681 #
682 # The following variables have been initialized in
683 # _history-substring-search-up/down-search():
684 #
685 # $_history_substring_search_matches is the current list of matches that
686 # need to be displayed to the user.
687 # $_history_substring_search_match_index is the index of the current match
688 # that is being displayed to the user.
689 #
690 # The range of values that $_history_substring_search_match_index can take
691 # is: [0, $#_history_substring_search_matches + 1]. A value of 0
692 # indicates that we are beyond the beginning of
693 # $_history_substring_search_matches. A value of
694 # $#_history_substring_search_matches + 1 indicates that we are beyond
695 # the end of $_history_substring_search_matches and that we have also
696 # processed all entries in _history_substring_search_raw_matches.
697 #
698 # If $_history_substring_search_match_index equals
699 # $#_history_substring_search_matches and
700 # $_history_substring_search_raw_match_index is not greater than
701 # $#_history_substring_search_raw_matches, then we need to further process
702 # $_history_substring_search_raw_matches to see if there are any more
703 # entries that need to be displayed to the user.
704 #
705 # In _history-substring-search-up-search() the initial value of
706 # $_history_substring_search_match_index is 0. This value is set in
707 # _history-substring-search-begin(). _history-substring-search-up-search()
708 # will initially increment it to 1.
709 #
710
711 if [[ $_history_substring_search_match_index -gt $#_history_substring_search_matches ]]; then
712 #
713 # We are beyond the end of $_history_substring_search_matches. This
714 # can only happen if we have also exhausted the unprocessed matches in
715 # _history_substring_search_raw_matches.
716 #
717 # 1. Update display to indicate search not found.
718 #
719 _history-substring-search-not-found
720 return
721 fi
722
723 if _history-substring-search-has-next; then
724 #
725 # We do have older matches.
726 #
727 # 1. Move index to point to the next match.
728 # 2. Update display to indicate search found.
729 #
730 _history_substring_search_match_index+=1
731 _history-substring-search-found
732
733 else
734 #
735 # We do not have older matches.
736 #
737 # 1. Move the index beyond the end of
738 # _history_substring_search_matches.
739 # 2. Update display to indicate search not found.
740 #
741 _history_substring_search_match_index+=1
742 _history-substring-search-not-found
743 fi
744
745 #
746 # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from
747 # history should be matched, make sure the new and old results are different.
748 #
749 # However, if the HIST_IGNORE_ALL_DUPS shell option, or
750 # HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set, then we already have a
751 # unique history, so in this case we do not need to do anything.
752 #
753 if [[ -o HIST_IGNORE_ALL_DUPS || -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then
754 return
755 fi
756
757 if [[ -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then
758 #
759 # Repeat the current search so that a different (unique) match is found.
760 #
761 _history-substring-search-up-search
762 fi
763}
764
765_history-substring-search-down-search() {
766 _history_substring_search_refresh_display=1
767
768 #
769 # Select history entry during history-substring-down-search:
770 #
771 # The following variables have been initialized in
772 # _history-substring-search-up/down-search():
773 #
774 # $_history_substring_search_matches is the current list of matches that
775 # need to be displayed to the user.
776 # $_history_substring_search_match_index is the index of the current match
777 # that is being displayed to the user.
778 #
779 # The range of values that $_history_substring_search_match_index can take
780 # is: [0, $#_history_substring_search_matches + 1]. A value of 0
781 # indicates that we are beyond the beginning of
782 # $_history_substring_search_matches. A value of
783 # $#_history_substring_search_matches + 1 indicates that we are beyond
784 # the end of $_history_substring_search_matches and that we have also
785 # processed all entries in _history_substring_search_raw_matches.
786 #
787 # In _history-substring-search-down-search() the initial value of
788 # $_history_substring_search_match_index is 1. This value is set in
789 # _history-substring-search-begin(). _history-substring-search-down-search()
790 # will initially decrement it to 0.
791 #
792
793 if [[ $_history_substring_search_match_index -lt 1 ]]; then
794 #
795 # We are beyond the beginning of $_history_substring_search_matches.
796 #
797 # 1. Update display to indicate search not found.
798 #
799 _history-substring-search-not-found
800 return
801 fi
802
803 if _history-substring-search-has-prev; then
804 #
805 # We do have younger matches.
806 #
807 # 1. Move index to point to the previous match.
808 # 2. Update display to indicate search found.
809 #
810 _history_substring_search_match_index+=-1
811 _history-substring-search-found
812
813 else
814 #
815 # We do not have younger matches.
816 #
817 # 1. Move the index beyond the beginning of
818 # _history_substring_search_matches.
819 # 2. Update display to indicate search not found.
820 #
821 _history_substring_search_match_index+=-1
822 _history-substring-search-not-found
823 fi
824
825 #
826 # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from
827 # history should be matched, make sure the new and old results are different.
828 #
829 # However, if the HIST_IGNORE_ALL_DUPS shell option, or
830 # HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set, then we already have a
831 # unique history, so in this case we do not need to do anything.
832 #
833 if [[ -o HIST_IGNORE_ALL_DUPS || -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then
834 return
835 fi
836
837 if [[ -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then
838 #
839 # Repeat the current search so that a different (unique) match is found.
840 #
841 _history-substring-search-down-search
842 fi
843}
844
845# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
846# vim: ft=zsh sw=2 ts=2 et