2012-02-18

How to make bash put the prompt to the leftmost column

This blog post explains how to configure bash so it will always put the prompt to the leftmost column of the terminal. By default, if the last command prints something without a terminating newline, the cursor remains in the middle of the line, and bash puts its prompt there.

This default behavior can be confusing for line editing, because libreadline, the library bash uses to read the next command interactively (handling cursor movements, history retrieval etc.), assumes that the prompt is put to the leftmost column, and if this assumption is wrong, libreadline may redraw the current line incorrectly. As an example, the default behavior of pressing Ctrl-A (Home) in a multiline command with a prompt not at the leftmost column would position the cursor incorrectly.

zsh fixes this by forcing each prompt to be put at the leftmost column of the terminal. The same can be achieved with bash by running this:

PROMPT_COMMAND='printf %b%${COLUMNS}b%b "\033[0;7m%\033[0m" "\r" "\e[K"'

The result looks like this (the commands typed by the user being bolded):

$ #foo
$ echo hello
hello
$ echo -n world
world$ #bar
$ PROMPT_COMMAND='printf %b%${COLUMNS}b%b "\033[0;7m%\033[0m" "\r" "\e[K"'
$ echo hello
hello
$ echo -n world
world%
$ #foo

If you like the difference, add the PROMPT_COMMAND=... assignment to your ~/.bashrc (and reopen the terminal windows) to make it permanent.

Explanations about how it works:

  • bash runs $PROMPT_COMMAND before displaying each prompt.
  • Before running $PROMPT_COMMAND, bash sets COLUMNS to the current number of columns on the terminal.
  • printf %${COLUMNS}s foo makes bash prints COLUMNS-3 spaces, and then foo, all without a newline.
  • The %b escape for bash printf is like %s, but it interprets backslash escapes, e.g. it substitutes \033 with a single character whose ASCII code is the octal 033.
  • Most modern terminals don't move to the next line if the cursor is in the rightmost column, and a single character is printed.
  • Printing an \r (carriage return) moves the cursor to the leftmost column in most terminals.
  • From the above it follows that printing COLUMNS spaces and then a \r prints some spaces and moves to the beginning of the next line, except when the cursor is already at the leftmost column, then it prints some spaces, and moves to the beginning of the current line.
  • \e[K removes everything from the cursor to the end-of-the line. It's better than a space and a \r (for removing the percent sign), because it doesn't pollute the copy-paste buffer of the terminal with extra spaces. (Thanks to Egmont for pointing this out and sending a fix!)
  • The solution does essentially this (prints COLUMNS spaces and then a \r), except that first it prints an inverted % sign, but later it hides the % sign if the cursor was in the leftmost column.

No comments: