Skip to content

Commit 92162d4

Browse files
authored
Merge pull request #1 from surajp/zsh-support
Add Zsh support
2 parents 7059df2 + 9bea430 commit 92162d4

3 files changed

Lines changed: 284 additions & 22 deletions

File tree

README.md

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Supercharge your Salesforce CLI game with FZF!
1+
## Supercharge your Salesforce CLI game with FZF! (For Bash and Zsh)
22

33
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!
44

@@ -9,29 +9,47 @@ See it in action below
99
#### Pre-Requisites
1010

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

1818
#### Setup
1919

20-
- Copy [fzf-keybindings.bash](https://github.com/junegunn/fzf/blob/master/shell/key-bindings.bash) from the `fzf` repo
21-
if you haven't already as part of installation, to a suitable directory and add it to your `~/.bashrc` file.
22-
- 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
23-
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.
24-
- Run the following command `sfdx commands --json > ~/.sfdxcommands.json`. (You can add it your `.bashrc` or `.profile`
25-
file to run it automatically every time you login. Note that this could add a noticeable delay to launching a new terminal)
20+
##### Bash
21+
22+
- Copy [fzf-key-bindings.bash](./fzf-key-bindings.bash) to a directory on your machine.
23+
- Replace the path to the `key-bindings.bash` file in `$HOME/.fzf.bash` with the path to the file above.
24+
- Run the following command `sfdx commands --json > ~/.sfdxcommands.json`. (You can add it your `.bashrc`
25+
file to run it automatically every time you login. Note that this could add a noticeable delay to launching a new terminal).
26+
- Restart your shell or run `source $HOME/.bashrc`.
27+
28+
##### Zsh
29+
30+
- Copy [fzf-key-bindings.zsh](./fzf-key-bindings.zsh) to a directory on your machine.
31+
- Replace the path to the `key-bindings.zsh` file in `$HOME/.fzf.zsh` with the path to the file above.
32+
- Run the following command `sfdx commands --json > ~/.sfdxcommands.json`. (You can add it your `.zshrc`
33+
file to run it automatically every time you login. Note that this could add a noticeable delay to launching a new terminal).
34+
- Restart your shell or run `source $HOME/.zshrc`.
2635

2736
#### Usage
2837

2938
- Type `Ctrl-e` on a new line to bring up the list of commands to fuzzy search through. The preview window shows up to
3039
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.
31-
- Once a command is selected, or if you have typed an `sfdx` command in manually (without fzf), hitting `Ctrl-l` again will bring
40+
- Once a command is selected, or if you have typed an `sfdx` command in manually (without fzf), hitting `Ctrl-e` again will bring
3241
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.
3342

3443
#### Notes
3544

3645
- 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)
3746
- 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
47+
- 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)
48+
- 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.
49+
50+
```json
51+
"terminal.integrated.commandsToSkipShell": [
52+
"-workbench.action.quickOpen"
53+
]
54+
```
55+

