How to rollback lines from cout?
Asked Answered
L

6

17

I'm coding a task monitoring, which updates tasks' progress using cout. I'd like to display one task progress per line, therefore I have to rollback several lines of the console.

I insist on "several" because \b does the job for one line, but does not erase \n between lines.

I tried std::cout.seekp(std::cout.tellp() - str.length()); but tellp() returns -1 (failure).

Labbe answered 18/7, 2010 at 20:10 Comment(3)
You can't. cout doesn't represent the console. It represents an output stream. That means you can write to it, but you can't do anything about what's already been written. cout is for printing output to whichever output device the platform uses (for example, but not necessarily, a console window). If you need to manipulate the console specifically, you have to use an OS-specific library which knows about the console window.Mediant
Why do you even want to erase the list of completed tasks? Just print one task progress item per line, and it'll play nicer if someone runs your program and pipes stdout to a log file.Crifasi
I know, but there will be A LOT of lines, I don't want to drown the user under infos, just give him status, and percentage progress of each task I got running.Sheppard
F
28

You can do cout << '\r'; to jump to the beginning of the current line, but moving upwards is system-specific. For Unix, see man termcap and man terminfo (and search for cursor_up). On ANSI-compatible terminals (such as most modern terminals available on Unix), this works to move up: cout << "\e[A";.

Don't try seeking in cout, it's unseekable most of the time (except when redirected to a file).

As mentioned in other answers, using the ncurses (or slang) library provides a good abstraction for terminal I/O on Unix.

Instead of filling with spaces (which is error-prone, because not every terminal is 80 characters wide), you can do \r + clr_eol: std::cout << "\r\e[K" << std::flush.

Froth answered 18/7, 2010 at 20:17 Comment(3)
Thanks, I gave up monitoring several lines, and used \r - fill 79 spaces - \r instead.Sheppard
I needed << std::flush too to get the display interactively.Rickettsia
Instead of filling with spaces (which is error-prone, because not every terminal is 80 characters wide), you can do \r + clr_eol: std::cout << "\r\e[K" << std::flush. Updated my answer.Froth
B
8

Use an output formatting library such as ncurses if you can; this simplifies terminal manipulation significantly.

Beata answered 18/7, 2010 at 20:15 Comment(0)
T
5

Neither C nor C++ define anything like that. You need explicit terminal manipulation. On Unix you can use curses. Have no idea what's there for Windows.

Thermograph answered 18/7, 2010 at 20:16 Comment(0)
J
2

I know this is an old post, but the accepted doesn't cover cases where cout is piped to a program or file and this is the top of my google searches. The following will handle both piped and non-piped stdout with slightly different behavior.

#include <iostream>
#include <functional>
#include <stdio.h>

#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#define _isatty isatty
#define _fileno fileno
#endif

const std::function<void(const size_t&)> progress_printer(_isatty(_fileno(stdout)) == 1 ?
    [](const size_t& i) {
        std::cout << "\rNumber " << i << std::flush;
    } :
    [](const size_t& i) {
        static std::ios::off_type last(-1);
        if(last != -1)
            std::cout.seekp(last, std::ios::beg);
        last = std::cout.tellp();
        std::cout << "Number " << i << std::endl;
    }
);

This is untested on windows, but should work. What it does is detect if the file descriptor or is a tty. If it is then it just writes '\r' if the pos hasn't changed since last time it printed or a newline. If it isn't a newline, it seeks to the last place it was after it printed.

It behaves differently for files than for tty. For a file, if something outputs to the stream between prints then it can overwrite some or all of what was written even after newlines. For ttys it just overwrites the chars at the beginning of the current line.

Jesusa answered 27/7, 2016 at 17:5 Comment(1)
old but still goldIncoherence
P
1

You can use first system(" "); for you can use \e[A (Dev-C++) or \u001B[A (Visual Studio)

#include <iostream>
using namespace std;
int main()
{
    system(" ");
    string Input;
    do
    {
        cout << "[#][\e[s";
        cin >> Input;
        cout << "[\e[u" << Input << "]"<<endl;

    } while (2==2);
    return 0;
}

enter image description here

Potamic answered 6/10, 2019 at 1:28 Comment(1)
What is the purpose of system(" ") in this program? What would be different if it was removed?Froth
T
0

Hope it helps ;) [It should work on Linux.]

// "\e[0K" Clear line from cursor to the end
cout << "\e[A\r\e[0K"<<what_you_want<<endl;
Towrey answered 31/5, 2018 at 2:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.