Browse Source

Add zsh-interactive-cd plugin

Henry Chang 8 years ago
parent
commit
3ec04997eb

+ 23 - 0
plugins/zsh-interactive-cd/README.md

@@ -0,0 +1,23 @@
+# zsh-interactive-cd
+
+This plugin adds a fish-like interactive tab completion for the `cd` command.
+
+To use it, add `zsh-interactive-cd` to the plugins array of your zshrc file:
+```zsh
+plugins=(... zsh-interactive-cd)
+```
+
+![demo](https://user-images.githubusercontent.com/1441704/74360670-cb202900-4dc5-11ea-9734-f60caf726e85.gif)
+
+## Usage
+
+Press tab for completion as usual, it'll launch fzf automatically. Check fzf’s [readme](https://github.com/junegunn/fzf#search-syntax) for more search syntax usage.
+
+## Requirements
+
+This plugin requires [fzf](https://github.com/junegunn/fzf). Install it by following
+its [installation instructions](https://github.com/junegunn/fzf#installation).
+
+## Author
+
+[Henry Chang](https://github.com/changyuheng)

+ 147 - 0
plugins/zsh-interactive-cd/zsh-interactive-cd.plugin.zsh

@@ -0,0 +1,147 @@
+# Copyright (c) 2017 Henry Chang
+
+__zic_fzf_prog() {
+  [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] \
+    && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
+}
+
+__zic_matched_subdir_list() {
+  local dir length seg starts_with_dir
+  if [[ "$1" == */ ]]; then
+    dir="$1"
+    if [[ "$dir" != / ]]; then
+      dir="${dir: : -1}"
+    fi
+    length=$(echo -n "$dir" | wc -c)
+    if [ "$dir" = "/" ]; then
+      length=0
+    fi
+    find -L "$dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \
+        | cut -b $(( ${length} + 2 ))- | sed '/^$/d' | while read -r line; do
+      if [[ "${line[1]}" == "." ]]; then
+        continue
+      fi
+      echo "$line"
+    done
+  else
+    dir=$(dirname -- "$1")
+    length=$(echo -n "$dir" | wc -c)
+    if [ "$dir" = "/" ]; then
+      length=0
+    fi
+    seg=$(basename -- "$1")
+    starts_with_dir=$( \
+      find -L "$dir" -mindepth 1 -maxdepth 1 -type d \
+          2>/dev/null | cut -b $(( ${length} + 2 ))- | sed '/^$/d' \
+          | while read -r line; do
+        if [[ "${seg[1]}" != "." && "${line[1]}" == "." ]]; then
+          continue
+        fi
+        if [[ "$line" == "$seg"* ]]; then
+          echo "$line"
+        fi
+      done
+    )
+    if [ -n "$starts_with_dir" ]; then
+      echo "$starts_with_dir"
+    else
+      find -L "$dir" -mindepth 1 -maxdepth 1 -type d \
+          2>/dev/null | cut -b $(( ${length} + 2 ))- | sed '/^$/d' \
+          | while read -r line; do
+        if [[ "${seg[1]}" != "." && "${line[1]}" == "." ]]; then
+          continue
+        fi
+        if [[ "$line" == *"$seg"* ]]; then
+          echo "$line"
+        fi
+      done
+    fi
+  fi
+}
+
+_zic_list_generator() {
+  __zic_matched_subdir_list "${(Q)@[-1]}" | sort
+}
+
+_zic_complete() {
+  setopt localoptions nonomatch
+  local l matches fzf tokens base
+
+  l=$(_zic_list_generator $@)
+
+  if [ -z "$l" ]; then
+    zle ${__zic_default_completion:-expand-or-complete}
+    return
+  fi
+
+  fzf=$(__zic_fzf_prog)
+
+  if [ $(echo $l | wc -l) -eq 1 ]; then
+    matches=${(q)l}
+  else
+    matches=$(echo $l \
+        | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} \
+          --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS \
+          --bind 'shift-tab:up,tab:down'" ${=fzf} \
+        | while read -r item; do
+      echo -n "${(q)item} "
+    done)
+  fi
+
+  matches=${matches% }
+  if [ -n "$matches" ]; then
+    tokens=(${(z)LBUFFER})
+    base="${(Q)@[-1]}"
+    if [[ "$base" != */ ]]; then
+      if [[ "$base" == */* ]]; then
+        base="$(dirname -- "$base")"
+        if [[ ${base[-1]} != / ]]; then
+          base="$base/"
+        fi
+      else
+        base=""
+      fi
+    fi
+    LBUFFER="${tokens[1]} "
+    if [ -n "$base" ]; then
+      base="${(q)base}"
+      if [ "${tokens[2][1]}" = "~" ]; then
+        base="${base/#$HOME/~}"
+      fi
+      LBUFFER="${LBUFFER}${base}"
+    fi
+    LBUFFER="${LBUFFER}${matches}/"
+  fi
+  zle redisplay
+  typeset -f zle-line-init >/dev/null && zle zle-line-init
+}
+
+zic-completion() {
+  setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
+  local tokens cmd
+
+  tokens=(${(z)LBUFFER})
+  cmd=${tokens[1]}
+
+  if [[ "$LBUFFER" =~ "^\ *cd$" ]]; then
+    zle ${__zic_default_completion:-expand-or-complete}
+  elif [ "$cmd" = cd ]; then
+    _zic_complete ${tokens[2,${#tokens}]/#\~/$HOME}
+  else
+    zle ${__zic_default_completion:-expand-or-complete}
+  fi
+}
+
+[ -z "$__zic_default_completion" ] && {
+  binding=$(bindkey '^I')
+  # $binding[(s: :w)2]
+  # The command substitution and following word splitting to determine the
+  # default zle widget for ^I formerly only works if the IFS parameter contains
+  # a space via $binding[(w)2]. Now it specifically splits at spaces, regardless
+  # of IFS.
+  [[ $binding =~ 'undefined-key' ]] || __zic_default_completion=$binding[(s: :w)2]
+  unset binding
+}
+
+zle -N zic-completion
+bindkey '^I' zic-completion