How do I fix my colour bash prompt wrapping?

05
2013-12
  • Chris R

    I have defined a bash prompt (using PROMPT_FUNCTION) like so:

    function get_hg_prompt_prefix() {
        local APPLIED_COLOR=$1; shift
        local UNAPPLIED_COLOR=$1; shift
        local ALERT_COLOUR=$1; shift
        local TEXTCOLOR=$1; shift
        local mercurial_prompt_line="{{patches|join(:)|pre_applied(${APPLIED_COLOR})|post_applied(${TEXTCOLOR})|pre_unapplied(${UNAPPLIED_COLOR})|post_unapplied(${TEXTCOLOR})}\n\r}"
        local mercurial_status_prompt="{ ${ALERT_COLOUR}{status}${TEXTCOLOR}}"
    
        echo "$(hg prompt "${mercurial_prompt_line}" 2>/dev/null)$(hg prompt "${mercurial_status_prompt}" 2>/dev/null)"
    }
    
    function set_prompt() {
        bright='\[[01m\]'
        colors_reset='\[[00m\]'
        HOSTCOLOR=${colors_reset}='\[[34m\]'
        USERCOLOR=${colors_reset}='\[[01m\]'
        TEXTCOLOR=${colors_reset}='\[[32m\]'
        APPLIED_COLOR=${colors_reset}='\[[32m\]'
        UNAPPLIED_COLOR=${colors_reset}='\[[37m\]'
        ALERT_COLOUR=${colors_reset}='\[[31m\]'
    
        hg_status="$(get_hg_prompt_prefix $APPLIED_COLOR $UNAPPLIED_COLOR $ALERT_COLOUR $TEXTCOLOR)"
        ps1_prefix="${hg_status}$colors_reset($bright$(basename $VIRTUAL_ENV)$colors_reset) "
        PROMPTEND='$'
        PS1="${ps1_prefix}${USERCOLOR}\u${colors_reset}${TEXTCOLOR}@${colors_reset}${HOSTCOLOR}\h${colors_reset}${TEXTCOLOR} (\W) ${PROMPTEND}${colors_reset} "
    }
    
    PROMPT_COMMAND=set_prompt
    

    In general, this gives me a multi-line prompt that displays some hg status info as well as my current virtualenv, looking (without colour) like this:

    buggy-wins.patch
     ! (saas) user@computer (~) $ 
    

    The problem is, this is screwing with the calculation of the length of the prompt (I think!) and causing weird terminal wrapping issues and cursor placement. For example, in an 80-char terminal, here's the prompt I see (the **-surrounded character is the cursor location):

    ~) $ **a**nis) crose@chris-rose (~
    

    In terminals wide enough to display the prompt, line wrapping occurs much earlier than it should; here's the most text I can fit on the first line of the prompt in a 108-char-wide terminal window (again, the ** marks my cursor location):

     **(**advanis) crose@chris-rose (~) $ sdkfjlskdjflksdjff
    

    When the line wraps over, it overwrites the prompt. The second line of input runs right to the edge of the terminal, however, and then wraps correctly.

    So, clearly something is messing with the width of the prompt. How can I cause bash to determine the length of the PS1 string not according to the ANSI escape codes, but according to the actual displayed length of the prompt?

  • Answers
  • grawity

    bash uses \[ \] to determine the "displayed length": text between those two escapes is considered unprintable and not counted in the total length; everything else is.

    There seems to be a problem with your variables: bright='\[[01m\]' doesn't actually include an ESC character, so [01m gets printed as normal text but is not counted in the length. It should be '\[\e[01m\]'. Same for all other variables.


    Related:

    • in Bash, you can put \$(hg_status) to $PS1 directly, without the need for a separate PROMPT_COMMAND.

    • Consistency is good. HOSTCOLOR and ALERT_COLOUR is not.


  • Related Question

    How to bind a function to Control-2 key combo on bash
  • Nuno Maltez

    I want to set a key binding in bash for "history-search-backward" readline command to a combination of Control+some other key (I'm using 2 as an example), but I'm unable to do so.

    (edit: it seems the problem was my choice of 2 as the example key. I tried with \C-l and it's working. I'll still accept an answer if someone explains why 2 doesn't work)

    After several tries my ~/.inputrc now looks like this

    set bind-tty-special-chars off
    "\C-2": history-search-backward
    

    but it doesn't work and bind -p | grep "-2" gives nothing. If I try something without the control key, it works:

    "C-2": history-search-backward
    

    I can search in the history by prssing the sequence C + - + 2.

    bind -p gives control in \C form, for example:

    "\C-w": unix-word-rubout
    

    I've tried different formats in my inputrc:

    Control-2: history-search-backward
    Ctrl-2: history-search-backward
    "Control-2": history-search-backward
    

    but nothing works.

    "\e2": history-search-backward
    

    works if I press Escape followed by 2.

    Can anyone help?

    Setup:
    Fedora 11:
    Bash version 4.0.23(1)
    GNU Readline 5.2 (according to the man page)


  • Related Answers
  • Peter S. Housel

    There's no ASCII code for Control-2. Control-@ through Control-_ correspond to control codes 0x00 (NUL) through 0x1F (Unit Separator). For example, the code for Control-I is the code for 'I' (0x49) minus 0x40 = 0x09 (HT, aka tab). There's no set definition for Control+(some other character not in the @ to _ block).

    Programs that do their own keyboard handling can interpret Control any way they like in combination with any other keys. But programs like bash, which read their input through a terminal, don't have any way of even seeing Control-2.