Skip to content

Commit

Permalink
Merge pull request #1 from surajp/zsh-support
Browse files Browse the repository at this point in the history
Add Zsh support
  • Loading branch information
surajp authored Sep 19, 2021
2 parents 7059df2 + 9bea430 commit 92162d4
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 22 deletions.
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Supercharge your Salesforce CLI game with FZF!
## Supercharge your Salesforce CLI game with FZF! (For Bash and Zsh)

Fuzzy find Salesforce CLI commands and flags as you type, with live previews of what each command and flag does. No more typos, switching to the browser or running `help` to lookup commands and associated options. Autocomplete doesn't get any better than this!

Expand All @@ -9,29 +9,47 @@ See it in action below
#### Pre-Requisites

- [FZF](https://github.com/junegunn/fzf)
(If you use the command line in any capacity, do yourself and favor and install fzf. Seriously, even if you don't plan
(If you use the cli more than the average user, do yourself and favor and install fzf. Seriously, even if you don't plan
on using this sfdx script, install FZF. It will change your life.)
- [jq](https://stedolan.github.io/jq/download/)
- [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli) (the npm version installed using `npm i -g sfdx-cli`)
- Bash shell
- Bash or Zsh shell

#### Setup

- Copy [fzf-keybindings.bash](https://github.com/junegunn/fzf/blob/master/shell/key-bindings.bash) from the `fzf` repo
if you haven't already as part of installation, to a suitable directory and add it to your `~/.bashrc` file.
- Add the contents of the [fzf-keybindings.bash](./fzf-key-bindings.bash) file from this repo to the appropriate sections in your `fzf-keybindings.bash` file above. (The functions `__fzf_sfdx` and `__fzf_sfdx_flags__` go in the functions section and the
keybindings go in the keybindings section). Note that the keybinding I'm using here is `Ctrl-l`. Feel free to change it in this file.
- Run the following command `sfdx commands --json > ~/.sfdxcommands.json`. (You can add it your `.bashrc` or `.profile`
file to run it automatically every time you login. Note that this could add a noticeable delay to launching a new terminal)
##### Bash

- Copy [fzf-key-bindings.bash](./fzf-key-bindings.bash) to a directory on your machine.
- Replace the path to the `key-bindings.bash` file in `$HOME/.fzf.bash` with the path to the file above.
- Run the following command `sfdx commands --json > ~/.sfdxcommands.json`. (You can add it your `.bashrc`
file to run it automatically every time you login. Note that this could add a noticeable delay to launching a new terminal).
- Restart your shell or run `source $HOME/.bashrc`.

##### Zsh

- Copy [fzf-key-bindings.zsh](./fzf-key-bindings.zsh) to a directory on your machine.
- Replace the path to the `key-bindings.zsh` file in `$HOME/.fzf.zsh` with the path to the file above.
- Run the following command `sfdx commands --json > ~/.sfdxcommands.json`. (You can add it your `.zshrc`
file to run it automatically every time you login. Note that this could add a noticeable delay to launching a new terminal).
- Restart your shell or run `source $HOME/.zshrc`.

#### Usage

- Type `Ctrl-e` on a new line to bring up the list of commands to fuzzy search through. The preview window shows up to
the right with the command description and examples. Use arrow keys or `Ctrl-k` and `Ctrl-J` to move up and down through the list of commands. Use `Alt-K` and `Alt-J` to move the preview window up and down, which can be useful for commands with long previews. Hit `Enter` to select a command and print it out onto the terminal.
- Once a command is selected, or if you have typed an `sfdx` command in manually (without fzf), hitting `Ctrl-l` again will bring
- Once a command is selected, or if you have typed an `sfdx` command in manually (without fzf), hitting `Ctrl-e` again will bring
up the list of flags associated with the command. As you scroll through the list of flags, the preview window will show a description of the flag and some of its properties. Hit `Enter` to select the flag and print it out onto the terminal.

#### Notes

- This script only works when `sfdx` is the only command on a line (i.e. doesn't work if you are trying to pipe the output of another command to sfdx, for example)
- If you don't end up adding `sfdx commands --json > ~/.sfdxcommands.json` to your `.profile` or `.bashrc` file, run the command manually from time to time to keep up with updates to the CLI
- The script binds `Ctrl-e` to bring up sfdx commands. If you'd like to change the mapping, you can change it [here](./fzf-key-bindings.bash#L130) (for bash) or [here](./fzf-key-bindings.zsh#L151) (for zsh)
- If you'd like to use this in VS Code's integrated terminal, you'd need to prevent `Ctrl-e` from being hijacked by VSC to quick open files and have it be sent to the shell itself. This can be accomplished by adding the following line to `settings.json`. Note the hyphen(`-`) before the setting name to unbind the action.

```json
"terminal.integrated.commandsToSkipShell": [
"-workbench.action.quickOpen"
]
```

112 changes: 100 additions & 12 deletions fzf-key-bindings.bash
Original file line number Diff line number Diff line change
@@ -1,3 +1,63 @@
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/ key-bindings.bash
#
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS

# Key bindings
# ------------
__fzf_select__() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
printf '%q ' "$item"
done
echo
}

if [[ $- =~ i ]]; then

__fzfcmd() {
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
}

fzf-file-widget() {
local selected="$(__fzf_select__)"
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
}

__fzf_cd__() {
local cmd dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
}

__fzf_history__() {
local output
output=$(
builtin fc -lnr -2147483648 |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
) || return
READLINE_LINE=${output#*$'\t'}
if [ -z "$READLINE_POINT" ]; then
echo "$READLINE_LINE"
else
READLINE_POINT=0x7fffffff
fi
}

__fzf_sfdx_flags__(){
local selected="$1"
Expand All @@ -9,7 +69,7 @@ __fzf_sfdx_flags__(){
echo "${ret//$'\n'/ --}"
}

__fzf_sfdx__(){
fzf-sfdx(){
local fullcmd="$READLINE_LINE"
local cmd="$(echo $fullcmd | awk '{print $1}')"
local subcmd="$(echo $fullcmd | awk '{print $2}')"
Expand All @@ -19,10 +79,12 @@ __fzf_sfdx__(){
local flag="$(__fzf_sfdx_flags__ $subcmd $fullcmd)"
if [[ "$flag" != "" ]]
then
READLINE_LINE="$fullcmd --$flag"
READLINE_POINT=$(( ${#fullcmd} + ${#flag} + 3 ))
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}--$flag${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#flag} + 3 ))
#READLINE_LINE="$fullcmd --$flag"
#READLINE_POINT=$(( ${#fullcmd} + ${#flag} + 3 ))
fi
elif [[ "$mcd" == "sfdx" || "$cmd" == "" ]]
elif [[ "$cmd" == "sfdx" || "$cmd" == "" ]]
then
local querystr=""
if [[ "$subcmd" != "" ]]; then
Expand All @@ -36,18 +98,44 @@ __fzf_sfdx__(){
fi
}

# Required to refresh the prompt after fzf
bind -m emacs-standard '"\er": redraw-current-line'

if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
bind -m vi-command '"\C-z": emacs-editing-mode'
bind -m vi-insert '"\C-z": emacs-editing-mode'
bind -m emacs-standard '"\C-z": vi-editing-mode'

# CTRL-K - Search through sfdx commands
bind -m vi-command '"\C-e": "\C-z\C-e\C-z"'
bind -m vi-insert '"\C-e": "\C-z\C-e\C-z"'
if (( BASH_VERSINFO[0] < 4 )); then
# CTRL-T - Paste the selected file path into the command line
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'

# CTRL-R - Paste the selected command from history into the command line
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
else
# CTRL-T - Paste the selected file path into the command line
bind -m emacs-standard -x '"\C-f": fzf-file-widget'
bind -m vi-command -x '"\C-f": fzf-file-widget'
bind -m vi-insert -x '"\C-f": fzf-file-widget'

# CTRL-R - Paste the selected command from history into the command line
bind -m emacs-standard -x '"\C-r": __fzf_history__'
bind -m vi-command -x '"\C-r": __fzf_history__'
bind -m vi-insert -x '"\C-r": __fzf_history__'

# CTRL-e - Search for and paste the selected sfdx command onto the command line
bind -m emacs-standard -x '"\C-e": fzf-sfdx'
bind -m vi-command -x '"\C-e": fzf-sfdx'
bind -m vi-insert -x '"\C-e": fzf-sfdx'

fi

# CTRL-K - Search through sfdx commands
bind -m emacs-standard -x '"\C-e": __fzf_sfdx__'
bind -m vi-command -x '"\C-e": __fzf_sfdx__'
bind -m vi-insert -x '"\C-e": __fzf_sfdx__'
# ALT-C - cd into the selected directory
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'

fi
156 changes: 156 additions & 0 deletions fzf-key-bindings.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/ key-bindings.zsh
#
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS

# Key bindings
# ------------

# The code at the top and the bottom of this file is the same as in completion.zsh.
# Refer to that file for explanation.
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
__fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})"
else
() {
__fzf_key_bindings_options="setopt"
'local' '__fzf_opt'
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
if [[ -o "$__fzf_opt" ]]; then
__fzf_key_bindings_options+=" -o $__fzf_opt"
else
__fzf_key_bindings_options+=" +o $__fzf_opt"
fi
done
}
fi

'emulate' 'zsh' '-o' 'no_aliases'

{

[[ -o interactive ]] || return 0

# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
local item
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
echo -n "${(q)item} "
done
local ret=$?
echo
return $ret
}

__fzfcmd() {
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
}

fzf-file-widget() {
LBUFFER="${LBUFFER}$(__fsel)"
local ret=$?
zle reset-prompt
return $ret
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget

# ALT-C - cd into the selected directory
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0
fi
zle push-line # Clear buffer. Auto-restored on next prompt.
BUFFER="cd ${(q)dir}"
zle accept-line
local ret=$?
unset dir # ensure this doesn't end up appearing in prompt expansion
zle reset-prompt
return $ret
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget

# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
if [ -n "$num" ]; then
zle vi-fetch-history -n $num
fi
fi
zle reset-prompt
return $ret
}

zle -N fzf-history-widget
bindkey '^R' fzf-history-widget

__fzf_sfdx_flags(){
local selected="$1"
local fullcmd=""
for i in "${@:2}"
do fullcmd+=" $i"
done
local ret=`cat ~/.sfdxcommands.json | jq -r ".[] | select(.id==\"$selected\") | .flags | keys[]" | $(__fzfcmd) -m --bind='ctrl-z:ignore,alt-j:preview-down,alt-k:preview-up' --preview='cat ~/.sfdxcommands.json | jq -r ".[] | select(.id==\"'$selected'\") | .flags | to_entries[] | select (.key==\""{}"\") | [\"Command:\n'"$fullcmd"'\n\",\"Flag Description:\",.value][]"' --preview-window='right:wrap'`
echo "${ret//$'\n'/ --}"
}


fzf-sfdx(){
local fullcmd="$LBUFFER"
local cmd="$(echo $fullcmd | awk '{print $1}')"
local subcmd="$(echo $fullcmd | awk '{print $2}')"
local match="$(cat ~/.sfdxcommands.json | jq -r '.[] | select(.id=="'$subcmd'")')"
if [[ "$cmd" = "sfdx" && "$match" != "" ]]
then
local flag="$(__fzf_sfdx_flags $subcmd $fullcmd)"
if [[ "$flag" != "" ]]
then
LBUFFER="${LBUFFER:0:$CURSOR} --$flag${LBUFFER:$CURSOR}"
fi
elif [[ "$cmd" == "sfdx" || "$cmd" == "" ]]
then
local querystr=""
if [[ "$subcmd" != "" ]]; then
querystr="--query $subcmd"
fi
local selected="$(cat ~/.sfdxcommands.json | jq -r '.[].id' | $(__fzfcmd) +m --bind=ctrl-z:ignore,alt-j:preview-down,alt-k:preview-up --preview='cat ~/.sfdxcommands.json | jq -r ".[] | select (.id==\""{}"\") | [\"\nDescription:\n \"+.description,\"\nUsage:\n \"+select(has(\"usage\")).usage, \"\nExamples:\n \"+(select(has(\"examples\")).examples|join(\"\n\"))][]"' --preview-window='right:wrap' $querystr)"
if [[ "$selected" != "" ]]; then
LBUFFER="sfdx $selected"
fi
fi
local ret=$?
zle reset-prompt
return $ret
}
zle -N fzf-sfdx{,}
bindkey "^e" fzf-sfdx

} always {
eval $__fzf_key_bindings_options
'unset' '__fzf_key_bindings_options'
}

0 comments on commit 92162d4

Please sign in to comment.