[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
re-evaluating prompt strings from a bind -x function
From: |
Christoph Anton Mitterer |
Subject: |
re-evaluating prompt strings from a bind -x function |
Date: |
Mon, 02 Oct 2023 02:54:20 +0200 |
User-agent: |
Evolution 3.50.0-1 |
Hey.
Recently (well, better late than never), I've stumbled over the awesome
fzf[0] and in specific it's various shell/completion integrations, for
which I tried to get some (hopefully) improvements merged.
One of that was my commit:
https://github.com/junegunn/fzf/pull/3448/commits/3c005cd9473ed553ab3179a002729e566f3bcd95
whose idea was to use bind -x instead of a sequence of a readline
command like:
bind -m emacs-standard '"\ec": " \C-b\C-k
\C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
The main motivation for this was, that a user might have already
assigned any of those keyseqs to some custom functionality, in which
case the thing would of course fail.
My commit, which naively used bind -x on __fzf_cd__, failed miserably
(and I didn't even notice ^^).
Now AFAIU, the reason why just:
bind -m emacs-standard -x '"\ec": __fzf_cd__'
doesn't work properly is, that, while it executes the function (which
lets then select a dir via fzf, and could be easily made to also cd
into that dir[1])... it doesn't print a new prompt (which often
contains the CWD).
Even if one somehow calls readline's redraw-current-line, it's still
the already evaluated PS1 with the old CWD.
There are numerous stackoverflow/etc. questions[2][3] dealing with that
issue, and none of them seems to provide a proper/clean solution.
Especially:
- They, too, need certain bindings to be set up, which possibly
override what a user has already assigned.
- The need to define further (non-local) variables, which again may be
already in use by something else.
Perhaps less of a problem, if one uses a name that's unlikely to
collide, though ideally I'd prefer to simply rule that out.
1) Is there a proper way to have the prompt re-evaluated and re-drawn
as well as the current readline buffer, in order to simple be able to
use bind -x with a __fzf_cd__ (that executes and not just print the cd
command)?
Proper in the sense, that it works with unicode, doesn't require
terminal hacks, etc. pp.
2) I'm a bit torn between three "modes" how something like fzf’s Alt-C
functionality should in the end look like.
If I have e.g.:
calestyo@heisenberg:/$ someCurrentReadlineBuffer
and press Alt-C and select a dir, I think either of the following could
happen:
a) The same line becomes:
calestyo@heisenberg:/new dir$ someCurrentReadlineBuffer
b) a new line is added (but no history entry made):
calestyo@heisenberg:/$ cd a\ b
calestyo@heisenberg:/new dir$ someCurrentReadlineBuffer
just in order to have the change more visibly in the terminal
transcript
c) the same as (b), but with `cd a\ b` actually being executed as
interactive command, and thus going into the history.
Ideally, an implementation of fzf's Alt-C would allow the user to
choose between any of (a), (b) and (c) according to personal taste.
3) I, so far, was only able to get the following:
If I have e.g.:
calestyo@heisenberg:/$ someCurrentReadlineBuffer
it becomes:
calestyo@heisenberg:/$
calestyo@heisenberg:/new dir$ someCurrentReadlineBuffer
which is a bit ugly.
For that I do:
bind -m emacs-standard -x '"\ec": _mycdwidget'
=> again, one keyseq needs to be used of course.
_mycdwidget()
{
BINDTMP="$(save_binds)"
bind -x '"\C-f":"_save; __fzf_cd__"'
bind -x '"\C-r":"_restore"'
bind '"\e[0n": "\C-f\n\C-r"'
printf '\e[5n'
}
_save() {
READLINE_LINE_OLD="$READLINE_LINE"
READLINE_POINT_OLD="$READLINE_POINT"
READLINE_LINE=
READLINE_POINT=0
}
_restore() {
READLINE_LINE="$READLINE_LINE_OLD"
READLINE_POINT="$READLINE_POINT_OLD"
eval "$BINDTMP"
}
It also uses the save_binds from [4] and a modified __fzf_cd___
__fzf_cd__() {
local cmd opts dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path
'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype
'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse
--scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd))
&& cd "$dir"
}
which actually executes the `cd "$dir"` rather than only printing it.
What also seems to work is:
_mycdwidget()
{
BINDTMP="$(save_binds)"
bind -x '"\C-f":"_save; __fzf_cd__"'
bind '"\e[0n": "\C-f\n"'
printf '\e[5n'
}
__fzf_cd__() {
local cmd opts dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path
'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype
'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse
--scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd))
&& cd "$dir"
bind -x '"\e[0n":"_restore"'
printf '\e[5n'
}
Not sure if that's any better or worse than the above.
The idea is, that effectively only "\ec" is overwritten, without using
any other keyseqs for executing "\ec" itself.
And all other needed keyseqs and their bindings are only overwritten
while doing the magic and restored afterwards.
What I don't like about that solution:
I) I'm not even sure whether it works under all circumstances.
Especially, is it save, that once \ec is pressed, mycdwidget() is
executed, right after that *with nothing possibly coming in between*
printing the \e[5n and executing the binding for \e[0n, and the
bindings for the keyseqs of that.
Or is there some generally better approach?
II) It doesn't really give me exactly any of the "modes" (2a), (2b) or
(2c) from above.
III) I need to place three additional functions in the users
environment:
_mycdwidget(), _save() and _restore()
as well as the variable
BINDTMP
Of course I can choose better names, less likely to collide, but
still.
So is there any good way to store/restore the previous state of
functions and variables, including whether set or unset, any
attributes (like readonly or local). Also if the variable is
an array?
_mycdwidget() could save the previous state of BINDTMP, _save()
and _restore() inside BINDTMP itself (being made an array).
Something like:
BINDTMP=("$(declare -p BINDTMP 2>/dev/null)" "$(declare -f _save
2>/dev/null)" "$(declare -f _restore 2>/dev/null)" '')
with the 4th element being used as the actual store for the old
bindings and:
"my" BINDTMP, _save() and _restore() unset respectively restored
at the end of _restore()?
Or is there any pitfall with that, which I don't see?
Of course if _save() and _restore() were also restored,
_mycdwidget() would always need to define them again.
Not sure how expensive that is in terms of performance and whether
it's worth the effort.
Oh and of course, fzf might then need two versions, one for pre v4 bash
that prints the cd command and one for newer versions, that executes
it.
Last but not least:
Would the save/restore of bindings work with older bash versions, when
bind -x is (allegedly) not yet available?
I'd rather guess not, as I think I'd already need to use keyseqs (which
may have already non-standard bindings) in order to get the above idea
working, when I cannot simply execute a function like with bind -x.
Thanks in advance,
Chris.
[0] https://github.com/junegunn/fzf/
[1] The current implementation of the function merely prints some cd
<dir> command, which is later executed.
[2]
https://stackoverflow.com/questions/40417695/refreshing-bash-prompt-after-invocation-of-a-readline-bound-command/40426702#40426702
[3]
https://superuser.com/questions/1662055/how-to-bind-x-keyboard-shortcut-and-refresh-prompt/1662149#1662149
[4] https://lists.gnu.org/archive/html/help-bash/2023-10/msg00000.html
- re-evaluating prompt strings from a bind -x function,
Christoph Anton Mitterer <=