How to delete (backspace) unsubmitted input from the command line after calling cout?

I’m trying to create a program that waits either until the user inputs a certain keyword or until a certain amount of time has elapsed.

I’ve succeeded in that, but now, if I start to type in an entry, wait until cout flushes, and then attempt to delete the unsubmitted input, instead of moving the cursor back and overwriting, the console prints ^R and goes to a new line.

Is there a way I can fix that?

Here’s my code. I’m using Visual Studio Code on Mac OS 10.13.6:

#include<iostream>
#include<chrono>
#include<thread>
#include<string>

void cinThread(std::string &T, std::string target)
{
    while(T != target)
        std::cin >> T;

    return;
}

void pauseRun()
{
    std::chrono::steady_clock::time_point t = std::chrono::steady_clock::now();
    while(std::chrono::steady_clock::now() - t < std::chrono::milliseconds(1000));

    return;
}

int main(void)
{
    std::cout << "Enter 'quit' or wait 30 seconds to quit: \n30 " << std::flush;
    std::string input{" "};
    std::thread th(cinThread, std::ref(input), "quit");
    th.detach();

    int timeLeft = 30;
    std::chrono::steady_clock::time_point t = std::chrono::steady_clock::now();

    while(input != "quit" && timeLeft > 0)
    {
        if(input != "quit" && input != " ")
        {
            std::cout << "\n\n\nEnter 'quit' or wait 30 seconds to quit: \n30 " << std::flush;
            timeLeft = 30;
            t = std::chrono::steady_clock::now();
            input = " ";
        }
        else if(input == "quit")
        {
            std::cout << "Quitting... goodbye! " << std::endl;
            pauseRun();
            return 0;
        }
        else if(std::chrono::steady_clock::now() - t >= std::chrono::milliseconds(1000))
        {
            timeLeft--;
            std::cout << "\x1B[s\r" << std::to_string(timeLeft);
            if(timeLeft < 10)
                std::cout << " ";
            
            std::cout << "\t\x1B[u" << std::flush;
            t = std::chrono::steady_clock::now();
        }
    }

    if(timeLeft == 0)
        std::cout << "\nSession timed out. Goodbye! " << std::endl;

    pauseRun();

    return 0;
}
Last edited on
what happens in a simple program with your tools:
int main()
{
string s;
cin >> s; //type something and then try to backspace or whatever you were trying to do above
}

if this does the same thing, your console delete interface is not working correctly or not supported. I havent done a lot of mac code since osx but ive seen a few posts where its console is weird at random times.
if it works properly, your threading above is likely scrambling the streams somehow, I don't see where, but the threads share the same cin / cout and may somehow get it into a bad state trying to run concurrently. You could try a mutex around the cins ?
Last edited on
I’ve already done some testing. The problem only occurs if I type something on the command line, wait for

  timeLeft--;
            std::cout << "\x1B[s\r" << std::to_string(timeLeft);
            if(timeLeft < 10)
                std::cout << " ";
            
            std::cout << "\t\x1B[u" << std::flush;
            t = std::chrono::steady_clock::now();

to run at least once, and THEN try deleting.

It’s honestly easier to show than to tell. Is there a way for me to submit video?

Also, I should note that the unsubmitted text IS being successfully deleted, it’s just not looking right on the console.
Last edited on
I wrote an even simpler program which proved what I suspected, it has nothing to do with threads. I was hoping that moving the cursor would allow the program to handle backspaces better.

The end location of the cursor appears to be irrelevant.

#include<iostream>
#include<chrono>

void pauseRun(int time)
{
    std::chrono::steady_clock::time_point t = std::chrono::steady_clock::now();
    while(std::chrono::steady_clock::now() - t < std::chrono::milliseconds(time));
    return;
}

