25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

805 lines
24 KiB

8 년 전
5 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
8 년 전
  1. # Fish-like fast/unobtrusive autosuggestions for zsh.
  2. # https://github.com/zsh-users/zsh-autosuggestions
  3. # v0.5.2
  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. # 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. 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 [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; 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
  275. if (( $#BUFFER > $#orig_buffer )); then
  276. local added=${BUFFER#$orig_buffer}
  277. # If the string added matches the beginning of the postdisplay
  278. if [[ "$added" = "${orig_postdisplay:0:$#added}" ]]; then
  279. POSTDISPLAY="${orig_postdisplay:$#added}"
  280. return $retval
  281. fi
  282. fi
  283. # Don't fetch a new suggestion if the buffer hasn't changed
  284. if [[ "$BUFFER" = "$orig_buffer" ]]; then
  285. POSTDISPLAY="$orig_postdisplay"
  286. return $retval
  287. fi
  288. # Bail out if suggestions are disabled
  289. if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then
  290. return $?
  291. fi
  292. # Get a new suggestion if the buffer is not empty after modification
  293. if (( $#BUFFER > 0 )); then
  294. if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
  295. _zsh_autosuggest_fetch
  296. fi
  297. fi
  298. return $retval
  299. }
  300. # Fetch a new suggestion based on what's currently in the buffer
  301. _zsh_autosuggest_fetch() {
  302. if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then
  303. _zsh_autosuggest_async_request "$BUFFER"
  304. else
  305. local suggestion
  306. _zsh_autosuggest_fetch_suggestion "$BUFFER"
  307. _zsh_autosuggest_suggest "$suggestion"
  308. fi
  309. }
  310. # Offer a suggestion
  311. _zsh_autosuggest_suggest() {
  312. emulate -L zsh
  313. local suggestion="$1"
  314. if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
  315. POSTDISPLAY="${suggestion#$BUFFER}"
  316. else
  317. unset POSTDISPLAY
  318. fi
  319. }
  320. # Accept the entire suggestion
  321. _zsh_autosuggest_accept() {
  322. local -i max_cursor_pos=$#BUFFER
  323. # When vicmd keymap is active, the cursor can't move all the way
  324. # to the end of the buffer
  325. if [[ "$KEYMAP" = "vicmd" ]]; then
  326. max_cursor_pos=$((max_cursor_pos - 1))
  327. fi
  328. # Only accept if the cursor is at the end of the buffer
  329. if [[ $CURSOR = $max_cursor_pos ]]; then
  330. # Add the suggestion to the buffer
  331. BUFFER="$BUFFER$POSTDISPLAY"
  332. # Remove the suggestion
  333. unset POSTDISPLAY
  334. # Move the cursor to the end of the buffer
  335. CURSOR=${#BUFFER}
  336. fi
  337. _zsh_autosuggest_invoke_original_widget $@
  338. }
  339. # Accept the entire suggestion and execute it
  340. _zsh_autosuggest_execute() {
  341. # Add the suggestion to the buffer
  342. BUFFER="$BUFFER$POSTDISPLAY"
  343. # Remove the suggestion
  344. unset POSTDISPLAY
  345. # Call the original `accept-line` to handle syntax highlighting or
  346. # other potential custom behavior
  347. _zsh_autosuggest_invoke_original_widget "accept-line"
  348. }
  349. # Partially accept the suggestion
  350. _zsh_autosuggest_partial_accept() {
  351. local -i retval cursor_loc
  352. # Save the contents of the buffer so we can restore later if needed
  353. local original_buffer="$BUFFER"
  354. # Temporarily accept the suggestion.
  355. BUFFER="$BUFFER$POSTDISPLAY"
  356. # Original widget moves the cursor
  357. _zsh_autosuggest_invoke_original_widget $@
  358. retval=$?
  359. # Normalize cursor location across vi/emacs modes
  360. cursor_loc=$CURSOR
  361. if [[ "$KEYMAP" = "vicmd" ]]; then
  362. cursor_loc=$((cursor_loc + 1))
  363. fi
  364. # If we've moved past the end of the original buffer
  365. if (( $cursor_loc > $#original_buffer )); then
  366. # Set POSTDISPLAY to text right of the cursor
  367. POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}"
  368. # Clip the buffer at the cursor
  369. BUFFER="${BUFFER[1,$cursor_loc]}"
  370. else
  371. # Restore the original buffer
  372. BUFFER="$original_buffer"
  373. fi
  374. return $retval
  375. }
  376. () {
  377. local action
  378. for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do
  379. eval "_zsh_autosuggest_widget_$action() {
  380. local -i retval
  381. _zsh_autosuggest_highlight_reset
  382. _zsh_autosuggest_$action \$@
  383. retval=\$?
  384. _zsh_autosuggest_highlight_apply
  385. zle -R
  386. return \$retval
  387. }"
  388. done
  389. zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch
  390. zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest
  391. zle -N autosuggest-accept _zsh_autosuggest_widget_accept
  392. zle -N autosuggest-clear _zsh_autosuggest_widget_clear
  393. zle -N autosuggest-execute _zsh_autosuggest_widget_execute
  394. zle -N autosuggest-enable _zsh_autosuggest_widget_enable
  395. zle -N autosuggest-disable _zsh_autosuggest_widget_disable
  396. zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle
  397. }
  398. #--------------------------------------------------------------------#
  399. # Completion Suggestion Strategy #
  400. #--------------------------------------------------------------------#
  401. # Fetches a suggestion from the completion engine
  402. #
  403. _zsh_autosuggest_capture_postcompletion() {
  404. # Always insert the first completion into the buffer
  405. compstate[insert]=1
  406. # Don't list completions
  407. unset 'compstate[list]'
  408. }
  409. _zsh_autosuggest_capture_completion_widget() {
  410. # Add a post-completion hook to be called after all completions have been
  411. # gathered. The hook can modify compstate to affect what is done with the
  412. # gathered completions.
  413. local -a +h comppostfuncs
  414. comppostfuncs=(_zsh_autosuggest_capture_postcompletion)
  415. # Only capture completions at the end of the buffer
  416. CURSOR=$#BUFFER
  417. # Run the original widget wrapping `.complete-word` so we don't
  418. # recursively try to fetch suggestions, since our pty is forked
  419. # after autosuggestions is initialized.
  420. zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]}
  421. # The completion has been added, print the buffer as the suggestion
  422. echo -nE - $'\0'$BUFFER$'\0'
  423. }
  424. zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget
  425. _zsh_autosuggest_capture_setup() {
  426. autoload -Uz is-at-least
  427. # There is a bug in zpty module in older zsh versions by which a
  428. # zpty that exits will kill all zpty processes that were forked
  429. # before it. Here we set up a zsh exit hook to SIGKILL the zpty
  430. # process immediately, before it has a chance to kill any other
  431. # zpty processes.
  432. if ! is-at-least 5.4; then
  433. zshexit() {
  434. # The zsh builtin `kill` fails sometimes in older versions
  435. # https://unix.stackexchange.com/a/477647/156673
  436. kill -KILL $$ 2>&- || command kill -KILL $$
  437. # Block for long enough for the signal to come through
  438. sleep 1
  439. }
  440. fi
  441. # Try to avoid any suggestions that wouldn't match the prefix
  442. zstyle ':completion:*' matcher-list ''
  443. zstyle ':completion:*' path-completion false
  444. zstyle ':completion:*' max-errors 0 not-numeric
  445. bindkey '^I' autosuggest-capture-completion
  446. }
  447. _zsh_autosuggest_capture_completion_sync() {
  448. _zsh_autosuggest_capture_setup
  449. zle autosuggest-capture-completion
  450. }
  451. _zsh_autosuggest_capture_completion_async() {
  452. _zsh_autosuggest_capture_setup
  453. zmodload zsh/parameter 2>/dev/null || return # For `$functions`
  454. # Make vared completion work as if for a normal command line
  455. # https://stackoverflow.com/a/7057118/154703
  456. autoload +X _complete
  457. functions[_original_complete]=$functions[_complete]
  458. _complete () {
  459. unset 'compstate[vared]'
  460. _original_complete "$@"
  461. }
  462. # Open zle with buffer set so we can capture completions for it
  463. vared 1
  464. }
  465. _zsh_autosuggest_strategy_completion() {
  466. typeset -g suggestion
  467. local line REPLY
  468. # Exit if we don't have completions
  469. whence compdef >/dev/null || return
  470. # Exit if we don't have zpty
  471. zmodload zsh/zpty 2>/dev/null || return
  472. # Zle will be inactive if we are in async mode
  473. if zle; then
  474. zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync
  475. else
  476. zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1"
  477. zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t'
  478. fi
  479. {
  480. # The completion result is surrounded by null bytes, so read the
  481. # content between the first two null bytes.
  482. zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0'
  483. # Extract the suggestion from between the null bytes. On older
  484. # versions of zsh (older than 5.3), we sometimes get extra bytes after
  485. # the second null byte, so trim those off the end.
  486. # See http://www.zsh.org/mla/workers/2015/msg03290.html
  487. suggestion="${${line#*$'\0'}%$'\0'*}"
  488. } always {
  489. # Destroy the pty
  490. zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME
  491. }
  492. }
  493. #--------------------------------------------------------------------#
  494. # History Suggestion Strategy #
  495. #--------------------------------------------------------------------#
  496. # Suggests the most recent history item that matches the given
  497. # prefix.
  498. #
  499. _zsh_autosuggest_strategy_history() {
  500. # Reset options to defaults and enable LOCAL_OPTIONS
  501. emulate -L zsh
  502. # Enable globbing flags so that we can use (#m)
  503. setopt EXTENDED_GLOB
  504. # Escape backslashes and all of the glob operators so we can use
  505. # this string as a pattern to search the $history associative array.
  506. # - (#m) globbing flag enables setting references for match data
  507. # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
  508. local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
  509. # Get the history items that match
  510. # - (r) subscript flag makes the pattern match on values
  511. typeset -g suggestion="${history[(r)${prefix}*]}"
  512. }
  513. #--------------------------------------------------------------------#
  514. # Match Previous Command Suggestion Strategy #
  515. #--------------------------------------------------------------------#
  516. # Suggests the most recent history item that matches the given
  517. # prefix and whose preceding history item also matches the most
  518. # recently executed command.
  519. #
  520. # For example, suppose your history has the following entries:
  521. # - pwd
  522. # - ls foo
  523. # - ls bar
  524. # - pwd
  525. #
  526. # Given the history list above, when you type 'ls', the suggestion
  527. # will be 'ls foo' rather than 'ls bar' because your most recently
  528. # executed command (pwd) was previously followed by 'ls foo'.
  529. #
  530. # Note that this strategy won't work as expected with ZSH options that don't
  531. # preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
  532. # `HIST_EXPIRE_DUPS_FIRST`.
  533. _zsh_autosuggest_strategy_match_prev_cmd() {
  534. # Reset options to defaults and enable LOCAL_OPTIONS
  535. emulate -L zsh
  536. # Enable globbing flags so that we can use (#m)
  537. setopt EXTENDED_GLOB
  538. # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
  539. local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
  540. # Get all history event numbers that correspond to history
  541. # entries that match pattern $prefix*
  542. local history_match_keys
  543. history_match_keys=(${(k)history[(R)$prefix*]})
  544. # By default we use the first history number (most recent history entry)
  545. local histkey="${history_match_keys[1]}"
  546. # Get the previously executed command
  547. local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")"
  548. # Iterate up to the first 200 history event numbers that match $prefix
  549. for key in "${(@)history_match_keys[1,200]}"; do
  550. # Stop if we ran out of history
  551. [[ $key -gt 1 ]] || break
  552. # See if the history entry preceding the suggestion matches the
  553. # previous command, and use it if it does
  554. if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then
  555. histkey="$key"
  556. break
  557. fi
  558. done
  559. # Give back the matched history entry
  560. typeset -g suggestion="$history[$histkey]"
  561. }
  562. #--------------------------------------------------------------------#
  563. # Fetch Suggestion #
  564. #--------------------------------------------------------------------#
  565. # Loops through all specified strategies and returns a suggestion
  566. # from the first strategy to provide one.
  567. #
  568. _zsh_autosuggest_fetch_suggestion() {
  569. typeset -g suggestion
  570. local -a strategies
  571. local strategy
  572. # Ensure we are working with an array
  573. strategies=(${=ZSH_AUTOSUGGEST_STRATEGY})
  574. for strategy in $strategies; do
  575. # Try to get a suggestion from this strategy
  576. _zsh_autosuggest_strategy_$strategy "$1"
  577. # Ensure the suggestion matches the prefix
  578. [[ "$suggestion" != "$1"* ]] && unset suggestion
  579. # Break once we've found a valid suggestion
  580. [[ -n "$suggestion" ]] && break
  581. done
  582. }
  583. #--------------------------------------------------------------------#
  584. # Async #
  585. #--------------------------------------------------------------------#
  586. zmodload zsh/system
  587. _zsh_autosuggest_async_request() {
  588. typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
  589. # If we've got a pending request, cancel it
  590. if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then
  591. # Close the file descriptor and remove the handler
  592. exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
  593. zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
  594. # Zsh will make a new process group for the child process only if job
  595. # control is enabled (MONITOR option)
  596. if [[ -o MONITOR ]]; then
  597. # Send the signal to the process group to kill any processes that may
  598. # have been forked by the suggestion strategy
  599. kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
  600. else
  601. # Kill just the child process since it wasn't placed in a new process
  602. # group. If the suggestion strategy forked any child processes they may
  603. # be orphaned and left behind.
  604. kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
  605. fi
  606. fi
  607. # Fork a process to fetch a suggestion and open a pipe to read from it
  608. exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
  609. # Tell parent process our pid
  610. echo $sysparams[pid]
  611. # Fetch and print the suggestion
  612. local suggestion
  613. _zsh_autosuggest_fetch_suggestion "$1"
  614. echo -nE "$suggestion"
  615. )
  616. # There's a weird bug here where ^C stops working unless we force a fork
  617. # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
  618. command true
  619. # Read the pid from the child process
  620. read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
  621. # When the fd is readable, call the response handler
  622. zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
  623. }
  624. # Called when new data is ready to be read from the pipe
  625. # First arg will be fd ready for reading
  626. # Second arg will be passed in case of error
  627. _zsh_autosuggest_async_response() {
  628. emulate -L zsh
  629. if [[ -z "$2" || "$2" == "hup" ]]; then
  630. # Read everything from the fd and give it as a suggestion
  631. zle autosuggest-suggest -- "$(cat <&$1)"
  632. # Close the fd
  633. exec {1}<&-
  634. fi
  635. # Always remove the handler
  636. zle -F "$1"
  637. }
  638. #--------------------------------------------------------------------#
  639. # Start #
  640. #--------------------------------------------------------------------#
  641. # Start the autosuggestion widgets
  642. _zsh_autosuggest_start() {
  643. # By default we re-bind widgets on every precmd to ensure we wrap other
  644. # wrappers. Specifically, highlighting breaks if our widgets are wrapped by
  645. # zsh-syntax-highlighting widgets. This also allows modifications to the
  646. # widget list variables to take effect on the next precmd. However this has
  647. # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND
  648. # to disable the automatic re-binding.
  649. if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then
  650. add-zsh-hook -d precmd _zsh_autosuggest_start
  651. fi
  652. _zsh_autosuggest_bind_widgets
  653. }
  654. # Start the autosuggestion widgets on the next precmd
  655. autoload -Uz add-zsh-hook
  656. add-zsh-hook precmd _zsh_autosuggest_start