zsh-autosuggestions.zsh 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  1. # Fish-like fast/unobtrusive autosuggestions for zsh.
  2. # https://github.com/zsh-users/zsh-autosuggestions
  3. # v0.7.0
  4. # Copyright (c) 2013 Thiago de Arruda
  5. # Copyright (c) 2016-2021 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. # Global Configuration Variables #
  29. #--------------------------------------------------------------------#
  30. # Color to use when highlighting suggestion
  31. # Uses format of `region_highlight`
  32. # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets
  33. (( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) &&
  34. typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
  35. # Prefix to use when saving original versions of bound widgets
  36. (( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) &&
  37. typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-
  38. # Strategies to use to fetch a suggestion
  39. # Will try each strategy in order until a suggestion is returned
  40. (( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && {
  41. typeset -ga ZSH_AUTOSUGGEST_STRATEGY
  42. ZSH_AUTOSUGGEST_STRATEGY=(history)
  43. }
  44. # Widgets that clear the suggestion
  45. (( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && {
  46. typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS
  47. ZSH_AUTOSUGGEST_CLEAR_WIDGETS=(
  48. history-search-forward
  49. history-search-backward
  50. history-beginning-search-forward
  51. history-beginning-search-backward
  52. history-substring-search-up
  53. history-substring-search-down
  54. up-line-or-beginning-search
  55. down-line-or-beginning-search
  56. up-line-or-history
  57. down-line-or-history
  58. accept-line
  59. copy-earlier-word
  60. )
  61. }
  62. # Widgets that accept the entire suggestion
  63. (( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && {
  64. typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS
  65. ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
  66. forward-char
  67. end-of-line
  68. vi-forward-char
  69. vi-end-of-line
  70. vi-add-eol
  71. )
  72. }
  73. # Widgets that accept the entire suggestion and execute it
  74. (( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && {
  75. typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS
  76. ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(
  77. )
  78. }
  79. # Widgets that accept the suggestion as far as the cursor moves
  80. (( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && {
  81. typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS
  82. ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(
  83. forward-word
  84. emacs-forward-word
  85. vi-forward-word
  86. vi-forward-word-end
  87. vi-forward-blank-word
  88. vi-forward-blank-word-end
  89. vi-find-next-char
  90. vi-find-next-char-skip
  91. )
  92. }
  93. # Widgets that should be ignored (globbing supported but must be escaped)
  94. (( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && {
  95. typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS
  96. ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(
  97. orig-\*
  98. beep
  99. run-help
  100. set-local-history
  101. which-command
  102. yank
  103. yank-pop
  104. zle-\*
  105. )
  106. }
  107. # Pty name for capturing completions for completion suggestion strategy
  108. (( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) &&
  109. typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty
  110. #--------------------------------------------------------------------#
  111. # Utility Functions #
  112. #--------------------------------------------------------------------#
  113. _zsh_autosuggest_escape_command() {
  114. setopt localoptions EXTENDED_GLOB
  115. # Escape special chars in the string (requires EXTENDED_GLOB)
  116. echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}"
  117. }
  118. #--------------------------------------------------------------------#
  119. # Widget Helpers #
  120. #--------------------------------------------------------------------#
  121. _zsh_autosuggest_incr_bind_count() {
  122. typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1))
  123. _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count
  124. }
  125. # Bind a single widget to an autosuggest widget, saving a reference to the original widget
  126. _zsh_autosuggest_bind_widget() {
  127. typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS
  128. local widget=$1
  129. local autosuggest_action=$2
  130. local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX
  131. local -i bind_count
  132. # Save a reference to the original widget
  133. case $widgets[$widget] in
  134. # Already bound
  135. user:_zsh_autosuggest_(bound|orig)_*)
  136. bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget]))
  137. ;;
  138. # User-defined widget
  139. user:*)
  140. _zsh_autosuggest_incr_bind_count $widget
  141. zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:}
  142. ;;
  143. # Built-in widget
  144. builtin)
  145. _zsh_autosuggest_incr_bind_count $widget
  146. eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
  147. zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget
  148. ;;
  149. # Completion widget
  150. completion:*)
  151. _zsh_autosuggest_incr_bind_count $widget
  152. eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}"
  153. ;;
  154. esac
  155. # Pass the original widget's name explicitly into the autosuggest
  156. # function. Use this passed in widget name to call the original
  157. # widget instead of relying on the $WIDGET variable being set
  158. # correctly. $WIDGET cannot be trusted because other plugins call
  159. # zle without the `-w` flag (e.g. `zle self-insert` instead of
  160. # `zle self-insert -w`).
  161. eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() {
  162. _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@
  163. }"
  164. # Create the bound widget
  165. zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget
  166. }
  167. # Map all configured widgets to the right autosuggest widgets
  168. _zsh_autosuggest_bind_widgets() {
  169. emulate -L zsh
  170. local widget
  171. local ignore_widgets
  172. ignore_widgets=(
  173. .\*
  174. _\*
  175. ${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/autosuggest-}
  176. $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\*
  177. $ZSH_AUTOSUGGEST_IGNORE_WIDGETS
  178. )
  179. # Find every widget we might want to bind and bind it appropriately
  180. for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
  181. if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
  182. _zsh_autosuggest_bind_widget $widget clear
  183. elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
  184. _zsh_autosuggest_bind_widget $widget accept
  185. elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
  186. _zsh_autosuggest_bind_widget $widget execute
  187. elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
  188. _zsh_autosuggest_bind_widget $widget partial_accept
  189. else
  190. # Assume any unspecified widget might modify the buffer
  191. _zsh_autosuggest_bind_widget $widget modify
  192. fi
  193. done
  194. }
  195. # Given the name of an original widget and args, invoke it, if it exists
  196. _zsh_autosuggest_invoke_original_widget() {
  197. # Do nothing unless called with at least one arg
  198. (( $# )) || return 0
  199. local original_widget_name="$1"
  200. shift
  201. if (( ${+widgets[$original_widget_name]} )); then
  202. zle $original_widget_name -- $@
  203. fi
  204. }
  205. #--------------------------------------------------------------------#
  206. # Highlighting #
  207. #--------------------------------------------------------------------#
  208. # If there was a highlight, remove it
  209. _zsh_autosuggest_highlight_reset() {
  210. typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
  211. if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
  212. region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
  213. unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
  214. fi
  215. }
  216. # If there's a suggestion, highlight it
  217. _zsh_autosuggest_highlight_apply() {
  218. typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
  219. if (( $#POSTDISPLAY )); then
  220. typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
  221. region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
  222. else
  223. unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
  224. fi
  225. }
  226. #--------------------------------------------------------------------#
  227. # Autosuggest Widget Implementations #
  228. #--------------------------------------------------------------------#
  229. # Disable suggestions
  230. _zsh_autosuggest_disable() {
  231. typeset -g _ZSH_AUTOSUGGEST_DISABLED
  232. _zsh_autosuggest_clear
  233. }
  234. # Enable suggestions
  235. _zsh_autosuggest_enable() {
  236. unset _ZSH_AUTOSUGGEST_DISABLED
  237. if (( $#BUFFER )); then
  238. _zsh_autosuggest_fetch
  239. fi
  240. }
  241. # Toggle suggestions (enable/disable)
  242. _zsh_autosuggest_toggle() {
  243. if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
  244. _zsh_autosuggest_enable
  245. else
  246. _zsh_autosuggest_disable
  247. fi
  248. }
  249. # Clear the suggestion
  250. _zsh_autosuggest_clear() {
  251. # Remove the suggestion
  252. unset POSTDISPLAY
  253. _zsh_autosuggest_invoke_original_widget $@
  254. }
  255. # Modify the buffer and get a new suggestion
  256. _zsh_autosuggest_modify() {
  257. local -i retval
  258. # Only available in zsh >= 5.4
  259. local -i KEYS_QUEUED_COUNT
  260. # Save the contents of the buffer/postdisplay
  261. local orig_buffer="$BUFFER"
  262. local orig_postdisplay="$POSTDISPLAY"
  263. # Clear suggestion while waiting for next one
  264. unset POSTDISPLAY
  265. # Original widget may modify the buffer
  266. _zsh_autosuggest_invoke_original_widget $@
  267. retval=$?
  268. emulate -L zsh
  269. # Don't fetch a new suggestion if there's more input to be read immediately
  270. if (( $PENDING > 0 || $KEYS_QUEUED_COUNT > 0 )); then
  271. POSTDISPLAY="$orig_postdisplay"
  272. return $retval
  273. fi
  274. # Optimize if manually typing in the suggestion or if buffer hasn't changed
  275. if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then
  276. POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}"
  277. return $retval
  278. fi
  279. # Bail out if suggestions are disabled
  280. if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
  281. return $?
  282. fi
  283. # Get a new suggestion if the buffer is not empty after modification
  284. if (( $#BUFFER > 0 )); then
  285. if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
  286. _zsh_autosuggest_fetch
  287. fi
  288. fi
  289. return $retval
  290. }
  291. # Fetch a new suggestion based on what's currently in the buffer
  292. _zsh_autosuggest_fetch() {
  293. if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then
  294. _zsh_autosuggest_async_request "$BUFFER"
  295. else
  296. local suggestion
  297. _zsh_autosuggest_fetch_suggestion "$BUFFER"
  298. _zsh_autosuggest_suggest "$suggestion"
  299. fi
  300. }
  301. # Offer a suggestion
  302. _zsh_autosuggest_suggest() {
  303. emulate -L zsh
  304. local suggestion="$1"
  305. if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
  306. POSTDISPLAY="${suggestion#$BUFFER}"
  307. else
  308. unset POSTDISPLAY
  309. fi
  310. }
  311. # Accept the entire suggestion
  312. _zsh_autosuggest_accept() {
  313. local -i retval max_cursor_pos=$#BUFFER
  314. # When vicmd keymap is active, the cursor can't move all the way
  315. # to the end of the buffer
  316. if [[ "$KEYMAP" = "vicmd" ]]; then
  317. max_cursor_pos=$((max_cursor_pos - 1))
  318. fi
  319. # If we're not in a valid state to accept a suggestion, just run the
  320. # original widget and bail out
  321. if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then
  322. _zsh_autosuggest_invoke_original_widget $@
  323. return
  324. fi
  325. # Only accept if the cursor is at the end of the buffer
  326. # Add the suggestion to the buffer
  327. BUFFER="$BUFFER$POSTDISPLAY"
  328. # Remove the suggestion
  329. unset POSTDISPLAY
  330. # Run the original widget before manually moving the cursor so that the
  331. # cursor movement doesn't make the widget do something unexpected
  332. _zsh_autosuggest_invoke_original_widget $@
  333. retval=$?
  334. # Move the cursor to the end of the buffer
  335. if [[ "$KEYMAP" = "vicmd" ]]; then
  336. CURSOR=$(($#BUFFER - 1))
  337. else
  338. CURSOR=$#BUFFER
  339. fi
  340. return $retval
  341. }
  342. # Accept the entire suggestion and execute it
  343. _zsh_autosuggest_execute() {
  344. # Add the suggestion to the buffer
  345. BUFFER="$BUFFER$POSTDISPLAY"
  346. # Remove the suggestion
  347. unset POSTDISPLAY
  348. # Call the original `accept-line` to handle syntax highlighting or
  349. # other potential custom behavior
  350. _zsh_autosuggest_invoke_original_widget "accept-line"
  351. }
  352. # Partially accept the suggestion
  353. _zsh_autosuggest_partial_accept() {
  354. local -i retval cursor_loc
  355. # Save the contents of the buffer so we can restore later if needed
  356. local original_buffer="$BUFFER"
  357. # Temporarily accept the suggestion.
  358. BUFFER="$BUFFER$POSTDISPLAY"
  359. # Original widget moves the cursor
  360. _zsh_autosuggest_invoke_original_widget $@
  361. retval=$?
  362. # Normalize cursor location across vi/emacs modes
  363. cursor_loc=$CURSOR
  364. if [[ "$KEYMAP" = "vicmd" ]]; then
  365. cursor_loc=$((cursor_loc + 1))
  366. fi
  367. # If we've moved past the end of the original buffer
  368. if (( $cursor_loc > $#original_buffer )); then
  369. # Set POSTDISPLAY to text right of the cursor
  370. POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}"
  371. # Clip the buffer at the cursor
  372. BUFFER="${BUFFER[1,$cursor_loc]}"
  373. else
  374. # Restore the original buffer
  375. BUFFER="$original_buffer"
  376. fi
  377. return $retval
  378. }
  379. () {
  380. typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS
  381. _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=(
  382. clear
  383. fetch
  384. suggest
  385. accept
  386. execute
  387. enable
  388. disable
  389. toggle
  390. )
  391. local action
  392. for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; do
  393. eval "_zsh_autosuggest_widget_$action() {
  394. local -i retval
  395. _zsh_autosuggest_highlight_reset
  396. _zsh_autosuggest_$action \$@
  397. retval=\$?
  398. _zsh_autosuggest_highlight_apply
  399. zle -R
  400. return \$retval
  401. }"
  402. done
  403. for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do
  404. zle -N autosuggest-$action _zsh_autosuggest_widget_$action
  405. done
  406. }
  407. #--------------------------------------------------------------------#
  408. # Completion Suggestion Strategy #
  409. #--------------------------------------------------------------------#
  410. # Fetches a suggestion from the completion engine
  411. #
  412. _zsh_autosuggest_capture_postcompletion() {
  413. # Always insert the first completion into the buffer
  414. compstate[insert]=1
  415. # Don't list completions
  416. unset 'compstate[list]'
  417. }
  418. _zsh_autosuggest_capture_completion_widget() {
  419. # Add a post-completion hook to be called after all completions have been
  420. # gathered. The hook can modify compstate to affect what is done with the
  421. # gathered completions.
  422. local -a +h comppostfuncs
  423. comppostfuncs=(_zsh_autosuggest_capture_postcompletion)
  424. # Only capture completions at the end of the buffer
  425. CURSOR=$#BUFFER
  426. # Run the original widget wrapping `.complete-word` so we don't
  427. # recursively try to fetch suggestions, since our pty is forked
  428. # after autosuggestions is initialized.
  429. zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]}
  430. if is-at-least 5.0.3; then
  431. # Don't do any cr/lf transformations. We need to do this immediately before
  432. # output because if we do it in setup, onlcr will be re-enabled when we enter
  433. # vared in the async code path. There is a bug in zpty module in older versions
  434. # where the tty is not properly attached to the pty slave, resulting in stty
  435. # getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream
  436. # commit f75904a38
  437. stty -onlcr -ocrnl -F /dev/tty
  438. fi
  439. # The completion has been added, print the buffer as the suggestion
  440. echo -nE - $'\0'$BUFFER$'\0'
  441. }
  442. zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget
  443. _zsh_autosuggest_capture_setup() {
  444. # There is a bug in zpty module in older zsh versions by which a
  445. # zpty that exits will kill all zpty processes that were forked
  446. # before it. Here we set up a zsh exit hook to SIGKILL the zpty
  447. # process immediately, before it has a chance to kill any other
  448. # zpty processes.
  449. if ! is-at-least 5.4; then
  450. zshexit() {
  451. # The zsh builtin `kill` fails sometimes in older versions
  452. # https://unix.stackexchange.com/a/477647/156673
  453. kill -KILL $$ 2>&- || command kill -KILL $$
  454. # Block for long enough for the signal to come through
  455. sleep 1
  456. }
  457. fi
  458. # Try to avoid any suggestions that wouldn't match the prefix
  459. zstyle ':completion:*' matcher-list ''
  460. zstyle ':completion:*' path-completion false
  461. zstyle ':completion:*' max-errors 0 not-numeric
  462. bindkey '^I' autosuggest-capture-completion
  463. }
  464. _zsh_autosuggest_capture_completion_sync() {
  465. _zsh_autosuggest_capture_setup
  466. zle autosuggest-capture-completion
  467. }
  468. _zsh_autosuggest_capture_completion_async() {
  469. _zsh_autosuggest_capture_setup
  470. zmodload zsh/parameter 2>/dev/null || return # For `$functions`
  471. # Make vared completion work as if for a normal command line
  472. # https://stackoverflow.com/a/7057118/154703
  473. autoload +X _complete
  474. functions[_original_complete]=$functions[_complete]
  475. function _complete() {
  476. unset 'compstate[vared]'
  477. _original_complete "$@"
  478. }
  479. # Open zle with buffer set so we can capture completions for it
  480. vared 1
  481. }
  482. _zsh_autosuggest_strategy_completion() {
  483. # Reset options to defaults and enable LOCAL_OPTIONS
  484. emulate -L zsh
  485. # Enable extended glob for completion ignore pattern
  486. setopt EXTENDED_GLOB
  487. typeset -g suggestion
  488. local line REPLY
  489. # Exit if we don't have completions
  490. whence compdef >/dev/null || return
  491. # Exit if we don't have zpty
  492. zmodload zsh/zpty 2>/dev/null || return
  493. # Exit if our search string matches the ignore pattern
  494. [[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return
  495. # Zle will be inactive if we are in async mode
  496. if zle; then
  497. zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync
  498. else
  499. zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1"
  500. zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t'
  501. fi
  502. {
  503. # The completion result is surrounded by null bytes, so read the
  504. # content between the first two null bytes.
  505. zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0'
  506. # Extract the suggestion from between the null bytes. On older
  507. # versions of zsh (older than 5.3), we sometimes get extra bytes after
  508. # the second null byte, so trim those off the end.
  509. # See http://www.zsh.org/mla/workers/2015/msg03290.html
  510. suggestion="${${(@0)line}[2]}"
  511. } always {
  512. # Destroy the pty
  513. zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME
  514. }
  515. }
  516. #--------------------------------------------------------------------#
  517. # History Suggestion Strategy #
  518. #--------------------------------------------------------------------#
  519. # Suggests the most recent history item that matches the given
  520. # prefix.
  521. #
  522. _zsh_autosuggest_strategy_history() {
  523. # Reset options to defaults and enable LOCAL_OPTIONS
  524. emulate -L zsh
  525. # Enable globbing flags so that we can use (#m) and (x~y) glob operator
  526. setopt EXTENDED_GLOB
  527. # Escape backslashes and all of the glob operators so we can use
  528. # this string as a pattern to search the $history associative array.
  529. # - (#m) globbing flag enables setting references for match data
  530. # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
  531. local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
  532. # Get the history items that match the prefix, excluding those that match
  533. # the ignore pattern
  534. local pattern="$prefix*"
  535. if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
  536. pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
  537. fi
  538. # Give the first history item matching the pattern as the suggestion
  539. # - (r) subscript flag makes the pattern match on values
  540. typeset -g suggestion="${history[(r)$pattern]}"
  541. }
  542. #--------------------------------------------------------------------#
  543. # Match Previous Command Suggestion Strategy #
  544. #--------------------------------------------------------------------#
  545. # Suggests the most recent history item that matches the given
  546. # prefix and whose preceding history item also matches the most
  547. # recently executed command.
  548. #
  549. # For example, suppose your history has the following entries:
  550. # - pwd
  551. # - ls foo
  552. # - ls bar
  553. # - pwd
  554. #
  555. # Given the history list above, when you type 'ls', the suggestion
  556. # will be 'ls foo' rather than 'ls bar' because your most recently
  557. # executed command (pwd) was previously followed by 'ls foo'.
  558. #
  559. # Note that this strategy won't work as expected with ZSH options that don't
  560. # preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
  561. # `HIST_EXPIRE_DUPS_FIRST`.
  562. _zsh_autosuggest_strategy_match_prev_cmd() {
  563. # Reset options to defaults and enable LOCAL_OPTIONS
  564. emulate -L zsh
  565. # Enable globbing flags so that we can use (#m) and (x~y) glob operator
  566. setopt EXTENDED_GLOB
  567. # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
  568. local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
  569. # Get the history items that match the prefix, excluding those that match
  570. # the ignore pattern
  571. local pattern="$prefix*"
  572. if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
  573. pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
  574. fi
  575. # Get all history event numbers that correspond to history
  576. # entries that match the pattern
  577. local history_match_keys
  578. history_match_keys=(${(k)history[(R)$~pattern]})
  579. # By default we use the first history number (most recent history entry)
  580. local histkey="${history_match_keys[1]}"
  581. # Get the previously executed command
  582. local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")"
  583. # Iterate up to the first 200 history event numbers that match $prefix
  584. for key in "${(@)history_match_keys[1,200]}"; do
  585. # Stop if we ran out of history
  586. [[ $key -gt 1 ]] || break
  587. # See if the history entry preceding the suggestion matches the
  588. # previous command, and use it if it does
  589. if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then
  590. histkey="$key"
  591. break
  592. fi
  593. done
  594. # Give back the matched history entry
  595. typeset -g suggestion="$history[$histkey]"
  596. }
  597. #--------------------------------------------------------------------#
  598. # Fetch Suggestion #
  599. #--------------------------------------------------------------------#
  600. # Loops through all specified strategies and returns a suggestion
  601. # from the first strategy to provide one.
  602. #
  603. _zsh_autosuggest_fetch_suggestion() {
  604. typeset -g suggestion
  605. local -a strategies
  606. local strategy
  607. # Ensure we are working with an array
  608. strategies=(${=ZSH_AUTOSUGGEST_STRATEGY})
  609. for strategy in $strategies; do
  610. # Try to get a suggestion from this strategy
  611. _zsh_autosuggest_strategy_$strategy "$1"
  612. # Ensure the suggestion matches the prefix
  613. [[ "$suggestion" != "$1"* ]] && unset suggestion
  614. # Break once we've found a valid suggestion
  615. [[ -n "$suggestion" ]] && break
  616. done
  617. }
  618. #--------------------------------------------------------------------#
  619. # Async #
  620. #--------------------------------------------------------------------#
  621. _zsh_autosuggest_async_request() {
  622. zmodload zsh/system 2>/dev/null # For `$sysparams`
  623. typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
  624. # If we've got a pending request, cancel it
  625. if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then
  626. # Close the file descriptor and remove the handler
  627. exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
  628. zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
  629. # We won't know the pid unless the user has zsh/system module installed
  630. if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
  631. # Zsh will make a new process group for the child process only if job
  632. # control is enabled (MONITOR option)
  633. if [[ -o MONITOR ]]; then
  634. # Send the signal to the process group to kill any processes that may
  635. # have been forked by the suggestion strategy
  636. kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
  637. else
  638. # Kill just the child process since it wasn't placed in a new process
  639. # group. If the suggestion strategy forked any child processes they may
  640. # be orphaned and left behind.
  641. kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
  642. fi
  643. fi
  644. fi
  645. # Fork a process to fetch a suggestion and open a pipe to read from it
  646. exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
  647. # Tell parent process our pid
  648. echo $sysparams[pid]
  649. # Fetch and print the suggestion
  650. local suggestion
  651. _zsh_autosuggest_fetch_suggestion "$1"
  652. echo -nE "$suggestion"
  653. )
  654. # There's a weird bug here where ^C stops working unless we force a fork
  655. # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
  656. autoload -Uz is-at-least
  657. is-at-least 5.8 || command true
  658. # Read the pid from the child process
  659. read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
  660. # When the fd is readable, call the response handler
  661. zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
  662. }
  663. # Called when new data is ready to be read from the pipe
  664. # First arg will be fd ready for reading
  665. # Second arg will be passed in case of error
  666. _zsh_autosuggest_async_response() {
  667. emulate -L zsh
  668. local suggestion
  669. if [[ -z "$2" || "$2" == "hup" ]]; then
  670. # Read everything from the fd and give it as a suggestion
  671. IFS='' read -rd '' -u $1 suggestion
  672. zle autosuggest-suggest -- "$suggestion"
  673. # Close the fd
  674. exec {1}<&-
  675. fi
  676. # Always remove the handler
  677. zle -F "$1"
  678. }
  679. #--------------------------------------------------------------------#
  680. # Start #
  681. #--------------------------------------------------------------------#
  682. # Start the autosuggestion widgets
  683. _zsh_autosuggest_start() {
  684. # By default we re-bind widgets on every precmd to ensure we wrap other
  685. # wrappers. Specifically, highlighting breaks if our widgets are wrapped by
  686. # zsh-syntax-highlighting widgets. This also allows modifications to the
  687. # widget list variables to take effect on the next precmd. However this has
  688. # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND
  689. # to disable the automatic re-binding.
  690. if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then
  691. add-zsh-hook -d precmd _zsh_autosuggest_start
  692. fi
  693. _zsh_autosuggest_bind_widgets
  694. }
  695. # Mark for auto-loading the functions that we use
  696. autoload -Uz add-zsh-hook is-at-least
  697. # Automatically enable asynchronous mode in newer versions of zsh. Disable for
  698. # older versions because there is a bug when using async mode where ^C does not
  699. # work immediately after fetching a suggestion.
  700. # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
  701. if is-at-least 5.0.8; then
  702. typeset -g ZSH_AUTOSUGGEST_USE_ASYNC=
  703. fi
  704. # Start the autosuggestion widgets on the next precmd
  705. add-zsh-hook precmd _zsh_autosuggest_start