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 |
