There is a program that interacts with the user by repeatedly overwriting the last line of text in stderr
. It is done as follows:
When It wants to overwrite a line s
with another line s'
, It pads s'
with spaces until it is at least as long as s
and prepends it with "carriage return" character:
n <- gets lastLineLength
let s'Padded | 0 < n = '\r': s' ++ replicate (n - length s') ' '
| otherwise = s'
hPutStr stderr s'Padded
This works just fine. (Though I personally have not tested it in circumstances other than the usual Linux terminal.)
I set about improving this program by replacing plain String
by a Doc
type from ansi-wl-pprint
so that I may paint the text in colours, in the same fashion as the recent GHC sports. A library such as this one may be overkill, as I only need to output a few lines at once, and without any indentation, but I wanted to give it a try for its abstract colouring facilities. However, I don't think this library (or any pretty printing library for that matter) would feature a function aimed at erasing previously printed Doc
s.
One solution I have in mind is rendering the Doc
to a String
and measuring its length. However, I will have to discount for colour codes; furthermore, this is overall an intrusion into the abstraction the library offers: specifically, I will have to rely on the assumption that the render I do manually will match the render implicitly done by hPutDoc
.
Should I forfeit the library altogether and keep dealing in String
s, manually throwing in ANSI escape sequences and carriage returns? Is there a nicer way to overwrite previous output? I welcome any advice.
ansi-wl-pprint
depends on ansi-terminal
, which has a clearLine
method and other utilities to move around and record positions in the console.
Under the hood, clearLine
sends a specific ANSI control sequence to erase the current line. There is also a control sequence to rewind the cursor to the beginning of the current line (or any line, for that matter). This is somewhat an obscure art, but you'd be surprised to see how many control sequences there are.
You can manipulate the control sequences manually. For example, if you putStr "\ESC[2K\ESC[0G"
, it should erase the current line and then put the cursor to the beginning of it − analogous to what your code is doing, but cleaner. But it would probably serve you best if you depend on ansi-terminal
and use the operations hClearLine
and hSetCursorColumn
defined there. If ou already indirectly depend on ansi-terminal
through ansi-wl-pprint
, it should not incur additional build time cost.