Through this module, this library provides platform-independent support for
control character sequences following the 'ANSI' standards (see further below)
for terminal software that supports those sequences, running on a Unix-like
operating system or on Windows (see further below).
The sequences of control characters (also referred to as 'escape' sequences or
codes) provide a rich range of functionality for terminal control, which
includes:
Colored text output, with control over foreground, background and (where
supported) underlining colors
Clearing parts of a line or the screen
Hiding or showing the cursor
Moving the cursor around
Reporting the position of the cursor
Enabling or disabling automatic line wrapping
Scrolling the screen up or down
Switching between the Alternate and Normal Screen Buffers
Clickable hyperlinks to URIs
Changing the title of the terminal
A terminal that supports control character sequences acts on them when they
are flushed from the output buffer (with a newline character "\n" or, for
the standard output channel, hFlush stdout).
'ANSI' standards
The 'ANSI' standards refer to (1) standard ECMA-48 `Control Functions for
Coded Character Sets' (5th edition, 1991); (2) extensions in ITU-T
Recommendation (previously CCITT Recommendation) T.416 (03/93) 'Information
Technology – Open Document Architecture (ODA) and Interchange Format: Character
Content Architectures` (also published as ISO/IEC International Standard
8613-6); and (3) further extensions used by 'XTerm', a terminal emulator for
the X Window System. The escape codes are described in a
Wikipedia article and those
codes supported on current versions of Windows are descibed in
Microsoft's documentation.
The whole of the 'ANSI' standards are not supported by this library but most
(if not all) of the parts that are popular and well-supported by terminal
software are supported (see further below).
Cursor positions
The functions moving the cursor to an absolute position are 0-based (the
top-left corner is considered to be at row 0 column 0) (see setCursorPosition)
and so is getCursorPosition. The 'ANSI' standards themselves are 1-based
(that is, the top-left corner is considered to be at row 1 column 1) and some
functions reporting the position of the cursor are too (see
reportCursorPosition).
Windows and control character sequences
The native terminal software on Windows has developed over time. Before
Windows 10 version 1511 (known as the 'November [2015] Update' or
'Threshold 2') that software did not support control character sequences. From
2018, Microsoft introduced the Windows Pseudo Console ('ConPTY') API and then
Windows Terminal, with the objective of replacing most of the Windows Console
API with the use of control character sequences and retiring the historical
user-interface role of Windows Console Host ('ConHost').
Windows Terminal is supported on Windows 10 version 19041.0 or higher and
provided with Windows 11. It can be downloaded from the Microsoft Store. Windows
Terminal can be set as the default terminal application on Windows 10 (from
the 22H2 update) and is the default application on Windows 11 (from the 22H2
update).
Despite the above developments, some Windows users may continue to use ConHost.
ConHost does not enable the processing of 'ANSI' control characters in output
by default. See hNowSupportsANSI for a function that can try to enable such
processing.
Terminal software other than the native software exists for Windows. One example
is the 'mintty' terminal emulator for 'Cygwin', 'MSYS' or 'MSYS2', and
dervied projects, and for 'WSL' (Windows Subsystem for Linux).
GHC's management of input and output (IO) on Windows has also developed over
time. If they are supported by the terminal software, some control character
sequences cause data to be emitted into the console input stream. For GHC's
historical and default IO manager, the function System.IO.hGetBufNonBlocking
in module System.IO does not work on Windows. This has been attributed to the
lack of non-blocking primatives in the operating system (see
GHC bug report #806. GHC's
native IO manager on Windows ('WinIO'), introduced as a preview in
GHC 9.0.1,
has not yet provided a solution. On Windows, this library uses emulation based
on the Windows Console API to try to read data emitted into the console input
stream. Functions that use that emulation are not supported on consoles, such
as mintty, that are not based on that API.
Function variants provided
Every function exported by this module comes in three variants, namely:
A variant that has an IO () type and doesn't take a Handle (for example,
clearScreen :: IO ()). This variant just outputs the `ANSI` command
directly to the standard output channel (stdout) and any terminal
corresponding to it. Commands issued like this should work as you expect on
both Unix-like operating systems and Windows (unless exceptions on Windows
are stated).
An 'h...' variant that has an IO () type but takes a Handle (for
example, hClearScreen :: Handle -> IO ()). This variant outputs the
`ANSI` command to the supplied handle and any terminal corresponding to it.
Commands issued like this should also work as you expect on both Unix-like
operating systems and Windows (unless exceptions on Windows are stated).
A '...Code' variant that has a String type (for example,
clearScreenCode :: String). This variant outputs the sequence of control
characters as a String, which can be added to any other bit of text before
being output. If a high degree of backwards compatability is rewuired, the
use of these codes is discouraged because they will not work on legacy
versions of Windows where the terminal in use is not ANSI-enabled (see
further above). On Windows, where emulation has been necessary, these
variants will always output the empty string. That is done so that it is
possible to use them portably; for example, coloring console output on the
understanding that you will see colors only if you are running on a Unix-like
operating system or a version of Windows where emulation has not been
necessary. If the control characters are always required, see module
System.Console.ANSI.Codes.
Examples of use
A simple example is below:
module Main where
import System.Console.ANSI
import System.IO (stdout)
-- Set colors and write some text in those colors.
main :: IO ()
main = do
stdoutSupportsANSI <- hNowSupportsANSI stdout
if stdoutSupportsANSI
then do
setSGR [SetColor Foreground Vivid Red]
setSGR [SetColor Background Vivid Blue]
putStrLn "Red-On-Blue"
setSGR [Reset] -- Reset to default colour scheme
putStrLn "Default colors."
else
putStrLn "Standard output does not support 'ANSI' escape codes."
Another example is below:
module Main where
import System.IO (hFlush, stdout)
import System.Console.ANSI
main :: IO ()
main = do
stdoutSupportsANSI <- hNowSupportsANSI stdout
if stdoutSupportsANSI
then do
setSGR [SetColor Foreground Dull Blue]
putStr "Enter your name: "
setSGR [SetColor Foreground Dull Yellow]
hFlush stdout -- flush the output buffer before getLine
name <- getLine
setSGR [SetColor Foreground Dull Blue]
putStrLn $ "Hello, " ++ name ++ "!"
setSGR [Reset] -- reset to default colour scheme
else
putStrLn "Standard output does not support 'ANSI' escape codes."
For many more examples, see the project's extensive
Example.hs file.
ANSI's eight standard colors. They come in two intensities, which are
controlled by ColorIntensity. Many terminals allow the colors of the
standard palette to be customised, so that, for example,
setSGR [ SetColor Foreground Vivid Green ] may not result in bright green
characters.
In respect of colors, there are three alternative commands:
the 'ANSI' standards allow for eight standard colors (with two
intensities). Windows and many other terminals (including xterm) allow the
user to redefine the standard colors (so, for example VividGreen may not
correspond to bright green;
an extension of the standard that allows true colors (24 bit color depth)
in RGB space. This is usually the best alternative for more colors; and
another extension that allows a palette of 256 colors, each color
specified by an index. Xterm provides a protocol for a palette of 256 colors
that many other terminals, including Windows 10, follow. Some terminals
(including xterm) allow the user to redefine some or all of the palette
colors.
Set a color from a palette of 256 colors using a numerical index
(0-based). Supported natively on Windows 10 from the Creators Update (April
2017) but not on legacy Windows native terminals. See xtermSystem,
xterm6LevelRGB and xterm24LevelGray to construct indices based on
xterm's standard protocol for a 256-color palette.
Given xterm's standard protocol for a 256-color palette, returns the index
to that part of the palette which is a spectrum of 24 grays, from dark
gray (0) to near white (23) (black and white are themselves excluded). Throws
an error if the gray is outside of the range 0 to 23. An example of use is:
Given xterm's standard protocol for a 256-color palette, returns the index
to that part of the palette which is a 6 level (6x6x6) color cube of 216 RGB
colors. Throws an error if any of the red, green or blue channels is outside
the range 0 to 5. An example of use is:
Given xterm's standard protocol for a 256-color palette, returns the index
to that part of the palette which corresponds to the 'ANSI' standards' 16
standard, or 'system', colors (eight colors in two intensities). An example
of use is:
Example1 expression
>>> setSGR [ SetPaletteColor $ xtermSystem Vivid Green ]
The difference between movements "by character" and "by line" is
that *Line functions additionally move the cursor to the start of the
line, while functions like cursorUp and cursorDown keep the column
the same.
Code to move the cursor to the specified position (row and column). The
position is 0-based (that is, the top-left corner is at row 0 column 0).
Saving, restoring and reporting cursor position
3 declarations
These code sequences are not part of ECMA-48 standard; they are popular,
but non-portable extensions. E. g., Terminal.app on MacOS
does not support them.
A more portable way would be to query terminfo database
for rc and sc capabilities.
Looking for a way to get the cursors position? See
getCursorPosition.
Emit the cursor position into the console input stream, immediately after
being recognised on the output stream, as:
ESC [ <cursor row> ; <cursor column> R
Note that the information that is emitted is 1-based (the top-left corner is
at row 1 column 1) but setCursorColumn and setCursorPosition are
0-based.
In isolation of getReportedCursorPosition or getCursorPosition, this
function may be of limited use on Windows operating systems because of
difficulties in obtaining the data emitted into the console input stream.
Code to emit the cursor position into the console input stream, immediately
after being recognised on the output stream, as:
ESC [ <cursor row> ; <cursor column> R
Note that the information that is emitted is 1-based (the top-left corner is
at row 1 column 1) but setCursorPositionCode is 0-based.
In isolation of getReportedCursorPosition or
getCursorPosition, this function may be of limited use
on Windows operating systems because of difficulties in obtaining the data
emitted into the console input stream.
Clearing parts of the screen
6 declarations
Note that these functions only clear parts of the screen. They do not
move the cursor. Some functions are based on the whole screen and others
are based on the line in which the cursor is located.
These code sequences are not part of ECMA-48 standard; they are popular,
but non-portable extensions, corresponding to smcup and rmcup capabilities
in terminfo database.
On Windows, if emulation is required, switching between alternate and
normal screen buffers is not emulated.
Use the Alternate Screen Buffer. If currently using the Normal Screen
Buffer, it will save the cursor position and switch to the Alternate Screen
Buffer. It will always clear the Alternate Screen Buffer. The Alternate
Screen Buffer has no scroll back facility.
It is an application's responsibility to ensure that it switches back to the
Normal Screen Buffer if an exception is raised while the Alternate Screen
Buffer is being used. For example, by using bracket_:
Use the Normal Screen Buffer. If currently using the Alternate Screen
Buffer, it will clear the Alternate Screen Buffer, and switch to the Normal
Screen Buffer. It will always restore the saved cursor position.
Looking for a way to get layer colors? See getLayerColor.
Emit the layerColor into the console input stream, immediately after
being recognised on the output stream, as:
ESC ] <Ps> ; rgb: <red> ; <green> ; <blue> <ST>
where <Ps> is 10 for Foreground and 11 for Background; <red>,
<green> and <blue> are the color channel values in hexadecimal (4, 8,
12 and 16 bit values are possible, although 16 bit values are most common);
and <ST> is the STRING TERMINATOR (ST). ST depends on the terminal
software and may be the BEL character or ESC \ characters.
This function may be of limited, or no, use on Windows operating systems
because (1) the function is not supported on native terminals and is
emulated, but the emulation does not work on Windows Terminal and (2) of
difficulties in obtaining the data emitted into the console input stream.
Code to emit the foreground or backgrond layer color into the console input
stream, immediately after being recognised on the output stream, as:
ESC ] <Ps> ; rgb: <red> ; <green> ; <blue> <ST>
where <Ps> is 10 for Foreground and 11 for Background; <red>,
<green> and <blue> are the color channel values in hexadecimal (4, 8,
12 and 16 bit values are possible, although 16 bit values are most common);
and <ST> is the STRING TERMINATOR (ST). ST depends on the terminal
software and may be the BEL character or ESC \ characters.
This function may be of limited, or no, use on Windows operating systems
because (1) the control character sequence is not supported on native
terminals (2) of difficulties in obtaining the data emitted into the
console input stream. See getReportedLayerColor.
Underlining is not supported.
Select Graphic Rendition mode: colors and other whizzy stuff
Commands: these will typically be applied on top of the
current console SGR mode. An empty list of commands is
equivalent to the list [Reset]. Commands are applied left to
right.
Commands: these will typically be applied on top of the current
console SGR mode. An empty list of commands is equivalent to the list
[Reset]. Commands are applied left to right.
Strictly speaking, these code sequences are not part of ECMA-48 standard;
they are popular, but non-portable extensions. However, in practice they seem
to work pretty much everywhere.
These code sequences are not part of ECMA-48 standard and not even an
xterm extension. Nevertheless
many terminals
support them. On Windows, if emulation is required,
hyperlinks are not emulated.
Introduce a hyperlink with an identifier for the link. Some terminals
support an identifier, so that hyperlinks with the same identifier are
treated as connected.
Introduce a hyperlink with (key, value) parameters. Some terminals support
an id parameter key, so that hyperlinks with the same id value are
treated as connected.
Code to introduce a hyperlink with an identifier for the link. Some
terminals support an identifier, so that hyperlinks with the same identifier
are treated as connected.
Code to introduce a hyperlink with (key, value) parameters. Some terminals
support an id parameter key, so that hyperlinks with the same id value
are treated as connected.
Use heuristics to determine whether the functions defined in this package
will work with a given handle.
If the handle is not writable (that is, it cannot manage output - see
System.IO.hIsWritable), then pure False is returned.
For Unix-like operating systems, the current implementation checks
that: (1) the handle is a terminal; and (2) a TERM environment variable is
not set to dumb (which is what the GNU Emacs text editor sets for its
integrated terminal).
For Windows, the current implementation checks: first that (1) the handle is
a terminal, (2) a TERM environment variable is not set to dumb, and (3)
the processing of 'ANSI' control characters in output is enabled; and
second, as an alternative, whether the handle is connected to a 'mintty'
terminal. (That is because the function System.IO.hIsTerminalDevice is used
to check if the handle is a terminal. However, where a non-native Windows
terminal (such as 'mintty') is implemented using redirection, that function
will not identify a handle to the terminal as a terminal.) If it is not
already enabled, this function does *not* enable the processing of 'ANSI'
control characters in output (see hNowSupportsANSI).
With one exception, equivalent to hSupportsANSI. The exception is that,
on Windows only, if a TERM environment variable is not set to dumb and
the processing of 'ANSI' control characters in output is not enabled, this
function first tries to enable such processing.
Some terminals (e.g. Emacs) are not fully ANSI compliant but can support
ANSI colors. This can be used in such cases, if colors are all that is
needed.
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on the Windows' Console API. (Command Prompt
and PowerShell are based on the Console API.)
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on the Windows' Console API. (Command Prompt
and PowerShell are based on the Console API.)
Attempts to get the reported cursor position data from the console input
stream. The function is intended to be called immediately after
reportCursorPosition (or related functions) have caused characters to be
emitted into the stream.
For example, on a Unix-like operating system:
-- set no buffering (if 'no buffering' is not already set, the contents of
-- the buffer will be discarded, so this needs to be done before the cursor
-- positon is emitted)
hSetBuffering stdin NoBuffering
-- ensure that echoing is off
input <- bracket (hGetEcho stdin) (hSetEcho stdin) $ \_ -> do
hSetEcho stdin False
reportCursorPosition
hFlush stdout -- ensure the report cursor position code is sent to the
-- operating system
getReportedCursorPosition
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on the Windows' Console API. (Command Prompt
and PowerShell are based on the Console API.)
Parses the characters emitted by reportCursorPosition into the console
input stream. Returns the cursor row and column as a tuple.
For example, if the characters emitted by reportCursorPosition are in
Stringinput then the parser could be applied like this:
let result = readP_to_S cursorPosition input
case result of
[] -> putStrLn $ "Error: could not parse " ++ show input
[((row, column), _)] -> putStrLn $ "The cursor was at row " ++ show row
++ " and column" ++ show column ++ "."
(_:_) -> putStrLn $ "Error: parse not unique"
Attempts to get the current terminal size (height in rows, width in
columns).
There is no 'ANSI' control character sequence that reports the terminal
size. So, it attempts to set the cursor position beyond the bottom right
corner of the terminal and then use getCursorPosition to query the console
input stream. It works only on terminals that support each step and if data
can be emitted to stdin. (Use System.IO.hIsTerminalDevice to test if
stdin is connected to a terminal.) Uses stdout. If stdout will be
redirected, see System.IO.hGetTerminalSize for a more general function.
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on Windows' Console API. (Command Prompt and
PowerShell are based on the Console API.)
For a different approach, one that does not use control character sequences
and works when stdin is redirected, see the
terminal-size package.
Attempts to get the current terminal size (height in rows, width in
columns), by writing control character sequences to the specified handle
(which will typically be stdout or System.IO.stderr).
There is no 'ANSI' control character sequence that reports the terminal
size. So, it attempts to set the cursor position beyond the bottom right
corner of the terminal and then use hGetCursorPosition to query the console
input stream. It works only on terminals that support each step and if data
can be emitted to stdin. (Use System.IO.hIsTerminalDevice to test if
stdin is connected to a terminal.)
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on the Windows' Console API. (Command Prompt
and PowerShell are based on the Console API.)
For a different approach, one that does not use control character sequences
and works when stdin is redirected, see the
terminal-size package.
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on the Windows' Console API. (Command Prompt
and PowerShell are based on the Console API.) This function also relies on
emulation that does not work on Windows Terminal.
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on the Windows' Console API. (Command Prompt
and PowerShell are based on the Console API.) This function also relies on
emulation that does not work on Windows Terminal.
Attempts to get the reported layer color data from the console input
stream. The function is intended to be called immediately after
reportLayerColor (or related functions) have caused characters to be
emitted into the stream.
For example, on a Unix-like operating system:
-- set no buffering (if 'no buffering' is not already set, the contents of
-- the buffer will be discarded, so this needs to be done before the cursor
-- positon is emitted)
hSetBuffering stdin NoBuffering
-- ensure that echoing is off
input <- bracket (hGetEcho stdin) (hSetEcho stdin) $ \_ -> do
hSetEcho stdin False
reportLayerColor Foreground
hFlush stdout -- ensure the report cursor position code is sent to the
-- operating system
getReportedLayerColor Foreground
On Windows operating systems, the function is not supported on consoles, such
as mintty, that are not based on the Windows' Console API. (Command Prompt
and PowerShell are based on the Console API.)
Parses the characters emitted by reportLayerColor into the console input
stream.
For example, if the characters emitted by reportLayerColor are in Stringinput then the parser could be applied like this:
let result = readP_to_S (layerColor layer) input
case result of
[] -> putStrLn $ "Error: could not parse " ++ show input
[(col, _)] -> putStrLn $ "The color was " ++ show col ++ "."
(_:_) -> putStrLn $ "Error: parse not unique"