Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

“readline” when there is output at beginning of line

I am using the readline (version 6.3, default [non-vi] mode, Ubuntu 14.04) 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, i.e. deleting 5 characters. 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.e. deleting 6+6==12. So you see:

Hello OK> 123456 <Ctrl+U>
Hell<caret here>

I need one of two possible solutions:

  1. 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.

I think I can sort of guess that for up to 5 characters typed it does up to 5 backspaces, but over that it chooses to do something else which messes up if it did not start at beginning of line?

I see GNU Readline: how to clear the input line?. Is this the same situation? The solutions seem pretty complex. Is it not possible to ask what column you are at when starting readline(), or to tell it not to try to be so clever at deleting and stick to only deleting as many characters as have actually been typed into it?

like image 501
JonBrave Avatar asked Dec 17 '16 12:12

JonBrave


1 Answers

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
like image 81
JonBrave Avatar answered Oct 30 '22 02:10

JonBrave