I wrote an fzf extension that completes flags for the current command from
your history. This makes it easy to build invocations with lots of flags, or to
remember the spelling of a rarely used flag.
You can find the code here .
This post assumes you already know about fzf and have it installed. If you don’t, a lot of this might be confusing. It is incredibly useful, and is as valuable to my workflows as
vimandtmux.

In the default
fzf
configuration, <c-r>
searches your history. It is a huge improvement over the default reverse history
search.
Note that in this post
<c-r>means hitting<ctrl>andrat the same time.
At work we have a script that has dozens of flags. I’ve used probably 20-30 of them at different times. On a single invocation I’ll need to pick and choose a subset of those flags. Maybe building up something like this:
$ cmd-with-logs-of-flags \
--use_magic \
--foo \
--bar \
--num_servers=100
Because it’s a custom command, I can’t benefit from the zsh tab completion.
And because I haven’t yet memorized the flags, every time I wanted to run it I
had to open up a new tmux pane and less the cmd-with-logs-of-flags source
file to see what was a valid flag. Gross.
At first I suffered along using <c-x> <c-e>. That will open your editor with
your current command. You can edit to your heart’s content. Save, quit, and the
command will be updated. Slightly less gross, as now you don’t have to retype
the whole command if you mess up the shell syntax.
To make things even easier, I wrote a zsh plugin that uses fzf to complete
previously used flags from your history. As you can see in the gif above, now
all I have to do is type cmd-with-logs-of-flags, hit <c-q>, then I can
complete flags to my heart’s content.
This has been a huge time saver. Not only is cmd-with-logs-of-flags less
annoying to use, but it’s also been useful for flags that I tend for forget.
When writing this article I was using hugo server. I used <c-r> to get me
started, but then I needed the flag to build draft posts, not just published
posts. I remembered it was something like --build-drafts. <c-q> showed me
that in fact it was --buildDrafts.
The general idea is:
Look at the command you’ve already entered. Save the first token, which is
the command, as match_prefix.
local match_prefix=$1
Look at your history:
fc -rl 1
Use sed to clean up the history lines:
1234* ls -al. You can use
fc -rl 1 | less to see what these commands look like._1234*_ part (note I replaced
spaces with underscores to make it easier to see what it’s doing).\ or \_. This only becomes a problem where you’ve typed multiline
commands, where \ is the line continuation character. This is needed for
a later step in the process, where the slashes confuse a while in the
shell. (Or at least I think that’s what is happening. Without stripping
those slashes, the flag doesn’t make it back to the prompt.)sed -E -e 's/^[[:space:]]*[0-9]*\*?[[:space:]]*//' -e 's/\\+[[:space:]]*$//' | \
Pass the commands to ripgrep .
match_prefix.awk, you could no doubt skip this step and just use
awk to do the matching. Keeping it as-is makes it easier for me to
reason about.rg "^${match_prefix}" --color=never --no-line-number
Finally, pass the commands to gawk.
gawk instead of normal
awk.--foo or -x.--num_servers=100 to suggest both --num_servers and
--num_servers=100.-x foo. We want that to produce both -x and -x foo.-x foo is a flag (-x)
and an argument (foo). With -v --num_servers=100, we don’t want to
think to think that --num_servers=100 is a value for the -v flag.The rest of the file is integrating with fzf. For the most part here I just
copied the setup code from other fzf commands. There are a couple things worth
nothing, though.
First, <c-q> normally already does something in the shell. It is the
counterpart to the <c-s> command. If you don’t already know what that does,
odds are that at some point you’ve accidentally hit <c-s>, then wondered why
your terminal froze. Normally you use <c-q> to unfreeze your prompt. This is a
useless feature in modern shells, as far as I can tell, so I’ve disabled <c-s>
by putting this in my zshrc:
stty -ixon
That frees up <c-q> so that, as far as I know, it’s not bound to anything.
Second, we need to operate on the text that has been entered in the command. If
I type hugo serve <c-q>, we need to be able to see that hugo is the first
thing we typed.
In zsh functions, that is normally saved in the BUFFER variable. I took
this approach at first, but it broke for multiline commands. In this scenario,
for example, ${BUFFER} wouldn’t be populated correctly:
$ hugo \
server <c-q>
On multiline commands, zsh
puts
the
first part of the command in PREBUFFER, and it sets CONTEXT=cont to let us
know that the part we care about will actually be in the ${PREBUFFER}
variable. We use that to know where to look:
local buffer_with_start_of_cmd=${BUFFER}
if [[ $CONTEXT == "cont" ]]; then
buffer_with_start_of_cmd=${PREBUFFER}
fi
You’ll need zsh. Make sure gawk and rg are on your path.
Clone the
repo
and source it. I
have this in my zshrc:
# Make sure you clone this first on new installations:
# git clone https://github.com/srsudar/fzf-complete-flags ~/.zsh/fzf-complete-flags
if [ ! -f ~/.zsh/fzf-complete-flags/fzf-complete-flags.zsh ]; then
echo "fzf-complete-flags.zsh not found--did you clone the repo?" >&2
else
source ~/.zsh/fzf-complete-flags/fzf-complete-flags.zsh
fi