progressbar: Do not trim messages to terminal width
Print full messages.
This commit is contained in:
parent
1081b928e6
commit
5f5b8719eb
|
@ -97,6 +97,11 @@ public:
|
|||
/// remove the last message
|
||||
void pop_message();
|
||||
const std::vector<Message> & get_messages() const noexcept;
|
||||
const std::string & get_message_prefix() const noexcept;
|
||||
|
||||
/// Calculate number of lines occupied by messages when printed on terminal of the given width.
|
||||
/// Takes new lines and wide utf characters into account.
|
||||
std::size_t calculate_messages_terminal_lines(std::size_t terminal_width);
|
||||
|
||||
// auto-finish feature; turn off if you want to handle state manually
|
||||
bool get_auto_finish() const noexcept;
|
||||
|
@ -116,6 +121,7 @@ public:
|
|||
|
||||
protected:
|
||||
virtual void to_stream(std::ostream & stream) = 0;
|
||||
std::size_t get_message_padding(std::size_t terminal_width, std::string_view message, std::size_t message_index);
|
||||
|
||||
private:
|
||||
class LIBDNF_CLI_LOCAL Impl;
|
||||
|
|
|
@ -217,56 +217,43 @@ void DownloadProgressBar::to_stream(std::ostream & stream) {
|
|||
stream << tty::reset;
|
||||
}
|
||||
|
||||
for (auto & msg : get_messages()) {
|
||||
auto message_type = msg.first;
|
||||
auto message = msg.second;
|
||||
std::size_t message_index = 0;
|
||||
for (const auto & [message_type, message] : get_messages()) {
|
||||
const auto & prefix = get_message_prefix();
|
||||
|
||||
const auto & prefix = ">>> ";
|
||||
const auto prefix_width = libdnf5::cli::utils::utf8::width(prefix);
|
||||
stream << std::endl << prefix;
|
||||
|
||||
stream << std::endl;
|
||||
// print only part of the prefix that fits the terminal width
|
||||
stream << libdnf5::cli::utils::utf8::substr_width(prefix, 0, terminal_width);
|
||||
|
||||
if (prefix_width < terminal_width) {
|
||||
// only proceed if there is at least some space for the message
|
||||
color_used = false;
|
||||
if (tty::is_coloring_enabled()) {
|
||||
// color the message in interactive terminal
|
||||
switch (message_type) {
|
||||
case MessageType::INFO:
|
||||
break;
|
||||
case MessageType::SUCCESS:
|
||||
stream << tty::green;
|
||||
color_used = true;
|
||||
break;
|
||||
case MessageType::WARNING:
|
||||
stream << tty::yellow;
|
||||
color_used = true;
|
||||
break;
|
||||
case MessageType::ERROR:
|
||||
stream << tty::red;
|
||||
color_used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding to fully fill the terminal_width, this is because MultiProgressBar
|
||||
// overrides its own messages, it doesn't clear the lines.
|
||||
// If the message is short some leftover characters could be still present after it.
|
||||
const auto message_width = libdnf5::cli::utils::utf8::width(message);
|
||||
const auto space_available = terminal_width - prefix_width;
|
||||
if (message_width < space_available) {
|
||||
message.append(space_available - message_width, ' ');
|
||||
}
|
||||
|
||||
// print only part of the message that fits the terminal width
|
||||
stream << libdnf5::cli::utils::utf8::substr_width(message, 0, space_available);
|
||||
|
||||
if (color_used) {
|
||||
stream << tty::reset;
|
||||
color_used = false;
|
||||
if (tty::is_coloring_enabled()) {
|
||||
// color the message in interactive terminal
|
||||
switch (message_type) {
|
||||
case MessageType::INFO:
|
||||
break;
|
||||
case MessageType::SUCCESS:
|
||||
stream << tty::green;
|
||||
color_used = true;
|
||||
break;
|
||||
case MessageType::WARNING:
|
||||
stream << tty::yellow;
|
||||
color_used = true;
|
||||
break;
|
||||
case MessageType::ERROR:
|
||||
stream << tty::red;
|
||||
color_used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stream << message;
|
||||
// Add padding to fully fill the terminal_width, this is because MultiProgressBar
|
||||
// overrides its own messages, it doesn't clear the lines.
|
||||
// If the message is short some leftover characters could be still present after it.
|
||||
stream << std::string(get_message_padding(terminal_width, prefix + message, message_index), ' ');
|
||||
|
||||
if (color_used) {
|
||||
stream << tty::reset;
|
||||
}
|
||||
++message_index;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,7 @@ std::size_t MultiProgressBar::get_total_num_of_bars() const noexcept {
|
|||
|
||||
std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
|
||||
const bool is_interactive{tty::is_interactive()};
|
||||
auto terminal_width = static_cast<std::size_t>(tty::get_width());
|
||||
|
||||
// We'll buffer the output text to a single string and print it all at once.
|
||||
// This is to avoid multiple writes to the terminal, which can cause flickering.
|
||||
|
@ -170,7 +171,7 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
|
|||
text_buffer << *bar;
|
||||
text_buffer << std::endl;
|
||||
num_of_lines_permanent++;
|
||||
num_of_lines_permanent += bar->get_messages().size();
|
||||
num_of_lines_permanent += bar->calculate_messages_terminal_lines(terminal_width);
|
||||
mbar.p_impl->bars_done.push_back(bar);
|
||||
// TODO(dmach): use iterator
|
||||
mbar.p_impl->bars_todo.erase(mbar.p_impl->bars_todo.begin() + static_cast<int>(i));
|
||||
|
@ -198,7 +199,7 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
|
|||
text_buffer << *bar;
|
||||
mbar.p_impl->line_printed = true;
|
||||
mbar.p_impl->num_of_lines_to_clear++;
|
||||
mbar.p_impl->num_of_lines_to_clear += bar->get_messages().size();
|
||||
mbar.p_impl->num_of_lines_to_clear += bar->calculate_messages_terminal_lines(terminal_width);
|
||||
}
|
||||
|
||||
// then print the "total" progress bar
|
||||
|
@ -227,8 +228,7 @@ std::ostream & operator<<(std::ostream & stream, MultiProgressBar & mbar) {
|
|||
text_buffer << std::endl;
|
||||
}
|
||||
// print divider
|
||||
int terminal_width = tty::get_width();
|
||||
text_buffer << std::string(static_cast<std::size_t>(terminal_width), '-');
|
||||
text_buffer << std::string(terminal_width, '-');
|
||||
text_buffer << std::endl;
|
||||
|
||||
// print Total progress bar
|
||||
|
|
|
@ -20,14 +20,25 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
#include "libdnf5-cli/progressbar/progress_bar.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
|
||||
namespace libdnf5::cli::progressbar {
|
||||
|
||||
class ProgressBar::Impl {
|
||||
public:
|
||||
struct MessageMetrics {
|
||||
std::size_t terminal_width;
|
||||
std::size_t lines;
|
||||
std::size_t padding;
|
||||
};
|
||||
|
||||
explicit Impl(int64_t total_ticks) : total_ticks{total_ticks} {}
|
||||
Impl(int64_t total_ticks, const std::string & description) : total_ticks{total_ticks}, description{description} {}
|
||||
|
||||
const MessageMetrics & get_message_metrics(
|
||||
std::size_t terminal_width, std::string_view message, std::size_t message_index) noexcept;
|
||||
|
||||
// ticks
|
||||
int64_t ticks = -1;
|
||||
int64_t total_ticks = -1;
|
||||
|
@ -41,6 +52,10 @@ public:
|
|||
|
||||
// messages
|
||||
std::vector<ProgressBar::Message> messages;
|
||||
std::string message_prefix{">>> "};
|
||||
// cache for number of terminal lines occupied by the message and padding
|
||||
// needed to fill the last line completely.
|
||||
std::vector<std::optional<MessageMetrics>> messages_metrics_cache;
|
||||
|
||||
ProgressBarState state = ProgressBarState::READY;
|
||||
|
||||
|
@ -60,6 +75,49 @@ public:
|
|||
int64_t current_speed_window_ticks = 0;
|
||||
};
|
||||
|
||||
const ProgressBar::Impl::MessageMetrics & ProgressBar::Impl::get_message_metrics(
|
||||
std::size_t terminal_width, std::string_view message, std::size_t message_index) noexcept {
|
||||
auto & cached_mm = messages_metrics_cache.at(message_index);
|
||||
if (cached_mm && (*cached_mm).terminal_width == terminal_width) {
|
||||
// the value is cached and valid for current terminal_with
|
||||
return *cached_mm;
|
||||
}
|
||||
|
||||
std::size_t msg_lines = 1;
|
||||
std::size_t current_column = 0;
|
||||
std::mbstate_t mbstate = std::mbstate_t();
|
||||
|
||||
while (!message.empty()) {
|
||||
if (message.front() == '\n') {
|
||||
++msg_lines;
|
||||
current_column = 0;
|
||||
message.remove_prefix(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate the display width of the character
|
||||
wchar_t wc;
|
||||
auto bytes_consumed = std::mbrtowc(&wc, message.data(), message.size(), &mbstate);
|
||||
if (bytes_consumed <= 0) {
|
||||
break;
|
||||
}
|
||||
auto char_width = static_cast<std::size_t>(wcwidth(wc));
|
||||
|
||||
// If the character doesn't fit, wrap to the next line
|
||||
if (current_column + char_width > terminal_width) {
|
||||
++msg_lines;
|
||||
current_column = char_width;
|
||||
} else {
|
||||
current_column += char_width;
|
||||
}
|
||||
|
||||
message.remove_prefix(static_cast<std::size_t>(bytes_consumed));
|
||||
}
|
||||
messages_metrics_cache[message_index].emplace(
|
||||
MessageMetrics{terminal_width, msg_lines, terminal_width - current_column});
|
||||
return messages_metrics_cache.at(message_index).value();
|
||||
}
|
||||
|
||||
|
||||
ProgressBar::~ProgressBar() = default;
|
||||
|
||||
|
@ -125,12 +183,33 @@ void ProgressBar::set_description(const std::string & value) {
|
|||
|
||||
void ProgressBar::add_message(MessageType type, const std::string & message) {
|
||||
p_impl->messages.emplace_back(type, message);
|
||||
p_impl->messages_metrics_cache.emplace_back(std::nullopt);
|
||||
}
|
||||
|
||||
const std::vector<ProgressBar::Message> & ProgressBar::get_messages() const noexcept {
|
||||
return p_impl->messages;
|
||||
}
|
||||
|
||||
const std::string & ProgressBar::get_message_prefix() const noexcept {
|
||||
return p_impl->message_prefix;
|
||||
}
|
||||
|
||||
std::size_t ProgressBar::get_message_padding(
|
||||
std::size_t terminal_width, std::string_view message, std::size_t message_index) {
|
||||
return p_impl->get_message_metrics(terminal_width, message, message_index).padding;
|
||||
}
|
||||
|
||||
std::size_t ProgressBar::calculate_messages_terminal_lines(std::size_t terminal_width) {
|
||||
std::size_t num_lines = 0;
|
||||
std::size_t message_index = 0;
|
||||
for (const auto & [msg_type, msg] : get_messages()) {
|
||||
std::string full_msg = p_impl->message_prefix + msg;
|
||||
num_lines += p_impl->get_message_metrics(terminal_width, full_msg, message_index).lines;
|
||||
++message_index;
|
||||
}
|
||||
return num_lines;
|
||||
}
|
||||
|
||||
bool ProgressBar::get_auto_finish() const noexcept {
|
||||
return p_impl->auto_finish;
|
||||
}
|
||||
|
@ -294,6 +373,7 @@ void ProgressBar::set_total_ticks(int64_t value) {
|
|||
void ProgressBar::pop_message() {
|
||||
if (!p_impl->messages.empty()) {
|
||||
p_impl->messages.pop_back();
|
||||
p_impl->messages_metrics_cache.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue