Monday, October 26, 2009

Recipe 21.9. Changing Text Color










Recipe 21.9. Changing Text Color





Problem


You want to display
multicolored text on the console.




Solution


The simplest solution is to use HighLine. It lets you enclose
color commands in an ERb template that gets interpreted within HighLine and printed to standard output. Try this colorful bit of code to test the capabilities of your terminal:



require 'rubygems'
require 'highline/import'

say(%{Here's some <%= color('dark red text', RED) %>.})
say(%{Here's some <%= color('bright red text on a blue background',
RED+BOLD+ON_BLUE) %>.})
say(%{Here's some <%=
color('blinking bright cyan
text', CYAN+BOLD+BLINK) %>.})
say(%{Here's some <%= GREEN+UNDERLINE %>underlined dark green text<%=CLEAR%>.})



Some of these features (particularly the blinking and underlining) aren't supported on all terminals.




Discussion


The
HighLine#color
method encloses a display string in special command strings, which start with an escape character and a left square bracket:



HighLine.new.color('Hello', HighLine::GREEN)
# => "\e[32mHello\e[0m"



These are ANSI escape sequences. Instead of displaying the string "\e[32m", an ANSI-compatible terminal treats it as a command: in this case, a command to start printing characters in green-on-black. The string "\e[0m" tells the terminal to go back to white-on-black.


Most modern Unix terminals support ANSI escape sequences, including the Mac OS X terminal. You should be able to get green text in your irb session just by calling puts "\e[32mHello\e[0m" (try it!), but HighLine makes it easy to get color without having to remember the ANSI sequences.


Windows terminals don't support ANSI by default, but you can get it to work by loading ANSI.SYS (see below for a relevant Microsoft support article).


An alternative to HighLine is the Ncurses library.[4] It supports color terminals that use a means other than ANSI, but these days, most color terminals get their color support through ANSI. Since Ncurses is much more complex than HighLine, and not available as a gem, you should only use Ncurses for color if you're already using it for its other features.

[4] Standard Curses doesn't support color because it was written in the 1980s, when monochrome ruled the world.


Here's a rough equivalent of the HighLine program given above. This program uses the Ncurses::program wrapper described in Recipe 21.5. The wrapper sets up Ncurses and initializes some default color pairs:



Ncurses.program do |s|
# Define the red-on-blue color pair used in the second string.
# All the default color pairs use a black background.
Ncurses.init_pair(8, Ncurses::COLOR_RED, Ncurses::COLOR_BLUE)

Ncurses::attrset(Ncurses::COLOR_PAIR(1))
s.mvaddstr(0,0, "Here's some dark red text.")

Ncurses::attrset(Ncurses::COLOR_PAIR(8) | Ncurses::A_BOLD)
s.mvaddstr(1,0, "Here's some bright red text on a blue background.")
Ncurses::attrset(Ncurses::
COLOR_PAIR(6) | Ncurses::A_BOLD |
Ncurses::A_BLINK)
s.mvaddstr(2,0, "Here's some blinking bright cyan
text.")

Ncurses::attrset(Ncurses::COLOR_PAIR(2) | Ncurses::A_UNDERLINE)
s.mvaddstr(3,0, "Here's some underlined dark green text.")

s.getch
end



An Ncurses program can draw from a palette of color pairscombinations of foreground and background colors. Ncurses::program sets up a default palette of the seven basic ncurses colors (red, green, yellow, blue, magenta, cyan, and white), each on a black background. You can change this around if you like, or define additional color pairs (like the red-on-blue defined in the example). The following Ncurses program prints out a color chart of all foreground-background pairs. It makes the text of the chart bold, so that the text doesn't become invisible when the background is the same color.



Ncurses.program do |s|
pair = 0
Ncurses::COLORS.each_with_index do |background, i|
Ncurses::COLORS.each_with_index do |foreground, j|
Ncurses::init_pair(pair, foreground, background) unless pair == 0
Ncurses::attrset(Ncurses::COLOR_PAIR(pair) | Ncurses::A_BOLD)
s.mvaddstr(i, j*4, "#{foreground},#{background}")
pair += 1
end
end
s.getch
end



You can modify a color pair by combining it with an Ncurses constant. The most useful constants are Ncurses::A_BOLD, Ncurses::A_BLINK, and Ncurses::A_UNDERLINE. This works the same way (and, on an ANSI system, uses the same ANSI codes) as HighLine's BOLD, BLINK, and UNDERLINE constants. The only difference is that you modify an Ncurses color with the OR operator (|), and you modify a HighLine color with the addition operator.




See Also


  • Recipe 1.3, "Substituting Variables into an Existing String," has more on ERb

  • http://en.wikipedia.org/wiki/ANSI_escape_code has technical details on ANSI color codes

  • The examples/ansi_colors.rb file in the HighLine gem

  • You can get a set of Ncurses bindings for Ruby at http://ncurses-ruby.berlios.de/; it's also available as the Debian package libncurses-ruby

  • If you want something more lightweight than the highline gem, try the termansicolor gem instead: it defines methods for generating the escape sequences for ANSI colors, and nothing else

  • "How to Enable ANSI.SYS in a Command Window" (http://support.microsoft.com/?id=101875)













No comments: