akinomyoga / ble.sh

Bash Line Editor―a line editor written in pure Bash with syntax highlighting, auto suggestions, vim modes, etc. for Bash interactive sessions.
BSD 3-Clause "New" or "Revised" License
2.7k stars 85 forks source link

[fzf] Problems with fzf and ble.sh #41

Closed dylankb closed 4 years ago

dylankb commented 4 years ago

ble version:

0.4.0-devel2+3031007

Bash version:

5.0.11(1)-release

I've noticed two issues if I have both fzf and bleh.sh sourced. I.e [ -f ~/.fzf.bash ] && source ~/.fzf.bash is run manually or in your bashrc.

1) fzf history search does not work well.

2) The shortcut for fuzzy tab completion does not work as expected.

cd ** returns

-bash: cd: too many arguments
[ble: exit 1]

Do you think it's possible to resolve these types of issues? Thanks in advance!

akinomyoga commented 4 years ago

Thank you very much for the report! 3031007 is a version 22 days ago. Could you try the latest version (by ble-update) to see if it works? Actually this was reported by another user in #38. After that issue, I have changed the detailed behavior of ble.sh so that fzf works.

akinomyoga commented 4 years ago

fzf completer with **

The shortcut for fuzzy tab completion does not work as expected.

For the command cd, actually ble.sh uses its own completer. You can turn off this ble.sh completer by the following setting so that the user setting (i.e., fzf completer) is used:

blehook/eval-after-load complete 'unset -f ble/cmdinfo/complete:cd'

For the other commands, ble.sh uses the user-provided settings so fzf should be effective. However, fzf completer does not work as expected. There are several design problems.

1. ble.sh translates ** to the first filename before fzf is called

ble.sh implements its own completion system that enhances the original completion mechanism of Bash, i.e. it modifies the behavior of completion. Before calling a user-provided completer, ble.sh tries to translate the content of the command line to a plainer form so that a naive user-provided completer can work for complicated cases. For the simplest example, when you have a command line like echo A; echo B "${C%/}/★ where ★ represents the cursor position and the value of the variable C is X, ble.sh constructs a virtual command line echo B X/ and passes it to the user-provided completer. In this way, even if the user-provided completer does not recognize incomplete quoting and the parameter expansions, it can generate appropriate completions.

ble.sh also tries to expand pathname patterns containing *, ?, etc. before passing it to the user-provided completer. Specifically it replaces pathname patterns with the first filepath generated by the pathname expansions. If you have ** in your command line, the command line that fzf receives is something like cd somefile where somefile is the first file in the current directory. As a result, fzf will never see the trigger string **, and therefore fzf selection will never be triggered.

2. auto-complete also triggers fzf

Another problem is that ble.sh uses the same completion setting for auto-complete. If you setup up fzf for completion, it will be used by auto-complete as well as by TAB completions. The result is that when you are typing a command line like cd some**, fzf will pop up even without pressing TAB. But maybe this is not a problem if you want to invoke fzf instantly after typing ** (without TAB).

Also there are other design problems, but they are easy to work around.


I need to consider how to design the combination of these two different enhancement by ble.sh and by fzf. Of course I can still write a code to detect fzf and behaves differently when fzf is used, but I do not want to adopt such an ad hoc way.

akinomyoga commented 4 years ago

fzf completer with ** (2)

There were too many mismatches between fzf and ble.sh, so it is hard to combine them by modifying only ble.sh. The behavior of fzf functions should also be modified. I finally decided to dynamically patch fzf functions. I created a couple of utilities for dynamical patching of shell functions fbe531a, and also added a few options to the ble.sh completion system 58e1be4.

Could you please (1) update ble.sh by ble-update and then (2) write the following settings instead of "[ -f ~/.fzf.bash ] && source ~/.fzf.bash"?