int main(void)
{
    std::cout << "Type something, but don't press enter. " << std::endl;

    pauseRun(5000);

    std::cout << "\nNow press backspace. Do you get ^R?" << std::endl;

    pauseRun(5000);

    std::cout << "Let's try something different. \nPress enter to continue" << std::endl;

    while(std::cin.get() != '\n');

    std::cout << "This time, type 'abcd' in the command line, then press backspace once, but \ndo not enter it: \t" << std::flush;
    pauseRun(5000);

    std::cout << "\x1B[s\rNOW PRESS BACKSPACE: \x1B[u" << std::flush;

    pauseRun(10000);
    std::cout << "Press enter to conclude this exercise. " << std::endl;

    while(std::cin.get() != '\n');
    std::cout << "Thank you. Goodbye! " << std::endl;
    pauseRun(1000);

    return 0;

}
Last edited on
I suspected the same, that it was not the threading.
All I can offer at the moment is that I see this in unix sometimes when a keyboard command sends a control sequence and the console reads the key press as ctrl+something instead of handling the action. Some of those are keys (like the arrow keys) and some are control commands (oops, tried to paste with standard ctrl+v, not supported, get rubbish chars in console).
You could try trapping the ^R sequence and when you see it, purge it from the buffer. But that seems like the last resort approach. I honestly do not know the proper approach.

you said delete: did you mean backspace, or the del key?
Ive found that backspace is almost always supported, delete, maybe not.
I meant backspace. Can you explain what you mean by trapping the ^R sequence and purging it from the buffer?
I did some digging... my best guess is that it’s related to ^R meaning “search backwards in history.”
you may be able to search the buffer with peek or something, if you see those letters, delete them with ignore or whatever it is? There are tools to manipulate it, but I haven't done a ton of this.. not sure exactly what steps you need there.
I’m pretty sure that won’t work. peek() actually blocks until enter is pressed.

I’m trying to delete something before it gets submitted (i.e. fixing a typo).

Here’s an updated version of the program with output.

#include<iostream>
#include<chrono>
#include<string>

void pauseRun(int time)
{
    std::chrono::steady_clock::time_point t = std::chrono::steady_clock::now();
    while(std::chrono::steady_clock::now() - t < std::chrono::milliseconds(time));
    return;
}

int main(void)
{
    std::cout << "Type something, but don't press enter. " << std::endl;

    pauseRun(5000);

    std::cout << "\nNow press backspace. Do you get ^R?" << std::endl;

    pauseRun(5000);

    std::cout << "Let's try something different. \nPress enter to continue" << std::endl;

    while(std::cin.get() != '\n');

    std::cout << "This time, type 'abcd' in the command line, then press backspace once, but \ndo not enter it: \t\t" << std::flush;
    pauseRun(5000);

    std::cout << "\x1B[s\rNOW PRESS BACKSPACE AGAIN: \x1B[u" << std::flush;

    pauseRun(3000);

    std::string remainder;    
    std::cout << "Press enter to conclude this exercise. " << std::endl;

    std::cin >> remainder;

    std::cout << "You entered: \"" << remainder << "\"\nThank you. Goodbye! \n\n\n" << std::endl;
    pauseRun(1000);

    return 0;

}

---------------------------------------

Output:

Type something, but don't press enter.
start
Now press backspace. Do you get ^R?
^R
starLet's try something different.
Press enter to continue

This time, type 'abcd' in the command line, then press backspace once, but
NOW PRESS BACKSPACE AGAIN:      abc^R
abPress enter to conclude this exercise.

You entered: "ab"
Thank you. Goodbye!
Streams are a generic i/o mechanism. They're not suited to the input with timeout thing that you're doing.

I see you're using Windows, so take a look at this:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/kbhit?view=vs-2019

You can test in fhere's input and then do the read if there's some, or and check the time if there isn't.

On a Mac? You can use select() with STDIN_FILENO to check if a read is available. It's best wrap all that stuff into your own kbhit() function, so the code will work in the same way.
Last edited on
Since you are on nix, add the gnu getline library to your program. The terminal is probably already using it on your behalf, but either way it will make your life easier. It is very customizable in code.
the gnu getline library
The what now?
I’m actually using a Mac. But I’d like it to be platform-non-specific.
The what now?

I'm pretty sure @Duthomhas means GNU Readline, which is a fine library.
https://tiswww.case.edu/php/chet/readline/rltop.html

Readline is portable, IINM.
It's not portable to Windows unless you're willing to depend upon Cygwin.
Last edited on
Thanks. All I could find was references for a readline() function.
Topic archived. No new replies allowed.