Mercurial > dotfiles
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 |