# bashrc
if [ -f ~/.fzf.bash ]; then
  source ~/.fzf.bash

  # clear blesh completer for cd
  blehook/eval-after-load complete 'unset -f ble/cmdinfo/complete:cd'

  # patch fzf functions
  ble/function#advice around __fzf_generic_path_completion _fzf_complete.advice
  ble/function#advice around _fzf_complete                 _fzf_complete.advice
  ble/function#advice around _fzf_complete_kill            _fzf_complete.advice
  function _fzf_complete.advice {
    [[ :$comp_type: == *:auto:* ]] && { compopt -o default; return; }
    compopt -o noquote
    COMP_WORDS=("${comp_words[@]}") COMP_CWORD=$comp_cword
    COMP_LINE=$comp_line COMP_POINT=$comp_point
    ble/function#push printf '[[ $1 == "\e[5n" ]] || builtin printf "$@"'
    ble/function#advice/do < /dev/tty &> /dev/tty
    ble/function#pop printf
    ble/textarea#invalidate
  }
fi
dylankb commented 4 years ago

Oh, great! This seems to work very well, even the fzf completer ** for cd works once the additional bashrc settings are added.

ble.sh also tries to expand pathname patterns containing *, ?, etc. before passing it to the user-provided completer. Specifically it replaces pathname patterns with the first filepath generated by the pathname expansions.

That's neat - I didn't realize that about ble.sh before. If ble.sh was able to provide the possible expand pathname patterns containing * to the autcomplete menu it would probably reduce a fair amount of what I use fzf for in terms of file navigation.

Anyways, thanks for the very quick response!

dylankb commented 4 years ago

One thing I did notice when I ran ble-upgrade was I got this message.

sed: RE error: illegal byte sequence.

It seems I get this message now anytime I try to use the ble-bind function. For example, just copying this example from the readme into ~/.blerc causes this error to appear.

ble-bind -c 'M-c' 'my-command'

I have no existing ble-bind functions in my .blerc file, but I was considering adding some fzf key bindings for easier git navigation.

https://junegunn.kr/2016/07/fzf-git/
https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236

akinomyoga commented 4 years ago

sed: RE error: illegal byte sequence.

Thank you. I hope this commit 0cc9160 fixes the problem.

dylankb commented 4 years ago

Great, that did solve the issue.

Final question. I have a bash version of a key binding that I want to convert to ble-bind but I'm having a bit of trouble. The bash version is bind '"\C-g\C-f": "$(gf)\e\C-e\er" which allows for appending a branch selection to the current line (equivalent of git checkout $(gf) without having to write the $(gf) bit - see Gist above). My ble-bind version is below. I referenced this line to check for the ble version of shell-expand-line

ble-bind -c 'C-g C-b' '$(gb) M-C-e'

If I run gb in the terminal I can succesfully print the branch selection to standard output. However if I try and use the ble-bind as above I get:

-bash: $BRANCH No such file or directory

It's as if there was a return entered based on what was in standard out. Is this something I'm doing wrong?

akinomyoga commented 4 years ago

Great, that did solve the issue.

OK! Thank you for checking!


Final question.

The ble.sh native way is to define a widget and register it through ble-bind -f:

function ble/widget/fzf-git {
  ble/widget/insert-string "$($1)"
  ble/textarea#invalidate
}
ble-bind -f 'C-g C-f' 'fzf-git gf'
ble-bind -f 'C-g C-b' 'fzf-git gb'
ble-bind -f 'C-g C-t' 'fzf-git gt'
ble-bind -f 'C-g C-h' 'fzf-git gh'
ble-bind -f 'C-g C-r' 'fzf-git gr'

Here ble/widget/insert-string "$($1)" inserts the result of the command substitution "$($1)" where $1 contains an argument specified in ble-bind such as gf, gb, etc. The line ble/textarea#invalidate notifies ble.sh that the textarea contents has been lost (overwritten by fzf) to induce the later redraw.

But if you don't know how to define it, actually you can still use the readline style settings (though it is not as flexible as the native style settings). ble.sh internally tries to translate the readline style settings for ble-bind:

