fzf
fzf copied to clipboard
Feature request: Allow conditional routing for custom fuzzy completion postprocessing functions
- [X] I have read through the manual page (
man fzf
) - [X] I have the latest version of fzf
- [X] I have searched through the existing issues
Info
- OS
- [ ] Linux
- [X] Mac OS X
- [ ] Windows
- [ ] Etc.
- Shell
- [ ] bash
- [X] zsh
- [ ] fish
Maybe this is already possible to do, but the current documentation is insufficient for understanding how to implement it.
The wiki shows an example for how to implement a custom fuzzy completion function with some conditional routing (_fzf_complete_git
). This is useful because it shows how you could, in theory, write a single function that provides different completions for many different subcommands. For example, git checkout
versus git cherry-pick
, the former presenting git branch
output and the latter presenting git cherry-pick
output from which to select. The example in the wiki already gives a good idea for how to implement this using if
/elif
string matching.
However, the postprocessing function that goes with that example (_fzf_complete_git_post
) does not illustrate how to branch the postprocessing in the same way. For example, say I want the git cherry-pick
to cut
out just the commit sha column, whereas git checkout
should just pass branch names through unchanged. I attempted to implement this myself, on the assumption that the input to the postprocessing function is the line selected through fuzzy completion, but I was completely guessing, and ultimately didn't succeed.
My point is that either the kind of conditional routing (to handle multiple commands) that is already possible in the "main" completion function should be made possible in the "post" function too, or else if it is already possible, the documentation should be updated to show how to implement it.
Good point, it's something I haven't really thought through.
In bash, you can access the original command via $COMP_WORDS
variable.
_fzf_complete_foo() {
_fzf_complete --multi --reverse --header-lines=3 -- "$@" < <(
ls -al
)
}
_fzf_complete_foo_post() {
echo -n "[ ${COMP_WORDS[@]} ]"
awk '{print $NF}'
}
[ -n "$BASH" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo
But I guess that's not possible in zsh (not a zsh user myself). If there is no such way to readily do it in zsh, we can consider passing the completion arguments to the post function.
Ah, looks like $LBUFFER
is available in post function.
_fzf_complete_foo() {
_fzf_complete --multi --reverse --header-lines=3 -- "$@" < <(
ls -al
)
}
_fzf_complete_foo_post() {
echo -n "[ $LBUFFER ]"
awk '{print $NF}'
}
Thanks for the reply. I've tried to implement what you showed, but I think it indicates a miscommunication.
I assumed that what gets passed to the postprocessing function is the verbatim string that the user selects in fzf. So like, in the first example from the README's section on custom fuzzy completion, if the user selects e.g., "such"
, I thought "such"
would be passed through for postprocessing. But for zsh, $LBUFFER
seems to refer to the original string that preceded the **
, having nothing to do with the fzf selection.
I confess I don't really see how that is useful, since that command string is already present in the terminal when fzf pastes the selected output. So returning $LBUFFER
via the postprocessing function just results in the command string being duplicated.
By contrast, passing through the actual selected text seems like it would be vastly more useful. Is that somehow possible?
Here's what I personally am trying to do, as an example to make it concrete:
_fzf_complete_git() {
ARGS="$@"
if [[ "${ARGS}" == "git checkout"* ]]; then
_fzf_complete --prompt="All branches> " -- "${ARGS}" < <(
git branch --no-color --all --format="%(refname:short)" \
| grep -vE ^origin$ \
| sed -E 's/^./LOCAL\t&/' \
| sed 's/LOCAL\torigin\//REMOTE\t/'
)
elif [[ "${ARGS}" == "git rebase -i"* ]]; then
_fzf_complete --prompt="Rebase onto commit> " -- "${ARGS}" < <(
git log --oneline
)
else
eval "zle ${fzf_default_completion:-expand-or-complete}"
fi
}
_fzf_complete_git_post() {
if [[ "${LBUFFER}" == "LOCAL"* || "${LBUFFER}" == "REMOTE"* ]]; then
# git checkout
awk -F "\t" '{print $2}'
else
# git rebase
awk '{print $1}' | sed -E "s/.$/&~/"
fi
}
In English: I want one function which works with git checkout
and git rebase
. What happens upon <TAB>
should depend on which of those two commands is detected.
-
If I do
git checkout **<TAB>
, I want the "main" function to rungit branch --all
and pass the output into a pipe that converts stuff like thissome-local-branch remote/origin/some-remote-branch
to this
LOCAL some-local-branch REMOTE some-remote-branch
in the fzf window. Then once I make a selection, I want the postprocessing function to take the selected line and return only the branch name component (column 2).
-
If I do
git rebase -i **<TAB>
, I want the "main" function to rungit log --oneline
and show the output in fzf. Then, once I make a selection in fzf, I want the postprocessing function to take that selected line, discard all but the commit sha (column 1), and append"~"
to the end.
The "main" function shown above does everything I want it to, in that it correctly distinguishes git checkout
from git rebase
and passes the correct stuff into fzf. But after I make the selection, the postprocessing function doesn't work. Instead, it seems as if everything is just routed to the else
clause.
Can you advise on what changes I'd need to make to bring about the desired postprocessing behavior?
Actually, after thinking on it I now realize why having access to $LBUFFER
in postprocessing is useful: You can use it to apply the same conditional branching as was done in the "main" function. So that makes total sense now.
But then how can I also get access to my fzf selection in postprocessing?
Aaaaaaand I just figured it out haha. My functions are now working as intended, including postprocessing, thanks to the magic of $LBUFFER
. Thanks for telling me about that.
At the very minimum, the example in the wiki should be updated to include that variable so that people can tinker with it and figure it out themselves.
Better though would be to actually describe what that variable is and how it should be used in postprocessing.
Best would be update the API to be more inherently transparent, then update the main README (not just the wiki) to reflect this cool capability. I would also prefer to see a short description of how information is passed between these functions, since it's a little opaque at present, and understanding that would open the door to many more cool and creative uses I'm sure.
But anyway, I believe my own immediate needs have now been met. I'll stop blowing up this thread now :) Cheers!
At the very minimum, the example in the wiki should be updated to include that variable so that people can tinker with it and figure it out themselves.
Yeah, the wiki needs an overhaul. The pages are written by the community, and sadly, there has been no quality control at all. I don't know which ones are working and which ones are not. But I just don't have time to go through the code.
Best would be update the API to be more inherently transparent, then update the main README (not just the wiki) to reflect this cool capability.
Agreed. It would be even better if the API is consistent across bash and zsh (no COMP_WORDS vs LBUFFER).