fzf icon indicating copy to clipboard operation
fzf copied to clipboard

CTRL-R: How to toggle between global and per-directory history within fzf?

Open cohml opened this issue 2 years ago • 2 comments

  • [x] I have read through the manual page (man fzf)
  • [x] I have searched through the existing issues

Info

  • OS
    • [x] Linux
    • [x] Mac OS X
  • Shell
    • [x] zsh

Sorry to blow up your issues lately. I love how deep you can take things with fzf if suitably motivated!

Inquiry

I use the wonderful per-directory-history plugin in Zsh. Using it, ^G will toggle between history files, either global or PWD-specific.

Without any modification of fzf, the CTRL-R output shows the correct history depending on the mode (i.e., global vs. PWD-specific). However, if I want to switch modes, I have to exit fzf, toggle the mode with ^G, then re-enter fzf. I'm wondering if it's possible to do this all within fzf.

I attempted to achieve this with various flavors of --bind='ctrl-g:execute(per-directory-history-toggle-history)' (NB: that function comes from the plugin [source]), but I get an error that per-directory-history-toggle-history cannot be found. Am I on the right track?

Followup inquiry

Lastly, to add one more layer of complexity that's specific to me, I wrap fzf-history-widget with my own simple logic to add a little indicator of the current history mode to the fzf prompt. I then bind this function to ^R, overwriting fzf's binding. The code shown below displays the correct indicator as intended:

fzf-history-widget-with-dynamic-history-mode-indicator() {
  source "file/that/sets/fzf/environment/variables/like/FZF_CTRL_R_OPTS.zsh"
  [[ $_per_directory_history_is_global = true ]] && HISTORY_MODE="G" || HISTORY_MODE="L"
  FZF_CTRL_R_OPTS+=" --prompt=${HISTORY_MODE}\  --color='prompt:#e57374'"
  fzf-history-widget
}

This is where I tried to add the --bind bit, in hopes that ^G within fzf would not only toggle the mode and refresh the results, but also update the indicator:

fzf-history-widget-with-dynamic-history-mode-indicator() {
  source "${DOTFILES}/.variables/.variables.fzf"
  [[ $_per_directory_history_is_global = true ]] && HISTORY_MODE="G" || HISTORY_MODE="L"
  FZF_CTRL_R_OPTS+=" --prompt=${HISTORY_MODE}\  \
                     --color='prompt:#e57374' \
                     --bind='ctrl-g:execute(per-directory-history-toggle-history)+become(fzf-history-widget-with-dynamic-history-mode-indicator)'" # <-- this results in errors
  fzf-history-widget
}

But when I add in --bind='...', open fzf with ^R, and hit ^G, it fails:

zsh:1: command not found: per-directory-history-toggle-history

I'm quite confident that this should be possible, but I can't quite figure it out. Any assistance @junegunn?

cohml avatar Dec 11 '23 21:12 cohml

fzf runs an external command with a new child shell process ($SHELL -c YOUR_COMMAND), and it looks like the zsh function is not available in the subshell. Perhaps the function is only defined in interactive sessions. Does $SHELL -ci per-directory-history-toggle-history work? If so, you can start trying something like become($SHELL -ci \"per-directory-history-toggle-history; fzf-history-widget-with-dynamic-history-mode-indicator\").

junegunn avatar Dec 15 '23 04:12 junegunn

Hot dog, your change seems to resolve the error! But now I get a different one:

per-directory-history-toggle-history:zle:11: can only be called from widget function

My zsh-fu isn't good enough to debug this, it seems. I tried to invoke zle inside my fzf keybinding, but no success. Then instead of this

function per-directory-history-toggle-history() { ... }

autoload per-directory-history-toggle-history
zle -N per-directory-history-toggle-history
bindkey -M vicmd "^G" per-directory-history-toggle-history

FZF_CTRL_R_OPTS="--bind='ctrl-g:become($SHELL -ci \"per-directory-history-toggle-history\")'"

I did this

function per-directory-history-toggle-history() { ... }

function per-directory-history-toggle-history-widget() {  # wrapper function
  per-directory-history-toggle-history
}

autoload per-directory-history-toggle-history-widget
zle -N per-directory-history-toggle-history-widget
bindkey -M vicmd "^G" per-directory-history-toggle-history-widget

FZF_CTRL_R_OPTS="--bind='ctrl-g:become($SHELL -ci \"per-directory-history-toggle-history\")'"

The hope was that making making the wrapper function the widget and keeping the original function not a widget would allow fzf to call it directly. But I still got exactly the same error!

per-directory-history-toggle-history:zle:11: can only be called from widget function

Any thoughts or advice?

cohml avatar Dec 18 '23 14:12 cohml