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