Home > Blockchain >  How to manipulate stdout?
How to manipulate stdout?

Time:12-29

For example, when using GitHub CLI, they prompt you like so:

Do something? (Y/n)

and if you were to input n and press 'Enter', then that output would change to:

Do something? No
'Next line's content'

I would like to do something similar in my program.

In my use case, it would look like:

Row 1: 1 2 3
Row 2: 4 5 6
Row 3: 7 8 9
Row 4:

where the user inputs the integers, presses 'Enter', and is now on Row 4: .

Upon the user pressing 'Enter', I would like it to transform into:

Row 1: 1 2 3
Row 2: 4 5 6
Row 3: 7 8 9
Row 4: N/A

or, preferably:

Row 1: 1 2 3
Row 2: 4 5 6
Row 3: 7 8 9

but I am not sure how to do this, since the user hitting 'Enter' would put std::out at the new line, and if I were to print out N/A, it would look like:

Row 1: 1 2 3
Row 2: 4 5 6
Row 3: 7 8 9
Row 4:
N/A

CodePudding user response:

Terminal applications do this by sending special control characters to the terminal (this is an extremely old school API!). Libraries like ncurses and external tools like tput can make the arcane incantations for you, but it's not too hard to do yourself.

In this case, once the user presses Enter, you want to move the cursor back up to the start of the previous line, clear the rest of the line, and write what you want instead.

#define ESC_PREV_LINE "\e[F"
#define ESC_CLEAR_TO_END "\e[K"

std::cerr << "Row 4: " << std::flush; // traditionally, interactive prompts appear on stderr! this makes it easy to separate the "real" output of a program from the interactive part to e.g. save in a file
// read input...
if(need_to_rewrite_input) {
  std::cerr << ESC_PREV_LINE ESC_CLEAR_TO_END "Row 4: 7 8 9\n";
}

You may want to check at the start of the program that cerr actually refers to a terminal, and either use a fallback behavior or just die if it's not.

CodePudding user response:

One option is to use a library like ncurses. That is particularly relevant if, for example, you want to overwrite output on multiple lines preceding the cursor.

If you're looking for something more basic (as in, only affect output on the last line, and without much error checking, and without relying on escape codes that are often device specific) you can do something like

const std::string yesno = "(Y/n)"
char input;
std::cout << "Do something? " << yesno << " ";
std::cin >> input;
if (std::toupper(input) == 'Y')
{
    for (std::size_t i = 0; i < yesno.length()   2;   i)
       std::cout << "\b \b";
    std::cout << 'Y';
    std::cout << std::endl;
}
// code that produces next line of output

The loop backs up an overwrites characters on the last line, one at a time. The magic number 2 in the loop comes from the need to overwrite the space character and the single character entered by the user.

The downside of the above is that it ASSUMES the user enters exactly one character before hitting the enter key. There is no way to prevent the user doing something different (e.g. a user may happily enter YYYY before hitting the enter key).

To deal with that, one can do

If you want to relax that assumption the following is possible

const std::string yesno = "(Y/n)"
std::string input;
std::cout << "Do something? " << yesno << " ";
std::getline(std::cin, input);
if (input.length() > 0 && std::toupper(input[0]) == 'Y')
{
    for (std::size_t i = 0; i < yesno.length()   input.length()   1;   i)
       std::cout << "\b \b";
    std::cout << 'Y';
    std::cout << std::endl;
}
// code that produces next line of output

This will cope with the user entering YNYN before the enter key, by checking only the first character entered, and ignoring (and overwriting) the rest.

Both of the above do assume that outputting a space character will overwrite the character at the cursor position). But default settings of most modern terminals or consoles (hardware device or emulator in a windowing system) are consistent with that assumption. There are some old devices (largely obsolete, and now rarely seen outside historical displays or museums) for which that assumption is not true [and ncurses will often not detect, let alone properly handle, such devices either].

The other assumption made is that the terminal width exceeds the length of user input. So, if the terminal/window width is 80 characters, it is assumed the user will not enter 80 or more characters before hitting the enter key.

  •  Tags:  
  • c
  • Related