Readline Hell

Posted on Sunday, 04 May 2008 at 16:24.

My GHC’s default getLine behaviour isn’t very pretty — pressing arrow keys or even backspace during input results in question marks and escape characters instead of cursor movement or deletion of characters. That’s why I decided to use readline for my MUD client, because readline handles these key presses nicely, comes preinstalled with GHC and has plenty of other features, such as a history. But boy, was it hard to get readline to do what I wanted!

The first problem was that readline wants to know exactly what the current prompt is. When communicating with a MUD, the MUD decides what the prompt is at any time, if any. If I didn’t tell readline explicitly about the prompt whenever it changed, it would delete the entire prompt whenever the user hit Control-U to remove input. This shouldn’t be hard to fix; it’s fairly easy to determine what part of the MUD’s output is the prompt. But in the Haskell wrapper library System.Console.Readline, there is no function that maps to rl_set_prompt. Instead, a prompt can only be specified at the readline call site, and that function blocks until the user has finished writing. In other words, it was impossible to change the prompt in between readline calls. It took me a while to discover that I could abuse rl_message instead of rl_set_prompt.

Then emptying the line buffer proved troublesome; simply calling rl_delete_text(0, n) would leave the user unable to type any new characters. After about three hours’ full-time debugging I found out this is because the cursor location (rl_point) isn’t automatically set to 0 when removing all text. In other words, the line buffer would be the empty string, but the cursor would still be (internally, not visibly) at, say, position 10.

Here is the final code for writing text to the terminal, preserving input the user hasn’t confirmed yet, and pushing it forward whenever new text is written: