4

The idea of my PS1 configuration is to show some extended info like Mercurial or Git repo status, command execution time, etc. The prompt is split by two lines because it produces too many characters to fit into a single line. Here is my PS1 in my .bashrc (not sure if the entire source code is necessary here):

function prompt_status {
        local color_app="\e[1;38;5;214m"
        local color_branch="\e[1;38;5;32m"
        local color_revision="\e[0;38;5;64m"
        if git rev-parse --is-inside-work-tree &> /dev/null; then
                local branch="$(git rev-parse --abbrev-ref HEAD | tr -d '\n')"
                local revision="$(git rev-parse HEAD | tr -d '\n')"
                echo -ne $color_app"git "$color_branch"$branch "$color_revision"($revision)"
        elif hg status &> /dev/null; then
                local branch="$(hg branch | tr -d '\n')"
                local revision_number="$(hg identify -n | tr -d '\n')"
                local revision="$(hg parent --template '{node}' | tr -d '\n')"
                echo -ne $color_app"hg "$color_branch"$branch "$color_revision"($revision_number:$revision)"
        else
                return
        fi
        echo -e " \e[0m"
}

function prompt_return_value {
        RET=$?
        if [[ $RET -eq 0 ]]; then
                echo -ne "" #echo -ne "\e[32m$RET\e[0m"
        else
                echo -ne "\e[1;37;41m$RET\e[0m "
        fi
}

function timer_start {
        timer=${timer:-$SECONDS}
}

function timer_stop {
        seconds_elapsed=$(($SECONDS - $timer))
        unset timer
}

function prompt_seconds_elapsed {
        local c;
        local t=${seconds_elapsed}s
        if [ $seconds_elapsed -ge 60 ]; then
                c=196
                t=$(format_seconds $seconds_elapsed)
        elif [ $seconds_elapsed -ge 20 ]; then
                c=214
        elif [ $seconds_elapsed -ge 10 ]; then
                c=100
        elif [ $seconds_elapsed -ge 5 ]; then
                c=34
        elif [ $seconds_elapsed -ge 1 ]; then
                c=22
        else
                return
        fi
        echo -ne "\e[0;38;5;${c}m${t} \e[0m"
}

function format_seconds {
        ((h=${1}/3600))
        ((m=(${1}%3600)/60))
        ((s=${1}%60))
        printf "%02d:%02d:%02d\n" $h $m $s
}

trap 'timer_start' DEBUG
PROMPT_COMMAND=timer_stop

export PS1="\n\e[1;38;5;106m\u@\h \e[0;38;5;136m\w\[\e[0m\]\n\$(prompt_return_value)\$(prompt_seconds_elapsed)\$(prompt_status)\$ "

The problem is that the prompt looks broken when a terminal window has small width. This is what I get for 80 columns:

username@some-very-long-hostname ~/tmp/d
06a14b06cac) $ 9866c9d0d26d2b27063a89ee1c330

It's like wrapped at the 2nd line causing total mess (see the $ sign in the middle). It works almost perfectly for a larger terminal column number, say 120:

username@some-very-long-hostname ~/tmp/d
hg default (0:69866c9d0d26d2b27063a89ee1c3306a14b06cac) $

Also I noticed that add more text to the end of the terminal line causes an issue that's very similar to the effects described for 80 columns above. The question is: does bash handle new lines or "too long" incorrectly for PS1?

Thanks.


UPDATE

This question is not an exact duplicate of Why is my bash prompt getting bugged when I browse the history? . After some discussion with @AdamKatz, it seems that zero-length output escapes \[ and '] work only when they are literally put in the PS1 string, but they does not seem to work when returned from a function causing to appear unescaped on terminal.

Rui F Ribeiro
  • 56,709
  • 26
  • 150
  • 232

1 Answers1

5

For multi-line prompts (including when it wraps, including from your commands), you need to enclose your color codes in escaped square brackets (like \[$color\]).

This example is green, has user@hostname:workingdir $ and then reverts back to uncolored:

PS1='\[\e[1;32m\]\u@\h:\w \$\[\e[0;0m\]'

If you're seeing literal \[ or [ appear in your prompt, it's probably being expanded from \[ to [ before being interpreted for prompt expansion. In that case, try \001 for \[ and \002 for \] and you should be good to go. If you're not having luck with \e, try \033 instead. These octal codes are more portable since they'll pass through (programs like sed won't try to unescape or interpret them).

Adam Katz
  • 3,965
  • See also my $PS1 coloration and truncations among other colorizing techniques. (\033 is the same as \e but sometimes works better.) – Adam Katz Mar 18 '15 at 15:41
  • Thank you for the answer. It seems to work only when the color codes are specified in the string literal passed to PS1 like in your example demonstrates. However, when I add enclosing brackets to a function like prompt_return_value (see the question), then I get \[ and \] (example: \[128\], but 128 is still red) to appear in my prompt. It looks like the enclosing brackets only work when passed "directly" in the string. Can't make it work with my functions. – Lyubomyr Shaydariv Mar 18 '15 at 16:02
  • maybe try the escaped brackets in the final $PS1 assignment, even if the content between them is a variable, like \[$color\] as noted in my answer. – Adam Katz Mar 18 '15 at 16:09
  • As far as I understand, \[ and \] work as if they consume the terminal output causing the output to be interpreted as zero-length output. Have no idea how to escape the function output properly. – Lyubomyr Shaydariv Mar 19 '15 at 08:27
  • My ~/.bashrc contains this: PSC() { echo -ne "\[\e[${1:-0;0}m\]"; } and then the assignment contains things like PS1="$(PSC '1;32')\u…" – Adam Katz Mar 19 '15 at 14:40