1

I am using the (excellent) readline (version 6.3, default [non-vi] mode) library from within my own program, running in a Terminal window (on a PC). There is a problem when there is previous output not terminated by newline when readline() is called.

#include <stdio.h>
#include <readline/readline.h>

void main(void)
{
  // Previous output from some other part of application
  // which *may* have output stuff *not* terminated with a '\n'
  printf("Hello ");
  fflush(stdout);

  char *in = readline("OK> ");
}

So the line looks like:

Hello OK> <caret here>

If you type a small number of characters (up to 5?) and then, say, Ctrl+U (may be others) to delete your input so far it all seems well --- readline() moves the caret back to just after its own prompt. However, try typing, say:

123456 <Ctrl+U>

Now it deletes back into the Hello, leaving just Hell on the line, followed by the caret.

I need one of two possible solutions:

  1. This does look like a bug, now that I have realised it depends on how many characters are typed on the line where it goes wrong. Any fix/workaround?

  2. Alternatively, is there a readline library call I could make which would tell me what position/column the caret is at before I invoke readline()? Then at least I could recognise the fact that that I am at the end of an existing line and output a \n so as to position myself at the start of a new line first.

P.S. Is this an OK place to ask about readline programming under Ubuntu or should I be posting at stackoverflow.com?

JonBrave
  • 699

2 Answers2

0

[Stackoverflow would be a more appropriate place for such a programming question]

This is not a bug but the expected behaviour. readline is not aware of what was written on the terminal earlier and at what position it is writing. Think "basic serial terminal". Moreover other background processes (which your program is not aware of) may also write to the terminal.

So, readline assumes it starts to write at the beginning of the terminal line. When you Ctrl-U (unix-line-discard), readline goes back where it thinks you started to type characters, i.e. just after its prompt. Your prompt "OK> " is four characters long, so readline put the caret at the 5th place and erase the line, leaving just "Hell".

The workaround could be to skip a line before calling readline, or to begin your prompt with a CR character (i.e. \r), which would force the prompt at the beginning of the line, overwriting "Hello" (but a longer text would only be partially overwritten).

[update]

As for why sometimes Ctrl-U erases just the last typed characters, and sometimes it erases (almost) the whole line, it is a readline optimisation.

readline can emit two different character sequences to erase the whole input:

  • either: n × <BS> (backspace) + <control sequence to erase the whole line> (e.g. ANSI <ESC> [ K), where n is the number of characters typed so far.
  • or: <CR> + m × <control sequence to move the cursor to the right> (e.g. ANSI <ESC> [ C) + <control sequence to erase the whole line>, where m is the length of the prompt.

readline choose the shortest, which depends on the number of characters typed wrt the length of your prompt.

xhienne
  • 391
0

It turns out that readline cannot recognise if it is not starting at column #1, and thereby stop itself from messing up the previous output on the line.

The only way to deal with this is to recognise the starting column ourselves, and move to the start of the next line down if the current position is not column #1. Then it will always starts from the left-most column, without outputting an unnecessary newline when it is already at column #1.

We can do this for the standard "Terminal" because it understands an ANSI escape sequence to query the current row & column of the terminal. The query is sent via characters to stdout and the response is read via characters the terminal inserts into stdin. We must put the terminal into "raw" input mode so that the response characters can be read immediately and will not be echoed.

So here is the code:

rl_prep_terminal(1);       // put the terminal into "raw" mode
fputs("\033[6n", stdout);  // <ESC>[6n is ANSI sequence to query terminal position
int row, col;              // terminal will reply with <ESC>[<row>;<col>R
fscanf(stdin, "\033[%d;%dR", &row, &col);
rl_deprep_terminal();      // restore terminal "cooked" mode
if (col > 1)               // if beyond the first column...
  fputc('\n', stdout);     // output '\n' to move to start of next line

in = readline(prompt);     // now we can invoke readline() with our prompt
JonBrave
  • 699