Skip to content

Instantly share code, notes, and snippets.

@KevinGoodsell
Created December 17, 2010 00:32
Show Gist options
  • Save KevinGoodsell/744284 to your computer and use it in GitHub Desktop.
Save KevinGoodsell/744284 to your computer and use it in GitHub Desktop.
The Trouble With Terminals
The Trouble With Terminals
Thu, 16 Dec 2010 16:43:20 -0800
Copyright 2010 Kevin Goodsell
0. License
This work is licensed under a Creative Commons
Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/3.0/; or, send a letter to
Creative Commons, 171 2nd Street, Suite 300, San Francisco, California,
94105, USA.
Attributions should include my name (Kevin Goodsell) and the web site
that hosts this document, https://gist.github.com/744284.
1. Overview
GNOME Terminal is the default terminal for the GNOME Desktop. GNU Screen
is a terminal multiplexer which functions as a terminal running in your
terminal. Vim is a powerful terminal-based[1] text editor. Terminfo (or
the older termcap) is a library that allows applications to interact
with a wide variety of terminals, and is used by both Screen and Vim.
Given this stack of software (Vim using terminfo on Screen using
terminfo on GNOME Terminal), what could possibly go wrong? It turns out
that getting one terminal set up properly can be a challenge, and
getting a stack of two working together is extra difficult. And that's
assuming you *only* use Screen under one type of terminal. Vim is a
pretty good test case for terminal behavior, because it is clear right
away when features like 256 colors and mouse reporting are not working
correctly.
2. GNOME Terminal
If you just run Vim in GNOME Terminal you'll probably find that the
mouse works (assuming you enable it with :set mouse=a) but the colors
are limited to 8. GNOME Terminal supports 256 colors, so why doesn't Vim
know this?
2.1 Colors
It turns out that Vim generally only knows what terminfo tells it about
the terminal. Terminfo only knows what's in its database about the
terminal, and it only knows *which* terminal by checking the $TERM
environment variable. You can find out what terminfo thinks about your
terminal with the command "infocmp":
$ infocmp
# Reconstructed via infocmp from file: /lib/terminfo/x/xterm
xterm|X11 terminal emulator,
am, bce, km, mc5i, mir, msgr, npc, xenl,
colors#8, cols#80, it#8, lines#24, pairs#64,
...
You can see immediately that "colors" is set to 8, so that explains the
problem in Vim. You can also see that terminfo thinks that GNOME
Terminal is actually xterm. The reason for that is the $TERM environment
variable:
$ echo $TERM
xterm
$TERM is set to "xterm" by GNOME Terminal -- always, unconditionally.
There simply is no way to make it do otherwise[2]. If you want your
terminal properly identified, you're going to have to change $TERM after
the terminal starts. You can do this in your shell initialization script
(.bashrc or whatever). However, if you change $TERM unconditionally, it
will be wrong for any other terminal you use. Here's a somewhat
reasonable attempt:
if [ "$TERM" = xterm -a "$COLORTERM" = "gnome-terminal" ]; then
TERM=gnome-256color
fi
Since GNOME Terminal also sets $COLORTERM as shown, that can be used to
make a guess about the terminal that's actually in use. This won't cover
all possible cases, however. If you launch xterm from your GNOME
Terminal session, it will probably get the wrong $TERM value.
There's another problem with this: the terminfo entry for gnome-256color
is probably not installed by default. You may need to install an extra
package to get it (ncurses-term on Debian and Ubuntu). Alternatively,
you can try using xterm-256color.
2.2 Mouse
Once you've changed the $TERM value, Vim should be able to use 256
colors, but the mouse no longer works. This is annoying but easy to fix.
Vim cheats a little to enable the mouse with xterms, and once you are
using a different $TERM this cheat stops working. You need to tell Vim
about your terminal's mouse support. This is done with Vim's ttymouse
setting, so you can simply add the following to your .vimrc:
set ttymouse=xterm2
3. Terminfo
Now that 256 color mode and the mouse are working, it's time to address
some other issues created by the $TERM change. The first problem is that
the window title is no longer dynamically updated to reflect the current
buffer in Vim.
3.1 Window Title (Status Line)
The window title problem is due to the terminfo entry being incomplete.
The only way to update the terminfo entry is to write a replacement
terminfo file and compile it with the tic command. This is actually
pretty easy to do once you have the terminal codes. Here's a possible
terminfo file:
gnome-256color|GNOME Terminal with xterm 256-colors,
# Set up "status line", which is actually the window title.
hs, wsl#60, dsl=\E]2;\007, tsl=\E]2;, fsl=\007,
use=gnome-256color,
Don't leave off the final comma, or the file won't work. This adds
"status line" features to the terminal description, emulated by the
window title. hs indicates "has status", wsl is the width of the status
line, dsl "disables" the status line by making it empty, tsl is "to
status line", and fsl is "from status line." Stuff written between a tsl
and a fsl appears in the window title -- note that dsl is just a tsl-fsl
pair with nothing in between. The "use" item causes the rest of the
entry to be filled in with the existing terminfo entry for
gnome-256color. You can read about the terminal description format in
terminfo(5).
Once you've saved this file, you can compile it with tic this way:
$ tic term.ti
That should be all you need. The compiled file is automatically placed
in you user terminfo folder, ~/.terminfo. Don't run that command as root
or it will replace the original system copy of gnome-256color.
3.2 Screen Clearing
Now that the title is fixed, there's another problem. If you use a
different color background in Vim than you use on the command line,
something weird happens. Try running Vim, choose a color scheme with a
dramatically different background color, then exit. Everything should
look fine, but now open a man page. This gives a weird two-toned
background, as if the manual text was drawn with one background color on
top of an existing background of a different color. In fact, that's
pretty much what happened.
Full-screen terminal applications like Vim and less (the pager typically
used by the "man" command) commonly use the "alternate screen buffer"
feature of some terminals. This mode is started with the smcup terminfo
string and ended with rmcup[3]. By using infocmp you can see what rmcup
does:
rmcup=\E[2J\E[?47l\E8
These terminal control codes can be interpreted with a little effort[4].
There are three distinct commands here, each beginning with an ESC
character which is indicated by \E. \E[2J erases the display, \E[?47l
switches from the alternate screen buffer to the normal screen buffer,
and \E8 is used to restore the cursor position (previously saved by
smcup). Based on what we've seen, we can infer that the screen is being
"erased" by filling it with the background color. Unfortunately the
color being used is Vim's background color, not the default terminal
background color. If we can restore the default background color first,
we should get better results. Fortunately there's a code for this:
\E[49m. Open your terminfo file again and add a new rmcup:
gnome-256color|GNOME Terminal with xterm 256-colors,
# Set up "status line", which is actually the window title.
hs, wsl#60, dsl=\E]2;\007, tsl=\E]2;, fsl=\007,
# Reset the background color before clearing the alt screen buffer:
rmcup=\E[49m\E[2J\E[?47l\E8,
use=gnome-256color,
Compile it with tic as before and the background problem should be
solved.
4. Screen
With everything finally working so well, it's time to try adding Screen.
Prepare for disappointment, as everything that was working stops working
under Screen. Time to start from scratch.
4.1 Colors Revisited
As before, infocmp will show us why we're back at 8 colors:
$ infocmp
# Reconstructed via infocmp from file: /lib/terminfo/s/screen
screen|VT 100/ANSI X3.64 virtual terminal,
am, km, mir, msgr, xenl,
colors#8, cols#80, it#8, lines#24, pairs#64,
...
There's nothing obviously wrong with $TERM, but the number of colors in
this terminfo entry is only 8. Since Screen is running on a 256 color
terminal, it should be able to support 256. Fortunately there is a
256-color terminfo entry for Screen. In fact, there are four:
screen-256color
screen-256color-s
screen-256color-bce
screen-256color-bce-s
The -s variants include the status line capabilities tsl, fsl, and dsl,
which were discussed previously. Strangely, they are missing the hs and
wsl capabilities, which may cause the status line to not work in some
applications.
The -bce variants support "background color erase", which means that
areas of the screen can be cleared by erasing them rather than filling
them with space characters. This may be faster, and prevents extra
spaces when doing copy-paste from the terminal.
While that may all sound nice, you can't actually use the last one
(screen-256color-bce-s) because the name is too long[5]. Ignoring that
for now, our immediate goal is to make 256 colors work again. Since bce
is nice to have, and it's supported in GNOME Terminal, we'll use
screen-256color-bce by putting the following in ~/.screenrc:
term screen-256color-bce
defbce on
After restarting screen, you should be back to 256 colors.
4.2 Mouse Revisited
Screen can't really tell that the terminal supports the xterm mouse
protocol for the same reason Vim can't: there's no terminfo capability
for it. Just like Vim needs you to explicitly set ttymouse, Screen needs
you to tell it that special xterm codes are supported. This is done in
the .screenrc file with a line like this:
termcapinfo gnome* XT
XT is a custom termcap/terminfo capability that Screen recognizes.
After adding this and restarting Screen and Vim, the mouse should be
working again.
4.3 Window Title (Status Line) Revisited
We already know how to fix the window title problem, and we can repeat
the process for Screen. The latest iteration of our terminfo file looks
like this:
status-addon|Extra attributes for window title as status line,
# Set up "status line", which is actually the window title.
hs, wsl#60, dsl=\E]2;\007, tsl=\E]2;, fsl=\007,
gnome-256color|GNOME Terminal with xterm 256-colors,
# Reset the background color before clearing the alt screen buffer:
rmcup=\E[49m\E[2J\E[?47l\E8,
use=status-addon,
use=gnome-256color,
screen-256color-bce|GNU Screen with 256 colors and BCE,
use=status-addon,
use=screen-256color-bce,
Compile it with tic and the window title should work again.
4.4 Screen Clearing Revisited
By default, Screen doesn't use an alternate screen buffer for
applications like Vim. If you want that, you can set the altscreen
option in .screenrc.
Whether you use altscreen or not, you may find that the screen doesn't
get cleared completely when exiting Vim. This is similar to what we've
already seen, and can be fixed in a similar way. For Screen, the
terminfo capability rmcup looks like this:
rmcup=\E[?1049l
That is just one control sequence, and it tells Screen to switch to the
normal screen buffer, clearing the screen and restoring the cursor if
necessary. We can add the same code from before (to reset the background
color to the default), but this doesn't completely solve the problem in
the non-altscreen case. To completely fix it we'll need to clear the
line as well, with the code \E[2K. So, our terminfo file now looks like
this:
status-addon|Extra attributes for window title as status line,
# Set up "status line", which is actually the window title.
hs, wsl#60, dsl=\E]2;\007, tsl=\E]2;, fsl=\007,
gnome-256color|GNOME Terminal with xterm 256-colors,
# Reset the background color before clearing the alt screen buffer:
rmcup=\E[49m\E[2J\E[?47l\E8,
use=status-addon,
use=gnome-256color,
screen-256color-bce|GNU Screen with 256 colors and BCE,
# Reset the background color and clear the line before leaving alt
# screen buffer:
rmcup=\E[49m\E[2K\E[?1049l,
use=status-addon,
use=screen-256color-bce,
Once you've compiled this with tic and restarted Screen you should
finally have everything working properly.
Appendix A. Multiple Screen Configurations
Screen doesn't have easy support for changing the configuration
depending on the hosting terminal. The screenrc file doesn't support
conditionals, so a little extra creativity is required.
A.1 Separate Terminal Definitions
Screen will attempt to set its term to screen.$TERM if such a terminal
definition exists. You can create custom terminal definitions for Screen
running under different terminal types the same way we created them
before. Here's a possible terminfo file with terminal definitions for
Screen on top of GNOME Terminal and the Linux console:
status-addon|Extra attributes for window title as status line,
# Set up "status line", which is actually the window title.
hs, wsl#60, dsl=\E]2;\007, tsl=\E]2;, fsl=\007,
gnome-256color|GNOME Terminal with xterm 256-colors,
# Reset the background color before clearing the alt screen buffer:
rmcup=\E[49m\E[2J\E[?47l\E8,
use=status-addon,
use=gnome-256color,
screen.gnome-256color|GNU Screen on GNOME Terminal,
# Reset the background color and clear the line before leaving alt
# screen buffer:
rmcup=\E[49m\E[2K\E[?1049l,
use=status-addon,
use=screen-256color-bce,
screen.linux|GNU Screen on Linux Console,
rmcup=\E[49m\E[2K\E[?1049l,
use=screen-bce,
A.2 Separate Screenrcs
Using separate terminal entries is reasonably powerful, but you may want
different Screen options under different terminals as well. This
requires separate configuration files, and some way to load the correct
one. The simplest way to do this is probably to conditionally set the
$SCREENRC environment variable in your shell startup script. Here's an
example:
if [ -f ~/.screenrc.$TERM ]; then
export SCREENRC=~/.screenrc.$TERM
fi
This will automatically select a file named .screenrc.gnome-256color in
GNOME Terminal, .screenrc.linux in the Linux console, etc. It will still
fall back on .screenrc if no $TERM-specific file is found. You can also
put common options in .screenrc and use Screen's "source" command inside
the $TERM-specific files to include those options.
Appendix B. Terminal Titles in the Shell
The default .bashrc file on Debian and Ubuntu contains something like
this:
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
;;
*)
;;
esac
This adds a prefix to the shell prompt which includes xterm escape codes
for setting the window title. This is unfortunate in a few ways. First,
it's unnecessarily restrictive in the shells it works with. GNOME
Terminal is perfectly capable of setting the window title, but if you've
set $TERM to something more GNOME-Terminal-appropriate, this feature
will be disabled.
The second reason this is an unfortunate implementation is that it
relies on intimate knowledge of the terminal's capabilities and escape
codes. The whole reason for the existence of termcap and terminfo is so
that applications can use terminals without worrying about details like
this. Here's an alternate approach:
if tput hs; then
PS1="\[$(tput tsl)\${debian_chroot:+(\$debian_chroot)}\u@\h: \w$(tput fsl)\]$PS1"
fi
This checks for the hs capability, and uses the tsl and fsl capabilities
to write to the "status line", which is actually the window title. The
meaning of these capabilities was explained earlier.
The use of \[ and \] is briefly described in the bash(1) man page, but
it's not obvious why they are necessary. This seems to be related to
bash's idea of how much space is available on the input line. If
non-printing characters aren't inside \[\], bash will expect them to
take up space on the line, then when you enter a long command it will
line-wrap at the wrong position.
--
[1] Vim also has a non-terminal GUI version which isn't relevant to this
article.
[2] https://bugzilla.gnome.org/show_bug.cgi?id=115750
[3] "cup" appears to stand for "cursor position", and is another
terminfo capability used for moving the cursor to an arbitrary location
on the screen. smcup is used to prepare the terminal for cup usage, and
rmcup is used to reset it.
[4] A decent reference for the codes supported by xterm can be found
here:
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Many terminals (including GNOME Terminal and Screen) support a subset of
these codes, but beware of subtle differences.
[5] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=491812
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment