comparison .zfun/zsh-autosuggestions/zsh-autosuggestions.zsh @ 467:e1ce8897030d

zsh: import df6f6f9ff41 of zsh-autosuggestions
author Augie Fackler <raf@durin42.com>
date Mon, 03 Dec 2018 22:37:29 -0500
parents
children
comparison
equal deleted inserted replaced
466:f248cf012d9a 467:e1ce8897030d
1 # Fish-like fast/unobtrusive autosuggestions for zsh.
2 # https://github.com/zsh-users/zsh-autosuggestions
3 # v0.5.0
4 # Copyright (c) 2013 Thiago de Arruda
5 # Copyright (c) 2016-2018 Eric Freese
6 #
7 # Permission is hereby granted, free of charge, to any person
8 # obtaining a copy of this software and associated documentation
9 # files (the "Software"), to deal in the Software without
10 # restriction, including without limitation the rights to use,
11 # copy, modify, merge, publish, distribute, sublicense, and/or sell
12 # copies of the Software, and to permit persons to whom the
13 # Software is furnished to do so, subject to the following
14 # conditions:
15 #
16 # The above copyright notice and this permission notice shall be
17 # included in all copies or substantial portions of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 # OTHER DEALINGS IN THE SOFTWARE.
27
28 #--------------------------------------------------------------------#
29 # Setup #
30 #--------------------------------------------------------------------#
31
32 # Precmd hooks for initializing the library and starting pty's
33 autoload -Uz add-zsh-hook
34
35 # Asynchronous suggestions are generated in a pty
36 zmodload zsh/zpty
37
38 #--------------------------------------------------------------------#
39 # Global Configuration Variables #
40 #--------------------------------------------------------------------#
41
42 # Color to use when highlighting suggestion
43 # Uses format of `region_highlight`
44 # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets
45 : ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'}
46
47 # Prefix to use when saving original versions of bound widgets
48 : ${ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-}
49
50 # Strategies to use to fetch a suggestion
51 # Will try each strategy in order until a suggestion is returned
52 (( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && ZSH_AUTOSUGGEST_STRATEGY=(history)
53
54 # Widgets that clear the suggestion
55 (( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && ZSH_AUTOSUGGEST_CLEAR_WIDGETS=(
56 history-search-forward
57 history-search-backward
58 history-beginning-search-forward
59 history-beginning-search-backward
60 history-substring-search-up
61 history-substring-search-down
62 up-line-or-beginning-search
63 down-line-or-beginning-search
64 up-line-or-history
65 down-line-or-history
66 accept-line
67 )
68
69 # Widgets that accept the entire suggestion
70 (( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
71 forward-char
72 end-of-line
73 vi-forward-char
74 vi-end-of-line
75 vi-add-eol
76 )
77
78 # Widgets that accept the entire suggestion and execute it
79 (( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(
80 )
81
82 # Widgets that accept the suggestion as far as the cursor moves
83 (( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(
84 forward-word
85 emacs-forward-word
86 vi-forward-word
87 vi-forward-word-end
88 vi-forward-blank-word
89 vi-forward-blank-word-end
90 vi-find-next-char
91 vi-find-next-char-skip
92 )
93
94 # Widgets that should be ignored (globbing supported but must be escaped)
95 (( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(
96 orig-\*
97 beep
98 run-help
99 set-local-history
100 which-command
101 yank
102 yank-pop
103 )
104
105 # Max size of buffer to trigger autosuggestion. Leave null for no upper bound.
106 : ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=}
107
108 # Pty name for calculating autosuggestions asynchronously
109 : ${ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty}
110
111 #--------------------------------------------------------------------#
112 # Utility Functions #
113 #--------------------------------------------------------------------#
114
115 _zsh_autosuggest_escape_command() {
116 setopt localoptions EXTENDED_GLOB
117
118 # Escape special chars in the string (requires EXTENDED_GLOB)
119 echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}"
120 }
121
122 #--------------------------------------------------------------------#
123 # Feature Detection #
124 #--------------------------------------------------------------------#
125
126 _zsh_autosuggest_feature_detect_zpty_returns_fd() {
127 typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD
128 typeset -h REPLY
129
130 zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }'
131
132 if (( REPLY )); then
133 _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1
134 else
135 _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0
136 fi
137
138 zpty -d zsh_autosuggest_feature_detect
139 }
140
141 #--------------------------------------------------------------------#
142 # Widget Helpers #
143 #--------------------------------------------------------------------#
144
145 _zsh_autosuggest_incr_bind_count() {
146 if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then
147 ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++))
148 else
149 _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1
150 fi
151
152 typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]
153 }
154
155 _zsh_autosuggest_get_bind_count() {
156 if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then
157 typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]
158 else
159 typeset -gi bind_count=0
160 fi
161 }
162
163 # Bind a single widget to an autosuggest widget, saving a reference to the original widget
164 _zsh_autosuggest_bind_widget() {
165 typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS
166
167 local widget=$1
168 local autosuggest_action=$2
169 local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX
170
171 local -i bind_count
172
173 # Save a reference to the original widget
174 case $widgets[$widget] in
175 # Already bound
176 user:_zsh_autosuggest_(bound|orig)_*);;
177
178 # User-defined widget
179 user:*)
180 _zsh_autosuggest_incr_bind_count $widget
181 zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:}
182 ;;
183
184 # Built-in widget
185 builtin)
186 _zsh_autosuggest_incr_bind_count $widget
187 eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
188 zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget
189 ;;
190
191 # Completion widget
192 completion:*)
193 _zsh_autosuggest_incr_bind_count $widget
194 eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}"
195 ;;
196 esac
197
198 _zsh_autosuggest_get_bind_count $widget
199
200 # Pass the original widget's name explicitly into the autosuggest
201 # function. Use this passed in widget name to call the original
202 # widget instead of relying on the $WIDGET variable being set
203 # correctly. $WIDGET cannot be trusted because other plugins call
204 # zle without the `-w` flag (e.g. `zle self-insert` instead of
205 # `zle self-insert -w`).
206 eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() {
207 _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@
208 }"
209
210 # Create the bound widget
211 zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget
212 }
213
214 # Map all configured widgets to the right autosuggest widgets
215 _zsh_autosuggest_bind_widgets() {
216 emulate -L zsh
217
218 local widget
219 local ignore_widgets
220
221 ignore_widgets=(
222 .\*
223 _\*
224 zle-\*
225 autosuggest-\*
226 $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\*
227 $ZSH_AUTOSUGGEST_IGNORE_WIDGETS
228 )
229
230 # Find every widget we might want to bind and bind it appropriately
231 for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
232 if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
233 _zsh_autosuggest_bind_widget $widget clear
234 elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
235 _zsh_autosuggest_bind_widget $widget accept
236 elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
237 _zsh_autosuggest_bind_widget $widget execute
238 elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
239 _zsh_autosuggest_bind_widget $widget partial_accept
240 else
241 # Assume any unspecified widget might modify the buffer
242 _zsh_autosuggest_bind_widget $widget modify
243 fi
244 done
245 }
246
247 # Given the name of an original widget and args, invoke it, if it exists
248 _zsh_autosuggest_invoke_original_widget() {
249 # Do nothing unless called with at least one arg
250 (( $# )) || return 0
251
252 local original_widget_name="$1"
253
254 shift
255
256 if (( ${+widgets[$original_widget_name]} )); then
257 zle $original_widget_name -- $@
258 fi
259 }
260
261 #--------------------------------------------------------------------#
262 # Highlighting #
263 #--------------------------------------------------------------------#
264
265 # If there was a highlight, remove it
266 _zsh_autosuggest_highlight_reset() {
267 typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
268
269 if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
270 region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
271 unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
272 fi
273 }
274
275 # If there's a suggestion, highlight it
276 _zsh_autosuggest_highlight_apply() {
277 typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
278
279 if (( $#POSTDISPLAY )); then
280 typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
281 region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
282 else
283 unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
284 fi
285 }
286
287 #--------------------------------------------------------------------#
288 # Autosuggest Widget Implementations #
289 #--------------------------------------------------------------------#
290
291 # Disable suggestions
292 _zsh_autosuggest_disable() {
293 typeset -g _ZSH_AUTOSUGGEST_DISABLED
294 _zsh_autosuggest_clear
295 }
296
297 # Enable suggestions
298 _zsh_autosuggest_enable() {
299 unset _ZSH_AUTOSUGGEST_DISABLED
300
301 if (( $#BUFFER )); then
302 _zsh_autosuggest_fetch
303 fi
304 }
305
306 # Toggle suggestions (enable/disable)
307 _zsh_autosuggest_toggle() {
308 if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then
309 _zsh_autosuggest_enable
310 else
311 _zsh_autosuggest_disable
312 fi
313 }
314
315 # Clear the suggestion
316 _zsh_autosuggest_clear() {
317 # Remove the suggestion
318 unset POSTDISPLAY
319
320 _zsh_autosuggest_invoke_original_widget $@
321 }
322
323 # Modify the buffer and get a new suggestion
324 _zsh_autosuggest_modify() {
325 emulate -L zsh
326
327 local -i retval
328
329 # Only available in zsh >= 5.4
330 local -i KEYS_QUEUED_COUNT
331
332 # Save the contents of the buffer/postdisplay
333 local orig_buffer="$BUFFER"
334 local orig_postdisplay="$POSTDISPLAY"
335
336 # Clear suggestion while waiting for next one
337 unset POSTDISPLAY
338
339 # Original widget may modify the buffer
340 _zsh_autosuggest_invoke_original_widget $@
341 retval=$?
342
343 # Don't fetch a new suggestion if there's more input to be read immediately
344 if (( $PENDING > 0 )) || (( $KEYS_QUEUED_COUNT > 0 )); then
345 POSTDISPLAY="$orig_postdisplay"
346 return $retval
347 fi
348
349 # Optimize if manually typing in the suggestion
350 if (( $#BUFFER > $#orig_buffer )); then
351 local added=${BUFFER#$orig_buffer}
352
353 # If the string added matches the beginning of the postdisplay
354 if [[ "$added" = "${orig_postdisplay:0:$#added}" ]]; then
355 POSTDISPLAY="${orig_postdisplay:$#added}"
356 return $retval
357 fi
358 fi
359
360 # Don't fetch a new suggestion if the buffer hasn't changed
361 if [[ "$BUFFER" = "$orig_buffer" ]]; then
362 POSTDISPLAY="$orig_postdisplay"
363 return $retval
364 fi
365
366 # Bail out if suggestions are disabled
367 if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then
368 return $?
369 fi
370
371 # Get a new suggestion if the buffer is not empty after modification
372 if (( $#BUFFER > 0 )); then
373 if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
374 _zsh_autosuggest_fetch
375 fi
376 fi
377
378 return $retval
379 }
380
381 # Fetch a new suggestion based on what's currently in the buffer
382 _zsh_autosuggest_fetch() {
383 if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then
384 _zsh_autosuggest_async_request "$BUFFER"
385 else
386 local suggestion
387 _zsh_autosuggest_fetch_suggestion "$BUFFER"
388 _zsh_autosuggest_suggest "$suggestion"
389 fi
390 }
391
392 # Offer a suggestion
393 _zsh_autosuggest_suggest() {
394 emulate -L zsh
395
396 local suggestion="$1"
397
398 if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
399 POSTDISPLAY="${suggestion#$BUFFER}"
400 else
401 unset POSTDISPLAY
402 fi
403 }
404
405 # Accept the entire suggestion
406 _zsh_autosuggest_accept() {
407 local -i max_cursor_pos=$#BUFFER
408
409 # When vicmd keymap is active, the cursor can't move all the way
410 # to the end of the buffer
411 if [[ "$KEYMAP" = "vicmd" ]]; then
412 max_cursor_pos=$((max_cursor_pos - 1))
413 fi
414
415 # Only accept if the cursor is at the end of the buffer
416 if [[ $CURSOR = $max_cursor_pos ]]; then
417 # Add the suggestion to the buffer
418 BUFFER="$BUFFER$POSTDISPLAY"
419
420 # Remove the suggestion
421 unset POSTDISPLAY
422
423 # Move the cursor to the end of the buffer
424 CURSOR=${#BUFFER}
425 fi
426
427 _zsh_autosuggest_invoke_original_widget $@
428 }
429
430 # Accept the entire suggestion and execute it
431 _zsh_autosuggest_execute() {
432 # Add the suggestion to the buffer
433 BUFFER="$BUFFER$POSTDISPLAY"
434
435 # Remove the suggestion
436 unset POSTDISPLAY
437
438 # Call the original `accept-line` to handle syntax highlighting or
439 # other potential custom behavior
440 _zsh_autosuggest_invoke_original_widget "accept-line"
441 }
442
443 # Partially accept the suggestion
444 _zsh_autosuggest_partial_accept() {
445 local -i retval cursor_loc
446
447 # Save the contents of the buffer so we can restore later if needed
448 local original_buffer="$BUFFER"
449
450 # Temporarily accept the suggestion.
451 BUFFER="$BUFFER$POSTDISPLAY"
452
453 # Original widget moves the cursor
454 _zsh_autosuggest_invoke_original_widget $@
455 retval=$?
456
457 # Normalize cursor location across vi/emacs modes
458 cursor_loc=$CURSOR
459 if [[ "$KEYMAP" = "vicmd" ]]; then
460 cursor_loc=$((cursor_loc + 1))
461 fi
462
463 # If we've moved past the end of the original buffer
464 if (( $cursor_loc > $#original_buffer )); then
465 # Set POSTDISPLAY to text right of the cursor
466 POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}"
467
468 # Clip the buffer at the cursor
469 BUFFER="${BUFFER[1,$cursor_loc]}"
470 else
471 # Restore the original buffer
472 BUFFER="$original_buffer"
473 fi
474
475 return $retval
476 }
477
478 for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do
479 eval "_zsh_autosuggest_widget_$action() {
480 local -i retval
481
482 _zsh_autosuggest_highlight_reset
483
484 _zsh_autosuggest_$action \$@
485 retval=\$?
486
487 _zsh_autosuggest_highlight_apply
488
489 zle -R
490
491 return \$retval
492 }"
493 done
494
495 zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch
496 zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest
497 zle -N autosuggest-accept _zsh_autosuggest_widget_accept
498 zle -N autosuggest-clear _zsh_autosuggest_widget_clear
499 zle -N autosuggest-execute _zsh_autosuggest_widget_execute
500 zle -N autosuggest-enable _zsh_autosuggest_widget_enable
501 zle -N autosuggest-disable _zsh_autosuggest_widget_disable
502 zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle
503
504 #--------------------------------------------------------------------#
505 # History Suggestion Strategy #
506 #--------------------------------------------------------------------#
507 # Suggests the most recent history item that matches the given
508 # prefix.
509 #
510
511 _zsh_autosuggest_strategy_history() {
512 # Reset options to defaults and enable LOCAL_OPTIONS
513 emulate -L zsh
514
515 # Enable globbing flags so that we can use (#m)
516 setopt EXTENDED_GLOB
517
518 # Escape backslashes and all of the glob operators so we can use
519 # this string as a pattern to search the $history associative array.
520 # - (#m) globbing flag enables setting references for match data
521 # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
522 local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
523
524 # Get the history items that match
525 # - (r) subscript flag makes the pattern match on values
526 typeset -g suggestion="${history[(r)${prefix}*]}"
527 }
528
529 #--------------------------------------------------------------------#
530 # Match Previous Command Suggestion Strategy #
531 #--------------------------------------------------------------------#
532 # Suggests the most recent history item that matches the given
533 # prefix and whose preceding history item also matches the most
534 # recently executed command.
535 #
536 # For example, suppose your history has the following entries:
537 # - pwd
538 # - ls foo
539 # - ls bar
540 # - pwd
541 #
542 # Given the history list above, when you type 'ls', the suggestion
543 # will be 'ls foo' rather than 'ls bar' because your most recently
544 # executed command (pwd) was previously followed by 'ls foo'.
545 #
546 # Note that this strategy won't work as expected with ZSH options that don't
547 # preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
548 # `HIST_EXPIRE_DUPS_FIRST`.
549
550 _zsh_autosuggest_strategy_match_prev_cmd() {
551 # Reset options to defaults and enable LOCAL_OPTIONS
552 emulate -L zsh
553
554 # Enable globbing flags so that we can use (#m)
555 setopt EXTENDED_GLOB
556
557 # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
558 local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
559
560 # Get all history event numbers that correspond to history
561 # entries that match pattern $prefix*
562 local history_match_keys
563 history_match_keys=(${(k)history[(R)$prefix*]})
564
565 # By default we use the first history number (most recent history entry)
566 local histkey="${history_match_keys[1]}"
567
568 # Get the previously executed command
569 local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")"
570
571 # Iterate up to the first 200 history event numbers that match $prefix
572 for key in "${(@)history_match_keys[1,200]}"; do
573 # Stop if we ran out of history
574 [[ $key -gt 1 ]] || break
575
576 # See if the history entry preceding the suggestion matches the
577 # previous command, and use it if it does
578 if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then
579 histkey="$key"
580 break
581 fi
582 done
583
584 # Give back the matched history entry
585 typeset -g suggestion="$history[$histkey]"
586 }
587
588 #--------------------------------------------------------------------#
589 # Fetch Suggestion #
590 #--------------------------------------------------------------------#
591 # Loops through all specified strategies and returns a suggestion
592 # from the first strategy to provide one.
593 #
594
595 _zsh_autosuggest_fetch_suggestion() {
596 typeset -g suggestion
597 local -a strategies
598
599 # Ensure we are working with an array
600 strategies=(${=ZSH_AUTOSUGGEST_STRATEGY})
601
602 for strategy in $strategies; do
603 # Try to get a suggestion from this strategy
604 _zsh_autosuggest_strategy_$strategy "$1"
605
606 # Break once we've found a suggestion
607 [[ -n "$suggestion" ]] && break
608 done
609 }
610
611 #--------------------------------------------------------------------#
612 # Async #
613 #--------------------------------------------------------------------#
614
615 # Zpty process is spawned running this function
616 _zsh_autosuggest_async_server() {
617 emulate -R zsh
618
619 # There is a bug in zpty module (fixed in zsh/master) by which a
620 # zpty that exits will kill all zpty processes that were forked
621 # before it. Here we set up a zsh exit hook to SIGKILL the zpty
622 # process immediately, before it has a chance to kill any other
623 # zpty processes.
624 zshexit() {
625 kill -KILL $$
626 sleep 1 # Block for long enough for the signal to come through
627 }
628
629 # Don't add any extra carriage returns
630 stty -onlcr
631
632 # Don't translate carriage returns to newlines
633 stty -icrnl
634
635 # Silence any error messages
636 exec 2>/dev/null
637
638 local last_pid
639
640 while IFS='' read -r -d $'\0' query; do
641 # Kill last bg process
642 kill -KILL $last_pid &>/dev/null
643
644 # Run suggestion search in the background
645 (
646 local suggestion
647 _zsh_autosuggest_fetch_suggestion "$query"
648 echo -n -E "$suggestion"$'\0'
649 ) &
650
651 last_pid=$!
652 done
653 }
654
655 _zsh_autosuggest_async_request() {
656 # Write the query to the zpty process to fetch a suggestion
657 zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0'
658 }
659
660 # Called when new data is ready to be read from the pty
661 # First arg will be fd ready for reading
662 # Second arg will be passed in case of error
663 _zsh_autosuggest_async_response() {
664 setopt LOCAL_OPTIONS EXTENDED_GLOB
665
666 local suggestion
667
668 zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null
669 zle autosuggest-suggest -- "${suggestion%%$'\0'##}"
670 }
671
672 _zsh_autosuggest_async_pty_create() {
673 # With newer versions of zsh, REPLY stores the fd to read from
674 typeset -h REPLY
675
676 # If we won't get a fd back from zpty, try to guess it
677 if (( ! $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD )); then
678 integer -l zptyfd
679 exec {zptyfd}>&1 # Open a new file descriptor (above 10).
680 exec {zptyfd}>&- # Close it so it's free to be used by zpty.
681 fi
682
683 # Fork a zpty process running the server function
684 zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server
685
686 # Store the fd so we can remove the handler later
687 if (( REPLY )); then
688 _ZSH_AUTOSUGGEST_PTY_FD=$REPLY
689 else
690 _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd
691 fi
692
693 # Set up input handler from the zpty
694 zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response
695 }
696
697 _zsh_autosuggest_async_pty_destroy() {
698 # Remove the input handler
699 zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null
700
701 # Destroy the zpty
702 zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null
703 }
704
705 _zsh_autosuggest_async_pty_recreate() {
706 _zsh_autosuggest_async_pty_destroy
707 _zsh_autosuggest_async_pty_create
708 }
709
710 _zsh_autosuggest_async_start() {
711 typeset -g _ZSH_AUTOSUGGEST_PTY_FD
712
713 _zsh_autosuggest_feature_detect_zpty_returns_fd
714 _zsh_autosuggest_async_pty_recreate
715
716 # We recreate the pty to get a fresh list of history events
717 add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate
718 }
719
720 #--------------------------------------------------------------------#
721 # Start #
722 #--------------------------------------------------------------------#
723
724 # Start the autosuggestion widgets
725 _zsh_autosuggest_start() {
726 add-zsh-hook -d precmd _zsh_autosuggest_start
727
728 _zsh_autosuggest_bind_widgets
729
730 # Re-bind widgets on every precmd to ensure we wrap other wrappers.
731 # Specifically, highlighting breaks if our widgets are wrapped by
732 # zsh-syntax-highlighting widgets. This also allows modifications
733 # to the widget list variables to take effect on the next precmd.
734 add-zsh-hook precmd _zsh_autosuggest_bind_widgets
735
736 if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then
737 _zsh_autosuggest_async_start
738 fi
739 }
740
741 # Start the autosuggestion widgets on the next precmd
742 add-zsh-hook precmd _zsh_autosuggest_start