Fzf: Bash: Multiple line PS1 prompt doubles up when redrawing

Created on 12 Feb 2016  路  13Comments  路  Source: junegunn/fzf

This one may be a bug with readline/bash itself, but I noticed after using fzf keybindings, which make use of redraw-current-line, that my prompt doubled up a lot.

You can reproduce this without fzf, with just bash by doing this:

bind '"\er": redraw-current-line'
PS1="first\nsecond"

Your prompt should now show

second

After pushing ESC-r a few times (or meta-r), then it shows

first
first
second

This is showing that the first line is overlapping with the last line. I say last, as you could set it to PS1="first\nsecond\nthird" and you get

second
first
second
first
second
third

Let me know if I should just file this with bash/readline directly, or whether you want it here to find a way around it?

Cheers,

Hugh

Most helpful comment

Same (?) issue on Zsh. I have a $PS1 with a line break in it. If I do CtrlR to search history with fzf, and then select a result (or just hit Esc to exit), the last line before my prompt is deleted. Demo: https://asciinema.org/a/uzF7EeuA2FLSd5UdVeIs8p5eD

All 13 comments

Interesting. Thanks for the heads up. Do you think we can find a workaround?

Haha, I'm googling this as we speak. I don't really want to go back to a single line prompt after getting used to my multi line one for so long... Though the extra new lines (my first line of my PS1 is just a blank) do take up a lot of vertical space.

Will let you know if I find a workaround, otherwise probably need to send this to bash/readline.

Not sure if similar behaviour happens on the other shells.

Cheers,

Hugh

Sent this to bash:

From: Hugh Davenport <scrubbed>
To: bug-bash AT gnu.org,bash AT packages.debian.org
Subject: redraw-current-line fails with multiline prompts

Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS:  -DPROGRAM='bash' -DCONF_HOSTTYPE='x86_64' -DCONF_OSTYPE='linux-gnu' -DCONF_MACHTYPE='x86_64-pc-linux-gnu' -DCONF_VENDOR='pc' -DLOCALEDIR='/usr/share/locale' -DPACKAGE='bash' -DSHELL -DHAVE_CONFIG_H   -I.  -I../. -I.././include -I.././lib  -D_FORTIFY_SOURCE=2 -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wall
uname output: Linux laptop 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u2 (2016-01-02) x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu

Bash Version: 4.3
Patch Level: 30
Release Status: release

Description:
    Assume I have a multiline prompt (`PS1="first\nsecond"`), and a bind to
    redraw-current-line (`bind '"\er": redraw-current-line'`). When I
    refresh the line with M-r, I get the following output:
    first
    first
    second

    What is happening is that the redraw-current-line is assuming that I
    only have a single line prompt, and is just redrawing that current
    line. Two possible solutions to this are:
      1) Work out the last line of the prompt and only redraw that (makes
        sense with function name, or
      2) Work out number of lines in the prompt, and redraw entire prompt.

Repeat-By:
    Have a rc file with the following
    PS1="first\nsecond"
    bind '"\er": redraw-current-line'

    Then start bash with that rc file, and hit M-r a few times, notice that
    the last line gets overwritten, but the first line doesn't. You can
    expand this by having PS="first\nsecond\nthird" and seeing that the
    third line gets overwritten, but not the first and the second. This
    leads to a lot of wasted vertical space if you are redrawing often.

Fix:
    I've got a patch for option 2, which works well with existing methods,
    but makes the function name a bit misleading.

index f29adf8..183aea4 100644
--- a/lib/readline/text.c
+++ b/lib/readline/text.c
@@ -562,8 +562,14 @@ rl_refresh_line (ignore1, ignore2)
      int ignore1, ignore2;
 {
   int curr_line;
+  int newlines;
+  char *s;

   curr_line = _rl_current_display_line ();
+  /* Detect any new lines on current prompt */
+  for (newlines = 0, s = rl_prompt; s[newlines]; s[newlines] == '\n' ? newlines++ : *s++);
+  /* Shift up current line by number of new lines */
+  curr_line -= newlines;

   _rl_move_vert (curr_line);
   _rl_move_cursor_relative (0, rl_line_buffer);   /* XXX is this right */

Nice :+1:

Possibly related: using C-R in bash when _not_ on the first (visible) line and pushing Esc right after that causes the bash prompt to repaint one line up, and leave an artifact on the bottom line.

himdel@niniel:~$ #cursor is here now
himdel@niniel:~$ $(__fzf_history__)

(bash version 4.3.42, libreadline5 5.2)

Got a reply from chat (bug maintainer).

Thanks for the report.  It's sufficient to change the call from
rl_forced_update_display to rl_redraw_prompt_last_line (your option 1).
This is what bash does for bash_execute_unix_command().

Chet

I replied:

Hey Chet,

Thanks for your reply. OK, so my patch doesn't need that weird for loop.
I couldn't find the redraw_prompt_last_line function when I had a quick
search.

Would that change be something that you would do to replace the readline
function binding redraw_current_line, or would it be something else that
would be exposed? It would be great if there was a way to redraw the prompt
without it generating a lot of extra vertical lines.

Cheers,

Hugh

@himdel hmm, I don't see that on same version when I use unmodified fzf bindings. I get the $(fzf_history) line removed and stay on the same line. Was that when you just did a brand new shell and pushed enter once then ctrl-r then esc? Or would you need more to reproduce it?

Cheers,

Hugh

Chets reply:

This is something that will be in bash-4.4/readline-7.0.  You can get the
current development sources to see how it will work.  According to my
change logs, it was added in November, 2014.

Chet

So this is something that will come eventually. I haven't figured out a workaround short of patching bash sadly...

@hughdavenport yes, I open a brand new bash, push enter, push ctrl+r, push escape .. and I get that output.

In case it's locale-dependent, I use en_US.UTF-8, and my terminal is rxvt-unicode. But trying different terminals, it seems xterm and lxterm are not affected, but aterm and rxvt-unicode are.

FWIW: I'm also seeing this behavior in zsh in iTerm2 (OS X).

I was on rxvt-unicode as well, and also on iterm2 osx. Locale was prob en_NZ for both

Same (?) issue on Zsh. I have a $PS1 with a line break in it. If I do CtrlR to search history with fzf, and then select a result (or just hit Esc to exit), the last line before my prompt is deleted. Demo: https://asciinema.org/a/uzF7EeuA2FLSd5UdVeIs8p5eD

Same issue with zsh and a multi-line $PS1.

IIRC, it is part of Zsh鈥檚 behavior that a call to the redisplay widget will cause the prompt to double up (and not be erased and redrawn) when you are using a multi-line prompt. Perhaps someone could look into the Zsh sources and maybe find a way to improve this.

In case you don鈥檛 find any better solution, you might also want to consider implementing a multiline prompt by using a single-line PROMPT and printing any additional lines before the prompt via a pre-command function. This also helped me work around some other issues, such as quirks when resizing the terminal.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fenuks picture fenuks  路  3Comments

olethanh picture olethanh  路  3Comments

ahmedelgabri picture ahmedelgabri  路  3Comments

firedev picture firedev  路  3Comments

ghost picture ghost  路  3Comments