Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I "refresh" the last line of output in terminal, if I want to use a pretty-printing library to add colours?

There is a program that interacts with the user by repeatedly overwriting the last line of text in stderr. It is done as follows:

  • There is a state monad that remembers the length of the last line printed.
  • 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 Docs.

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 Strings, manually throwing in ANSI escape sequences and carriage returns? Is there a nicer way to overwrite previous output? I welcome any advice.

like image 634
Ignat Insarov Avatar asked Feb 25 '18 16:02

Ignat Insarov


1 Answers

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.

like image 84
Li-yao Xia Avatar answered Oct 14 '22 16:10

Li-yao Xia