zsh-interactive-cd.plugin.zsh 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #!/usr/bin/env zsh
  2. #
  3. # Copyright 2017-2018 Henry Chang
  4. #
  5. # This Source Code Form is subject to the terms of the Mozilla Public
  6. # License, v. 2.0. If a copy of the MPL was not distributed with this
  7. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  8. __zic_fzf_prog() {
  9. [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] \
  10. && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
  11. }
  12. __zic_matched_subdir_list() {
  13. local dir length seg starts_with_dir
  14. if [[ "$1" == */ ]]; then
  15. dir="$1"
  16. if [[ "$dir" != / ]]; then
  17. dir="${dir: : -1}"
  18. fi
  19. length=$(echo -n "$dir" | wc -c)
  20. if [ "$dir" = "/" ]; then
  21. length=0
  22. fi
  23. find -L "$dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \
  24. | cut -b $(( ${length} + 2 ))- | command sed '/^$/d' | while read -r line; do
  25. if [[ "${line[1]}" == "." ]]; then
  26. continue
  27. fi
  28. echo "$line"
  29. done
  30. else
  31. dir=$(dirname -- "$1")
  32. length=$(echo -n "$dir" | wc -c)
  33. if [ "$dir" = "/" ]; then
  34. length=0
  35. fi
  36. seg=$(basename -- "$1")
  37. starts_with_dir=$( \
  38. find -L "$dir" -mindepth 1 -maxdepth 1 -type d \
  39. 2>/dev/null | cut -b $(( ${length} + 2 ))- | command sed '/^$/d' \
  40. | while read -r line; do
  41. if [[ "${seg[1]}" != "." && "${line[1]}" == "." ]]; then
  42. continue
  43. fi
  44. if [ "$zic_case_insensitive" = "true" ]; then
  45. if [[ "$line:u" == "$seg:u"* ]]; then
  46. echo "$line"
  47. fi
  48. else
  49. if [[ "$line" == "$seg"* ]]; then
  50. echo "$line"
  51. fi
  52. fi
  53. done
  54. )
  55. if [ -n "$starts_with_dir" ]; then
  56. echo "$starts_with_dir"
  57. else
  58. find -L "$dir" -mindepth 1 -maxdepth 1 -type d \
  59. 2>/dev/null | cut -b $(( ${length} + 2 ))- | command sed '/^$/d' \
  60. | while read -r line; do
  61. if [[ "${seg[1]}" != "." && "${line[1]}" == "." ]]; then
  62. continue
  63. fi
  64. if [ "$zic_case_insensitive" = "true" ]; then
  65. if [[ "$line:u" == *"$seg:u"* ]]; then
  66. echo "$line"
  67. fi
  68. else
  69. if [[ "$line" == *"$seg"* ]]; then
  70. echo "$line"
  71. fi
  72. fi
  73. done
  74. fi
  75. fi
  76. }
  77. __zic_fzf_bindings() {
  78. autoload is-at-least
  79. fzf=$(__zic_fzf_prog)
  80. if $(is-at-least '0.21.0' $(${=fzf} --version)); then
  81. echo 'shift-tab:up,tab:down,bspace:backward-delete-char/eof'
  82. else
  83. echo 'shift-tab:up,tab:down'
  84. fi
  85. }
  86. _zic_list_generator() {
  87. __zic_matched_subdir_list "${(Q)@[-1]}" | sort
  88. }
  89. _zic_complete() {
  90. setopt localoptions nonomatch
  91. local l matches fzf tokens base
  92. l=$(_zic_list_generator $@)
  93. if [ -z "$l" ]; then
  94. zle ${__zic_default_completion:-expand-or-complete}
  95. return
  96. fi
  97. fzf=$(__zic_fzf_prog)
  98. fzf_bindings=$(__zic_fzf_bindings)
  99. if [ $(echo $l | wc -l) -eq 1 ]; then
  100. matches=${(q)l}
  101. else
  102. matches=$(echo $l \
  103. | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} \
  104. --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS \
  105. --bind '${fzf_bindings}'" ${=fzf} \
  106. | while read -r item; do
  107. echo -n "${(q)item} "
  108. done)
  109. fi
  110. matches=${matches% }
  111. if [ -n "$matches" ]; then
  112. tokens=(${(z)LBUFFER})
  113. base="${(Q)@[-1]}"
  114. if [[ "$base" != */ ]]; then
  115. if [[ "$base" == */* ]]; then
  116. base="$(dirname -- "$base")"
  117. if [[ ${base[-1]} != / ]]; then
  118. base="$base/"
  119. fi
  120. else
  121. base=""
  122. fi
  123. fi
  124. LBUFFER="${tokens[1]} "
  125. if [ -n "$base" ]; then
  126. base="${(q)base}"
  127. if [ "${tokens[2][1]}" = "~" ]; then
  128. base="${base/#$HOME/~}"
  129. fi
  130. LBUFFER="${LBUFFER}${base}"
  131. fi
  132. LBUFFER="${LBUFFER}${matches}/"
  133. fi
  134. zle redisplay
  135. typeset -f zle-line-init >/dev/null && zle zle-line-init
  136. }
  137. zic-completion() {
  138. setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
  139. local tokens cmd
  140. tokens=(${(z)LBUFFER})
  141. cmd=${tokens[1]}
  142. if [[ "$LBUFFER" =~ "^\ *cd$" ]]; then
  143. zle ${__zic_default_completion:-expand-or-complete}
  144. elif [ "$cmd" = cd ]; then
  145. _zic_complete ${tokens[2,${#tokens}]/#\~/$HOME}
  146. else
  147. zle ${__zic_default_completion:-expand-or-complete}
  148. fi
  149. }
  150. [ -z "$__zic_default_completion" ] && {
  151. binding=$(bindkey '^I')
  152. # $binding[(s: :w)2]
  153. # The command substitution and following word splitting to determine the
  154. # default zle widget for ^I formerly only works if the IFS parameter contains
  155. # a space via $binding[(w)2]. Now it specifically splits at spaces, regardless
  156. # of IFS.
  157. [[ $binding =~ 'undefined-key' ]] || __zic_default_completion=$binding[(s: :w)2]
  158. unset binding
  159. }
  160. zle -N zic-completion
  161. if [ -z $zic_custom_binding ]; then
  162. zic_custom_binding='^I'
  163. fi
  164. bindkey "${zic_custom_binding}" zic-completion