fzf-key-bindings.bash

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,63 @@
1+
# ____ ____
2+
# / __/___ / __/
3+
# / /_/_ / / /_
4+
# / __/ / /_/ __/
5+
# /_/ /___/_/ key-bindings.bash
6+
#
7+
# - $FZF_TMUX_OPTS
8+
# - $FZF_CTRL_T_COMMAND
9+
# - $FZF_CTRL_T_OPTS
10+
# - $FZF_CTRL_R_OPTS
11+
# - $FZF_ALT_C_COMMAND
12+
# - $FZF_ALT_C_OPTS
13+
14+
# Key bindings
15+
# ------------
16+
__fzf_select__() {
17+
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 \
18+
-o -type f -print \
19+
-o -type d -print \
20+
-o -type l -print 2> /dev/null | cut -b3-"}"
21+
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
22+
printf '%q ' "$item"
23+
done
24+
echo
25+
}
26+
27+
if [[ $- =~ i ]]; then
28+
29+
__fzfcmd() {
30+
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
31+
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
32+
}
33+
34+
fzf-file-widget() {
35+
local selected="$(__fzf_select__)"
36+
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
37+
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
38+
}
39+
40+
__fzf_cd__() {
41+
local cmd dir
42+
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
43+
-o -type d -print 2> /dev/null | cut -b3-"}"
44+
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"
45+
}
46+
47+
__fzf_history__() {
48+
local output
49+
output=$(
50+
builtin fc -lnr -2147483648 |
51+
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{$_}++' |
52+
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"
53+
) || return
54+
READLINE_LINE=${output#*$'\t'}
55+
if [ -z "$READLINE_POINT" ]; then
56+
echo "$READLINE_LINE"
57+
else
58+
READLINE_POINT=0x7fffffff
59+
fi
60+
}
161

262
__fzf_sfdx_flags__(){
363
local selected="$1"
@@ -9,7 +69,7 @@ __fzf_sfdx_flags__(){
969
echo "${ret//$'\n'/ --}"
1070
}
1171

12-
__fzf_sfdx__(){
72+
fzf-sfdx(){
1373
local fullcmd="$READLINE_LINE"
1474
local cmd="$(echo $fullcmd | awk '{print $1}')"
1575
local subcmd="$(echo $fullcmd | awk '{print $2}')"
@@ -19,10 +79,12 @@ __fzf_sfdx__(){
1979
local flag="$(__fzf_sfdx_flags__ $subcmd $fullcmd)"
2080
if [[ "$flag" != "" ]]
2181
then
22-
READLINE_LINE="$fullcmd --$flag"
23-
READLINE_POINT=$(( ${#fullcmd} + ${#flag} + 3 ))
82+
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}--$flag${READLINE_LINE:$READLINE_POINT}"
83+
READLINE_POINT=$(( READLINE_POINT + ${#flag} + 3 ))
84+
#READLINE_LINE="$fullcmd --$flag"
85+
#READLINE_POINT=$(( ${#fullcmd} + ${#flag} + 3 ))
2486
fi
25-
elif [[ "$mcd" == "sfdx" || "$cmd" == "" ]]
87+
elif [[ "$cmd" == "sfdx" || "$cmd" == "" ]]
2688
then
2789
local querystr=""
2890
if [[ "$subcmd" != "" ]]; then
@@ -36,18 +98,44 @@ __fzf_sfdx__(){
3698
fi
3799
}
38100

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

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

42-
# CTRL-K - Search through sfdx commands
43-
bind -m vi-command '"\C-e": "\C-z\C-e\C-z"'
44-
bind -m vi-insert '"\C-e": "\C-z\C-e\C-z"'
108+
if (( BASH_VERSINFO[0] < 4 )); then
109+
# CTRL-T - Paste the selected file path into the command line
110+
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"'
111+
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
112+
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
45113

114+
# CTRL-R - Paste the selected command from history into the command line
115+
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
116+
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
117+
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
46118
else
119+
# CTRL-T - Paste the selected file path into the command line
120+
bind -m emacs-standard -x '"\C-f": fzf-file-widget'
121+
bind -m vi-command -x '"\C-f": fzf-file-widget'
122+
bind -m vi-insert -x '"\C-f": fzf-file-widget'
123+
124+
# CTRL-R - Paste the selected command from history into the command line
125+
bind -m emacs-standard -x '"\C-r": __fzf_history__'
126+
bind -m vi-command -x '"\C-r": __fzf_history__'
127+
bind -m vi-insert -x '"\C-r": __fzf_history__'
128+
129+
# CTRL-e - Search for and paste the selected sfdx command onto the command line
130+
bind -m emacs-standard -x '"\C-e": fzf-sfdx'
131+
bind -m vi-command -x '"\C-e": fzf-sfdx'
132+
bind -m vi-insert -x '"\C-e": fzf-sfdx'
133+
134+
fi
47135

48-
# CTRL-K - Search through sfdx commands
49-
bind -m emacs-standard -x '"\C-e": __fzf_sfdx__'
50-
bind -m vi-command -x '"\C-e": __fzf_sfdx__'
51-
bind -m vi-insert -x '"\C-e": __fzf_sfdx__'
136+
# ALT-C - cd into the selected directory
137+
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"'
138+
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
139+
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
52140

53141
fi

fzf-key-bindings.zsh

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# ____ ____
2+
# / __/___ / __/
3+
# / /_/_ / / /_
4+
# / __/ / /_/ __/
5+
# /_/ /___/_/ key-bindings.zsh
6+
#
7+
# - $FZF_TMUX_OPTS
8+
# - $FZF_CTRL_T_COMMAND
9+
# - $FZF_CTRL_T_OPTS
10+
# - $FZF_CTRL_R_OPTS
11+
# - $FZF_ALT_C_COMMAND
12+
# - $FZF_ALT_C_OPTS
13+
14+
# Key bindings
15+
# ------------
16+
17+
# The code at the top and the bottom of this file is the same as in completion.zsh.
18+
# Refer to that file for explanation.
19+
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
20+
__fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})"
21+
else
22+
() {
23+
__fzf_key_bindings_options="setopt"
24+
'local' '__fzf_opt'
25+
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
26+
if [[ -o "$__fzf_opt" ]]; then
27+
__fzf_key_bindings_options+=" -o $__fzf_opt"
28+
else
29+
__fzf_key_bindings_options+=" +o $__fzf_opt"
30+
fi
31+
done
32+
}
33+
fi
34+
35+
'emulate' 'zsh' '-o' 'no_aliases'
36+
37+
{
38+
39+
[[ -o interactive ]] || return 0
40+
41+
# CTRL-T - Paste the selected file path(s) into the command line
42+
__fsel() {
43+
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 \
44+
-o -type f -print \
45+
-o -type d -print \
46+
-o -type l -print 2> /dev/null | cut -b3-"}"
47+
setopt localoptions pipefail no_aliases 2> /dev/null
48+
local item
49+
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
50+
echo -n "${(q)item} "
51+
done
52+
local ret=$?
53+
echo
54+
return $ret
55+
}
56+
57+
__fzfcmd() {
58+
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
59+
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
60+
}
61+
62+
fzf-file-widget() {
63+
LBUFFER="${LBUFFER}$(__fsel)"
64+
local ret=$?
65+
zle reset-prompt
66+
return $ret
67+
}
68+
zle -N fzf-file-widget
69+
bindkey '^T' fzf-file-widget
70+
71+
# ALT-C - cd into the selected directory
72+
fzf-cd-widget() {
73+
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 \
74+
-o -type d -print 2> /dev/null | cut -b3-"}"
75+
setopt localoptions pipefail no_aliases 2> /dev/null
76+
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)"
77+
if [[ -z "$dir" ]]; then
78+
zle redisplay
79+
return 0
80+
fi
81+
zle push-line # Clear buffer. Auto-restored on next prompt.
82+
BUFFER="cd ${(q)dir}"
83+
zle accept-line
84+
local ret=$?
85+
unset dir # ensure this doesn't end up appearing in prompt expansion
86+
zle reset-prompt
87+
return $ret
88+
}
89+
zle -N fzf-cd-widget
90+
bindkey '\ec' fzf-cd-widget
91+
92+
# CTRL-R - Paste the selected command from history into the command line
93+
fzf-history-widget() {
94+
local selected num
95+
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
96+
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
97+
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)) )
98+
local ret=$?
99+
if [ -n "$selected" ]; then
100+
num=$selected[1]
101+
if [ -n "$num" ]; then
102+
zle vi-fetch-history -n $num
103+
fi
104+
fi
105+
zle reset-prompt
106+
return $ret
107+
}
108+
109+
zle -N fzf-history-widget
110+
bindkey '^R' fzf-history-widget
111+
112+
__fzf_sfdx_flags(){
113+
local selected="$1"
114+
local fullcmd=""
115+
for i in "${@:2}"
116+
do fullcmd+=" $i"
117+
done
118+
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'`
119+
echo "${ret//$'\n'/ --}"
120+
}
121+
122+
123+
fzf-sfdx(){
124+
local fullcmd="$LBUFFER"
125+
local cmd="$(echo $fullcmd | awk '{print $1}')"
126+
local subcmd="$(echo $fullcmd | awk '{print $2}')"
127+
local match="$(cat ~/.sfdxcommands.json | jq -r '.[] | select(.id=="'$subcmd'")')"
128+
if [[ "$cmd" = "sfdx" && "$match" != "" ]]
129+
then
130+
local flag="$(__fzf_sfdx_flags $subcmd $fullcmd)"
131+
if [[ "$flag" != "" ]]
132+
then
133+
LBUFFER="${LBUFFER:0:$CURSOR} --$flag${LBUFFER:$CURSOR}"
134+
fi
135+
elif [[ "$cmd" == "sfdx" || "$cmd" == "" ]]
136+
then
137+
local querystr=""
138+
if [[ "$subcmd" != "" ]]; then
139+
querystr="--query $subcmd"
140+
fi
141+
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)"
142+
if [[ "$selected" != "" ]]; then
143+
LBUFFER="sfdx $selected"
144+
fi
145+
fi
146+
local ret=$?
147+
zle reset-prompt
148+
return $ret
149+
}
150+
zle -N fzf-sfdx{,}
151+
bindkey "^e" fzf-sfdx
152+
153+
} always {
154+
eval $__fzf_key_bindings_options
155+
'unset' '__fzf_key_bindings_options'
156+
}

0 commit comments

Comments
 (0)