Parent directory

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