# Actually you could just copy and paste
# (Note: \eX is equilvalent to \M-X)
bind '"\er": redraw-current-line'
bind '"\C-g\C-f": "$(gf)\e\C-e\er"'
bind '"\C-g\C-b": "$(gb)\e\C-e\er"'
bind '"\C-g\C-t": "$(gt)\e\C-e\er"'
bind '"\C-g\C-h": "$(gh)\e\C-e\er"'
bind '"\C-g\C-r": "$(gr)\e\C-e\er"'

# Or you can use existing binding C-M-l for redraw-line:
bind '"\C-g\C-f": "$(gf)\M-\C-e\M-\C-l"'
bind '"\C-g\C-b": "$(gb)\M-\C-e\M-\C-l"'
bind '"\C-g\C-t": "$(gt)\M-\C-e\M-\C-l"'
bind '"\C-g\C-h": "$(gh)\M-\C-e\M-\C-l"'
bind '"\C-g\C-r": "$(gr)\M-\C-e\M-\C-l"'

ble-bind -c 'C-g C-b' '$(gb) M-C-e'

A shell command can be specified for the option for -c; i.e., $(gb) M-C-e above will be treated as a single command. That is the reason it doesn't work. Instead you can use the option -s to specify a (readline-style) keyseq. The working version of the above setting is as follows:

ble-bind -s 'C-g C-b' '$(gb)\M-\C-e'
dylankb commented 4 years ago

Nice, a really fascinating explanation. The only thing I did differently was include the \C-M-l portion to redraw the line like you mentioned: ble-bind -s 'C-g C-b' '$(gb)\M-\C-e\M-\C-l'

The only problem with the above is that C-g looks like it's bound toauto_complete/cancel and C-b to @nomarked backward-char. The result is cancelled auto-suggestions and white space removals before the cursor must happen before function can trigger, but I can probably work around that by thinking of a different key mappings to use.

One side note is that ble-bind -L returns an error for me.

$ ble-bind -L
/usr/bin/sed: illegal option -- r
usage: sed script [-Ealn] [-i extension] [file ...]
       sed [-Ealn] [-i extension] [-e script] ... [-f script_file] ... [file ...]
akinomyoga commented 4 years ago

but I can probably work around that by thinking of a different key mappings to use.

Maybe you can also think about using sabbrev. The sabbrev expansions can be invoked by hitting SP just after the registered words. For example, with the following settings, SP after echo gb expands gb to the word selected in fzf.

function fzf-git.sabbrev {
  COMPREPLY=$($1)
  ble/textarea#invalidate
}
ble-sabbrev -m gf='fzf-git.sabbrev gf'
ble-sabbrev -m gb='fzf-git.sabbrev gb'
ble-sabbrev -m gt='fzf-git.sabbrev gt'
ble-sabbrev -m gh='fzf-git.sabbrev gh'
ble-sabbrev -m gr='fzf-git.sabbrev gr'

Another possibility would be to use lib/vim-arpeggio.sh (it is named vim-* but actually can be used in Emacs editing mode as well). You can bind simultaneous pressing of two letters. (Be careful that if you bind two letters to a keyboard macro that contains that two letters using the option -s, it will cause infinite loop.)

function ble/widget/fzf-git {
  ble/widget/insert-string "$($1)"
  ble/textarea#invalidate
}
ble-import 'lib/vim-arpeggio.sh'
ble/lib/vim-arpeggio.sh/bind -f 'gf' 'fzf-git gf'
ble/lib/vim-arpeggio.sh/bind -f 'gb' 'fzf-git gb'
ble/lib/vim-arpeggio.sh/bind -f 'gt' 'fzf-git gt'
ble/lib/vim-arpeggio.sh/bind -f 'gh' 'fzf-git gh'
ble/lib/vim-arpeggio.sh/bind -f 'gr' 'fzf-git gr'

One side note is that ble-bind -L returns an error for me.

Thank you! This is another bug related to sed compatibility in different systems. Fixed 2184739

dylankb commented 4 years ago

Cool, looks like that fixed the issue.

Those were interesting options I didn't know about - thanks!