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