GNU Readline: how to clear the input line? - c

I use GNU Readline in the "select" fashion, by registering a callback function like so:
rl_callback_handler_install("", on_readline_input);
And then hooking up rl_callback_read_char as the callback for my select() loop for STDIN_FILENO. That's all pretty standard stuff, and works fine.
Now, my program asynchronously prints messages to the screen, sometimes interleaved with input from the user. A "clean" session would look like this:
user input
SERVER OUTPUT
SERVER OUTPUT
user input
SERVER OUTPUT
But what if the user is midway through a line when the server response arrives? Then it gets ugly:
user input
SERVER OUTPUT
user inSERVER OUTPUT
put
SERVER OUTPUT
I fixed this simply by printing a newline before the server output if the user had typed anything (this is easy to tell by checking rl_line_buffer), and then doing rl_forced_update_display() after printing the server output. Now it looks like this:
user input
SERVER OUTPUT
user in
SERVER OUTPUT
user input
SERVER OUTPUT
This is better, but still not perfect. The problem comes when the user typed an entire line but didn't yet press Enter--then it looks like this:
user input
SERVER OUTPUT
user input
SERVER OUTPUT
user input
SERVER OUTPUT
This is bad because it appears to the user that they typed three commands (three responses for three inputs is just as possible as three responses for two inputs, which is what actually happened).
A nasty hack (which works) is to do this:
user input
SERVER OUTPUT
user input - INCOMPLETE
SERVER OUTPUT
user input
SERVER OUTPUT
I figured I could improve this by printing backspace ('\b') characters instead of " - INCOMPLETE", but that doesn't seem to do anything at all on my terminal (gnome-terminal on Ubuntu Hardy). printf("ABC\b"); just prints ABC, for whatever reason.
So how can I erase the incomplete input line? Either by printing backspaces somehow (I can figure out how many to print--it's strlen(rl_line_buffer)), or by using some Readline facility I don't yet know about?

After quite a lot of hacking I was able to get this mechanism. I hope other people will find it useful. It does not even use select(), but I hope you will get the point.
#include <readline/readline.h>
#include <readline/history.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
const char const* prompt = "PROMPT> ";
void printlog(int c) {
char* saved_line;
int saved_point;
saved_point = rl_point;
saved_line = rl_copy_text(0, rl_end);
rl_set_prompt("");
rl_replace_line("", 0);
rl_redisplay();
printf("Message: %d\n", c);
rl_set_prompt(prompt);
rl_replace_line(saved_line, 0);
rl_point = saved_point;
rl_redisplay();
free(saved_line);
}
void handle_line(char* ch) {
printf("%s\n", ch);
add_history(ch);
}
int main() {
int c = 1;
printf("Start.\n");
rl_callback_handler_install(prompt, handle_line);
while (1) {
if (((++c) % 5) == 0) {
printlog(c);
}
usleep(10);
rl_callback_read_char();
}
rl_callback_handler_remove();
}

With spaces? Try to print "\b \b" for each character you want to "delete" rather than a single '\b'.
Edit
How it works
Suppose you have written "Hello, world!" to the display device and you want to replace "world!" with "Jim."
Hello, world!
^ /* active position */ /* now write "\b \b" */
/* '\b' moves the active position back;
// ' ' writes a space (erases the '!')
// and another '\b' to go back again */
Hello, world
^ /* active position */ /* now write "\b \b" again */
Hello, worl
^ /* active position */ /* now write "\b \b" 4 times ... */
Hello,
^ /* active position */ /* now write "Jim." */
Hello, Jim.
^ /* active position */
Portability
I'm not sure, but the Standard specifically describes the behaviour of '\b' and '\r' as has been described in answers to your question.
Section 5.2.2 Character display semantics
> 1 The active position is that location on a display device where the next character output by
> the fputc function would appear. The intent of writing a printing character (as defined
> by the isprint function) to a display device is to display a graphic representation of
> that character at the active position and then advance the active position to the next
> position on the current line. The direction of writing is locale-specific. If the active
> position is at the final position of a line (if there is one), the behavior of the display devic e
> is unspecified.
>
> 2 Alphabetic escape sequences representing nongraphic characters in the execution
> character set are intended to produce actions on display devices as follows:
> \a (alert) Produces an audible or visible alert without changing the active position.
> \b (backspace) Moves the active position to the previous position on the current line. If
> the active position is at the initial position of a line, the behavior of the display
> device is unspecified.
> \f ( form feed) Moves the active position to the initial position at the start of the next
> logical page.
> \n (new line) Moves the active position to the initial position of the next line.
> \r (carriage return) Moves the active position to the initial position of the current line.
> \t (horizontal tab) Moves the active position to the next horizontal tabulation position
> on the current line. If the active position is at or past the last defined horizontal
> tabulation position, the behavior of the display device is unspecified.
> \v (vertical tab) Moves the active position to the initial position of the next vertical
> tabulation position. If the active position is at or past the last defined vertical
> tabulation position, the behavior of the display device is unspecified.
>
> 3 Each of these escape sequences shall produce a unique implementation-defined value
> which can be stored in a single char object. The external representations in a text file
> need not be identical to the internal representations, and are outside the scope of this
> International Standard.

One thing you can do is to use \r to jump to the beginning of the line for the server output. Then you can use field width specifiers to right pad the output to the rest of the line. This will, in effect, overwrite whatever the user had already entered.
fprintf (stdout, "\r%-20s\n", "SERVER OUTPUT");
You may want to fflush(stdout) to ensure that the buffers are in a consistent state before you do that.

I tried to separate server output and user input with ncurses windows. Server output is simulated with a thread. The program run until You enter a line beginning with 'q'.
#include <unistd.h>
#include <curses.h>
#include <pthread.h>
WINDOW *top, *bottom;
int win_update( WINDOW *win, void *data ){
wprintw(win,"%s", (char*)data ); wrefresh(win);
return 0;
}
void *top_thread( void *data ){
char buff[1024];
int i=0;
while(1){
snprintf(buff, 1024, "SERVER OUTPUT: %i\n", i++ );
use_window( top, win_update, (void*)buff );
sleep(1);
}
return NULL;
}
int main(){
initscr();
int maxy, maxx;
getmaxyx( stdscr, maxy, maxx );
top = newwin(maxy-1,maxx,0,0);
wsetscrreg(top,0,maxy-1); idlok(top,1); scrollok(top,1);
pthread_t top_tid;
pthread_create(&top_tid, NULL, top_thread, NULL);
bottom = newwin(1,maxx,maxy-1,0);
char buff[1024], input[maxx];
do{
werase(bottom); wmove(bottom,0,0);
wprintw(bottom,"input> " ); wrefresh(bottom);
wgetnstr(bottom,input,sizeof(input));
snprintf(buff, 1024, "user input: '%s'\n", input );
use_window( top, win_update, (void*)buff );
}while( input[0] != 'q' );
endwin();
}

Do any of these functions help?
rl_reset_line_state()
rl_clear_message()
rl_delete_text()
rl_kill_text()
Also, can you mediate the server output - have the server output controlled so that it only appears when and where you want it to, rather than just sprawling over what the user is typing? For example, if your application is running in curses mode, could you have a split window with a line or two at the bottom in one sub-window reserved for user input and the rest of the output (server output and accepted user input) in a second sub-window above it?

This also seems to work:
rl_clear_visible_line();
printf(...);
rl_reset_line_state();
rl_redisplay();

Related

How backspace can actually delete the string in getchar() loop

#include <stdio.h>
#include <conio.h>
#define ENTER_KEY '\n'
#define NULL_TERMINATOR '\0'
int main()
{
char name[100], input;
int counter = 0;
while(input != ENTER_KEY)
{
input = getchar();
name[counter] = input;
counter++;
}
counter--;
name[counter] = NULL_TERMINATOR;
printf("%s", name);
return 0;
}
If I write something, it should continuously saved in the name Array. And the counter should go up on every character I enter. But if I press Backspace, it looks like it makes the counter decreased. Because for example if I write "abcdef" and press backspace 3 times and change that to "abcxyz", and then press Enter. It prints "abcxyz".
It depends on the console driver. On most systems (at least Unix-like in line mode and in Windows console), the program does not receive the characters at the moment they are typed but the system prepares a line (up to the newline character) and sends the full line to the program.
In that case, the backspace if often used to edit that console buffer, meaning that the characters erased are actually removed before being handed to the program. So if you type abcdef<backspace><backspace><backspace>xyz<Return> the program will receive the following string: "abcxyz\n".
Beware, in a GUI program or in fullscreen text mode program like emacs or vi, the system is in raw mode (Unix language) and each character is received when it is typed. In that case, the program has to manage the input and erase its own character array when it receives a <backspace>.

How to get the cursor position in a C program using termcap, without writing a character?

I would like to know how to get the cursor position (x, y) in my program, without writing anything on the screen neither tracking it all the time.
I found out a way to get its position with this function (I don't check the return of read, write, etc here to write a smaller code on this subject but I do it in my program):
void get_cursor_position(int *col, int *rows)
{
int a = 0;
int i = 0;
char buf[4];
write(1, "\033[6n", 4); // string asking for the cursor position
read(1, buf, 4);
while (buf[i])
{
if (buf[i] >= 48 && buf[i] <= 57)
{
if (a == 0)
*rows = atoi(&buf[i]) - 1;
else
*col = atoi(&buf[i]) - 1;
a++;
}
i++;
}
}
This function gives me the exact cursor position (*rows = y, *col = x), but it writes on the screen.
How can I get the cursor position without writing anything on the screen?
(If the cursor is on one of the printed characters, it will overwrite it.)
Should echo be toggled before and after sending the escape sequence?
This is a school project, so I only can use termcap, I can't use ncurses functions, the only allowed functions are tputs, tgoto, tgetstr, tgetnum, tgetflag.
There are several problems:
canonical mode is buffered (see below)
the read is done on the file-descriptor for standard output (that may happen to work — sometimes — but don't count on it)
the read does not read enough characters to get a typical response
the response would have two decimal integers, separated by semicolon ;
the response would have a final character (which would become an issue if the read actually asked for enough characters...)
Further reading:
General Terminal Interface The Single UNIX ® Specification, Version 2
In canonical mode input processing, terminal input is processed in units of lines. A line is delimited by a newline character (NL), an end-of-file character (EOF), or an end-of-line (EOL) character. See Special Characters for more information on EOF and EOL. This means that a read request will not return until an entire line has been typed or a signal has been received. Also, no matter how many bytes are requested in the read() call, at most one line will be returned. It is not, however, necessary to read a whole line at once; any number of bytes, even one, may be requested in a read() without losing information.
XTerm Control Sequences
CSI Ps n Device Status Report (DSR).
Ps = 5 -> Status Report.
Result ("OK") is CSI 0 n
Ps = 6 -> Report Cursor Position (CPR) [row;column].
Result is CSI r ; c R
That is, your program should be prepared to read Escape[ followed by two decimal integers (with no fixed limit on their length), and two other characters ; and R.
By the way, termcap by itself will do little for your solution. While ncurses has some relevant capabilities defined in the terminal database:
# u9 terminal enquire string (equiv. to ANSI/ECMA-48 DA)
# u8 terminal answerback description
# u7 cursor position request (equiv. to VT100/ANSI/ECMA-48 DSR 6)
# u6 cursor position report (equiv. to ANSI/ECMA-48 CPR)
few programs use those, and in any case you would find it difficult to use the cursor position report in a termcap application.

Delete the newline symbol

I have a calculator application, which enquires the user to input an arithmetic expression like 10 + 15, follow it with an = sign, and press Enter. The program should evaluate the expression and print the result.
However, the answer is on a new line, even though I want it on the same one like
10 + 15 = 25
I tried to use ungetch as well but it didn't work.
So how can I remove that newline character to obtain the upper result?
If your terminal supports them, you can use terminal escape codes to move the cursor.
#include <stdio.h>
int main ( ) {
char input[200] = "";
int ch = 0;
int count = 0;
printf ( "\033[2J");//clear screen and move cursor to upper left corner
printf ( "\033[8;H");//move cursor to line 8
printf("Enter equation\n");//\n advances to line 9
while ( ( ch = getchar ( )) != '\n' && ch != EOF) {
input[count] = ch;//store input for processing
count++;//count characters
}
printf ( "\033[9;%dH", count + 1);//move cursor to row 9 col count + 1
printf ( "answer here\n");
return 0;
}
If you are reading a line of input from the console, the user can freely edit the text they type in until they press Enter. When they do press Enter, the cursor immediately moves to the start of the line, possibly scrolling the screen buffer up in the process.
To move the cursor back to the previous line, you will need to use a "curses" library that allows you to send cursor movement commands to move the cursor back up to the previous line, and then to the end of their input. This could be complicated if they used tab characters and/or typed in more than one line of input.
See "ncurses" or, on Windows, PDCurses.
If you're using a terminal it's not easy because it's typically waiting on newlines before it does anything. Have a look here...
setvbuf not able to make stdin unbuffered
If you are doing this on a socket or some other stream that you completely controle then you need to use unbuffered input. Have a look here
Buffered and Unbuffered inputs in C
Note, this will make your calculator a bit harder to write. Alternatively you could print the whole line ie input and answer in the terminal.

How to display * for Each Input Character for a Password in C - making the character invisible [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Hide password input on terminal
I want to achieve this:
$Insert Pass:
User types: a (a immediately disappears & '*' takes its position on the shell)
On the Shell : a
Intermediate O/P: *
User types: b (b immediately disappears & '*' takes its position on the shell)
On the Shell : *b
Intermediate O/P: **
User types: c (c immediately disappears & '*' takes its position on the shell)
On the Shell : **c
Final O/P : ***
I have tried the following approach:
#include <stdio.h>
#include <string.h>
#define SIZE 20
int main()
{
char array[SIZE];
int counter = 0;
memset(array,'0',SIZE);
while ((array[counter]!='\n')&&(counter<=SIZE-2))
{
array[counter++] = getchar();
printf("\b\b");
printf ("*");
}
printf("\nPassword: %s\n", array);
return 0;
}
But I am unable to achieve the expected Output. This Code cannot make the User-Typed Characters invisible & display a '*' immediately.
Can someone please guide me on this.
Thanks.
Best Regards,
Sandeep Singh
Your approach doesn't work; even if you can overwrite the character, I could run your command in a tool like script(1) and see the output.
The correct solution is to switch the terminal from cooked to raw mode and turn echo off.
The first change will make your program see each character as it is typed (otherwise, the shell will collect one line of input and send it to your process after the user has pressed enter).
The second change prevents the shell/terminal from printing what the user types.
See this article how to do this.
The problem is getchar() waits until the user presses the enter key and then returns the whole string at once. What you want is a method to return immediately after a character has been typed. While there is no portable way to do this, for Windows you can #include <conio.h> in your application and replace array[counter++] = getchar() with array[counter++] = _getch() and it should work.

Erase the current printed console line

How can I erase the current printed console line in C? I am working on a Linux system. For example -
printf("hello");
printf("bye");
I want to print bye on the same line in place of hello.
You can use VT100 escape codes. Most terminals, including xterm, are VT100 aware. For erasing a line, this is ^[[2K. In C this gives:
printf("\33[2K\r");
Some worthwhile subtleties...
\33[2K erases the entire line your cursor is currently on
\033[A moves your cursor up one line, but in the same column i.e. not to the start of the line
\r brings your cursor to the beginning of the line (r is for carriage return N.B. carriage returns do not include a newline so cursor remains on the same line) but does not erase anything
In xterm specifically, I tried the replies mentioned above and the only way I found to erase the line and start again at the beginning is the sequence (from the comment above posted by #Stephan202 as well as #vlp and #mantal) \33[2K\r
On an implementation note, to get it to work properly for example in a countdown scenario since I wasn't using a new line character '\n'
at the end of each fprintf(), so I had to fflush() the stream each time (to give you some context, I started xterm using a fork on a linux machine without redirecting stdout, I was just writing to the buffered FILE pointer fdfile with a non-blocking file descriptor I had sitting on the pseudo terminal address which in my case was /dev/pts/21):
fprintf(fdfile, "\33[2K\rT minus %d seconds...", i);
fflush(fdfile);
Note that I used both the \33[2K sequence to erase the line followed by the \r carriage return sequence to reposition the cursor at the beginning of the line. I had to fflush() after each fprintf() because I don't have a new line character at the end '\n'. The same result without needing fflush() would require the additional sequence to go up a line:
fprintf(fdfile, "\033[A\33[2K\rT minus %d seconds...\n", i);
Note that if you have something on the line immediately above the line you want to write on, it will get over-written with the first fprintf(). You would have to leave an extra line above to allow for the first movement up one line:
i = 3;
fprintf(fdfile, "\nText to keep\n");
fprintf(fdfile, "Text to erase****************************\n");
while(i > 0) { // 3 second countdown
fprintf(fdfile, "\033[A\33[2KT\rT minus %d seconds...\n", i);
i--;
sleep(1);
}
You can use a \r (carriage return) to return the cursor to the beginning of the line:
printf("hello");
printf("\rbye");
This will print bye on the same line. It won't erase the existing characters though, and because bye is shorter than hello, you will end up with byelo. To erase it you can make your new print longer to overwrite the extra characters:
printf("hello");
printf("\rbye ");
Or, first erase it with a few spaces, then print your new string:
printf("hello");
printf("\r ");
printf("\rbye");
That will print hello, then go to the beginning of the line and overwrite it with spaces, then go back to the beginning again and print bye.
You could delete the line using \b
printf("hello");
int i;
for (i=0; i<80; i++)
{
printf("\b");
}
printf("bye");
Usually when you have a '\r' at the end of the string, only carriage return is printed without any newline. If you have the following:
printf("fooooo\r");
printf("bar");
the output will be:
barooo
One thing I can suggest (maybe a workaround) is to have a NULL terminated fixed size string that is initialized to all space characters, ending in a '\r' (every time before printing), and then use strcpy to copy your string into it (without the newline), so every subsequent print will overwrite the previous string. Something like this:
char str[MAX_LENGTH];
// init str to all spaces, NULL terminated with character as '\r'
strcpy(str, my_string); // copy my_string into str
str[strlen(my_string)] = ' '; // erase null termination char
str[MAX_LENGTH - 1] = '\r';
printf(str);
You can do error checking so that my_string is always atleast one less in length than str, but you get the basic idea.
i iterates through char array words. j keeps track of word length. "\b \b" erases word while backing over line.
#include<stdio.h>
int main()
{
int i = 0, j = 0;
char words[] = "Hello Bye";
while(words[i]!='\0')
{
if(words[i] != ' ') {
printf("%c", words[i]);
fflush(stdout);
}
else {
//system("ping -n 1 127.0.0.1>NUL"); //For Microsoft OS
system("sleep 0.25");
while(j-->0) {
printf("\b \b");
}
}
i++;
j++;
}
printf("\n");
return 0;
}
This script is hardcoded for your example.
#include <stdio.h>
int main ()
{
//write some input
fputs("hello\n",stdout);
//wait one second to change line above
sleep(1);
//remove line
fputs("\033[A\033[2K",stdout);
rewind(stdout);
//write new line
fputs("bye\n",stdout);
return 0;
}
Click here for source.
under windows 10 one can use VT100 style by activating the VT100 mode in the current console to use escape sequences as follow :
#include <windows.h>
#include <iostream>
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#define DISABLE_NEWLINE_AUTO_RETURN 0x0008
int main(){
// enabling VT100 style in current console
DWORD l_mode;
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(hStdout,&l_mode)
SetConsoleMode( hStdout, l_mode |
ENABLE_VIRTUAL_TERMINAL_PROCESSING |
DISABLE_NEWLINE_AUTO_RETURN );
// create a waiting loop with changing text every seconds
while(true) {
// erase current line and go to line begining
std::cout << "\x1B[2K\r";
std::cout << "wait a second .";
Sleep(1);
std::cout << "\x1B[2K\r";
std::cout << "wait a second ..";
Sleep(1);
std::cout << "\x1B[2K\r";
std::cout << "wait a second ...";
Sleep(1);
std::cout << "\x1B[2K\r";
std::cout << "wait a second ....";
}
}
see following link : windows VT100
there is a simple trick you can work here but it need preparation before you print, you have to put what ever you wants to print in a variable and then print so you will know the length to remove the string.here is an example.
#include <iostream>
#include <string> //actually this thing is not nessasory in tdm-gcc
using namespace std;
int main(){
//create string variable
string str="Starting count";
//loop for printing numbers
for(int i =0;i<=50000;i++){
//get previous string length and clear it from screen with backspace charactor
cout << string(str.length(),'\b');
//create string line
str="Starting count " +to_string(i);
//print the new line in same spot
cout <<str ;
}
}
Just found this old thread, looking for some kind of escape sequence to blank the actual line.
It's quite funny no one came to the idea (or I have missed it) that printf returns the number of characters written. So just print '\r' + as many blank characters as printf returned and you will exactly blank the previuosly written text.
int BlankBytes(int Bytes)
{
char strBlankStr[16];
sprintf(strBlankStr, "\r%%%is\r", Bytes);
printf(strBlankStr,"");
return 0;
}
int main(void)
{
int iBytesWritten;
double lfSomeDouble = 150.0;
iBytesWritten = printf("test text %lf", lfSomeDouble);
BlankBytes(iBytesWritten);
return 0;
}
As I cant use VT100, it seems I have to stick with that solution
echo -e "hello\c" ;sleep 1 ; echo -e "\rbye "
What the above command will do :
It will print hello and the cursor will remain at "o" (using \c)
Then it will wait for 1 sec (sleep 1)
Then it will replace hello with bye.(using \r)
NOTE : Using ";", We can run multiple command in a single go.
For me, this code, work well for serial console window with arduino
on Tera Term VT console:
SEROUT.print("\e[A\r\n\e[2K");
SEROUT.print('>');
I use '>' because on my console command i type command after '>'
use this function to clear n lines in C++
void clear_line(int n) {
std::string line_up = "\x1b[A";
std::string line_clear = "\33[2K\r";
for (int i = 0; i < n; ++i)
std::cout << line_up << line_clear << std::flush;
}
Others have already answered OP's question. Here is an answer for those wondering why carriage return behaves the way it does on their Linux machine -
The behavior of the carriage return character seems to be platform-dependent.
From section '5.2.2 Character display semantics' of the C11 standard:
\r (carriage return) Moves the active position to the initial position
of the current line.
From section '3.86 Carriage-Return Character (< carriage-return>)' of the POSIX standard (https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html):
A character that in the output stream indicates that printing should
start at the beginning of the same physical line in which the
carriage-return occurred. It is the character designated by '\r' in
the C language. It is unspecified whether this character is the exact
sequence transmitted to an output device by the system to accomplish
the movement to the beginning of the line.
It does not state whether carriage return is supposed to erase (=> populate with NUL characters) the entire line or not. My guess is that it is NOT supposed to erase.
However, on my Linux machine (tried on both x86_64 and ARM32), what I observed is that the carriage return character moved the cursor to the beginning of the current line and also populated the line with '\0' characters (NUL characters). In order to notice those NUL characters, you might have to call the write system call directly from your code instead of calling via glibc printf.
Let's take the following code snippet as an example:
printf("hello");
printf("\rbye");
Building and running this on beaglebone black (32-bit ARM) bash terminal:
ubuntu#arm:~$ ./a.out
byeubuntu#arm:~$
strace output on write syscall:
bye) = 9 9hello
+++ exited with 4 +++

Resources