Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Allow conditional routing for custom fuzzy completion postprocessing functions #3367

Open
5 of 10 tasks
cohml opened this issue Jul 12, 2023 · 7 comments
Open
5 of 10 tasks

Comments

@cohml
Copy link

cohml commented Jul 12, 2023

  • I have read through the manual page (man fzf)
  • I have the latest version of fzf
  • I have searched through the existing issues

Info

  • OS
    • Linux
    • Mac OS X
    • Windows
    • Etc.
  • Shell
    • bash
    • 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.

@junegunn
Copy link
Owner

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.

@junegunn
Copy link
Owner

junegunn commented Jul 12, 2023

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}'
}

@cohml
Copy link
Author

cohml commented Jul 12, 2023

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?

@cohml
Copy link
Author

cohml commented Jul 12, 2023

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 run git branch --all and pass the output into a pipe that converts stuff like this

    some-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 run git 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?

@cohml
Copy link
Author

cohml commented Jul 12, 2023

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?

@cohml
Copy link
Author

cohml commented Jul 12, 2023

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!

@junegunn
Copy link
Owner

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants