junegunn / fzf

:cherry_blossom: A command-line fuzzy finder
https://junegunn.github.io/fzf/
MIT License
62.23k stars 2.35k forks source link

FZF_CTRL_R_OPTS to support ctrl-o to execute and prepare next commad #2399

Open ri-aje opened 3 years ago

ri-aje commented 3 years ago

Info

Problem / Steps to reproduce

bash ctrl-o operate-and-get-next stops working once I source source ~/.fzf.bash which is auto generated by fzf and replaces the default ctrl-r by the __fzf_history__ function.

fzf ctrl-r behavior

  1. bash --noprofile --norc
  2. source ~/.fzf.bash
  3. export FZF_CTRL_R_OPTS="--bind=ctrl-o:accept", I don't see an accept-and-execute event, and execute(...) isn't what I want either.
  4. cd ~
  5. ls
  6. press ctrl-r
  7. find cd ~ command
  8. press ctrl-o, notice fzf exits and command line is populated with cd ~, but no execution happens.
  9. press ctrl-o again, notice it executes cd ~, then populates cd ~ again.

for comparison, here is vanilla bash ctrl-r behavior

  1. bash --noprofile --norc
  2. cd ~
  3. ls
  4. press ctrl-r
  5. find cd ~ command
  6. press ctrl-o, notice the history search exits and the command is executed, and ls is auto populated, ready for execution.
  7. press ctrl-o will execute ls and further chain up remaining next commands, eventualy giving a walking through previous sequence effect. this works even when I modify the commands (except the first one found in history which has to be executed as is by ctrl-o).

is there anyway to keep bash original ctrl-o behavior? I do like fzf to search history, but the ctrl-o auto sequencing behavior is very useful, e.g., in edit-compile-run cycles. thanks.

for reference, here is some explanation about bash behavior, I found it on http://web.mit.edu/gnu/doc/html/features_7.html

operate-and-get-next (C-o)
Accept the current line for execution and fetch the next line relative to the current line from the history for editing. Any argument is ignored.
junegunn commented 3 years ago

Thanks for the suggestion. I wasn't aware of operate-and-get-next, hence not implemented here. I like the idea but I can't immediately see how we can correctly implement the behavior.

These two steps are easy:

diff --git a/shell/key-bindings.bash b/shell/key-bindings.bash
index 0cfc423..514d7ff 100644
--- a/shell/key-bindings.bash
+++ b/shell/key-bindings.bash
@@ -45,13 +45,21 @@ __fzf_cd__() {
 }

 __fzf_history__() {
-  local output
+  local output key no cmd
   output=$(
     builtin fc -lnr -2147483648 |
       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{$_}++' |
-      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"
+      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" --expect ctrl-o
   ) || return
-  READLINE_LINE=${output#*$'\t'}
+  key=${output%%$'\n'*}
+  output=${output#*$'\n'}
+  no=${output%$'\t'*}
+  cmd=${output#*$'\t'}
+  if [ "$key" = ctrl-o ]; then
+    cmd=$(history -p "!$(( no + 1 ))" 2> /dev/null)
+    fc -s "$no"
+  fi
+  READLINE_LINE=$cmd
   if [ -z "$READLINE_POINT" ]; then
     echo "$READLINE_LINE"
   else

But after that, we need to programmatically move the current position to the point in the command history so that we can proceed with the subsequent commands.

ri-aje commented 3 years ago

Thanks for the suggestion. I wasn't aware of operate-and-get-next, hence not implemented here. I like the idea but I can't immediately see how we can correctly implement the behavior.

These two steps are easy:

  • Accept the current line for execution
  • and fetch the next line
diff --git a/shell/key-bindings.bash b/shell/key-bindings.bash
index 0cfc423..514d7ff 100644
--- a/shell/key-bindings.bash
+++ b/shell/key-bindings.bash
@@ -45,13 +45,21 @@ __fzf_cd__() {
 }

 __fzf_history__() {
-  local output
+  local output key no cmd
   output=$(
     builtin fc -lnr -2147483648 |
       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{$_}++' |
-      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"
+      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" --expect ctrl-o
   ) || return
-  READLINE_LINE=${output#*$'\t'}
+  key=${output%%$'\n'*}
+  output=${output#*$'\n'}
+  no=${output%$'\t'*}
+  cmd=${output#*$'\t'}
+  if [ "$key" = ctrl-o ]; then
+    cmd=$(history -p "!$(( no + 1 ))" 2> /dev/null)
+    fc -s "$no"
+  fi
+  READLINE_LINE=$cmd
   if [ -z "$READLINE_POINT" ]; then
     echo "$READLINE_LINE"
   else

But after that, we need to programmatically move the current position to the point in the command history so that we can proceed with the subsequent commands.

thanks. as for the second ctrl-o and beyond, I suspect it's some bash internal keeping track of the history index, that is reset on every use of ctrl-r/ctrl-o, for otherwise, bash doesn't have necessary info to be smart enough to differentiate the case where a command is prepared by previous ctrl-o vs. the case when the same command is typed up. without an internal index, bash would also have trouble going back to the correct history point based on a command modified from previous ctrl-o result.

I am guessing this might be the internal state. if you can somehow call rl_operate_and_get_next for the job, maybe you can ride on bash behavior for free? I am not sure if it's doable inside fzf, just some ideas.

poetaman commented 2 years ago

+1 I was looking for this... Yep it will be very helpful, thanks!

poetaman commented 2 years ago

Also, at the very least it should be possible with fzf-tmux while using popup window, as fzf can send keys to the "current pane" to execute the command...

whallify commented 1 year ago

+1 for me as well. Long ago, when I first learned about CTRL+R in general, my life improved vastly. Then, years later, when I learned about CTRL+O's enhancement of CTRL+R, my life again improved vastly. Then a few years ago I learned about fzf and my life improved vastly.

Then when I tried my typical CTRL+O sequences and that fzf didn't support it, I was saddened. I'm hopeful it can be added back!