Skip to content

Commit

Permalink
Fix progress bar display when terminal width is too small
Browse files Browse the repository at this point in the history
When the terminal width is too small, we run into problems as mentioned
in p-ranav#132. This PR fixes the
problem by keeping track of number of wrapped lines printed by progress
bar and erasing those extra wrapped lines before printing progress for
next iteration.

For multi_progress bar and dynamic_progress bar, we need to move cursor
to start before each iteration by summing up number of wrapped lines for
each bar. Since each bar's `print_progress` expects cursor to be on last
printed line, we move cursor down by number of wrapped lines before
printing progress for each bar.

I have not yet handled case when `dynamic_progress` option
`hide_bar_when_complete` is set.

Fixes Issue p-ranav#132
  • Loading branch information
vawale committed Jun 27, 2024
1 parent 222382c commit 925a5bd
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 27 deletions.
38 changes: 25 additions & 13 deletions include/indicators/block_progress_bar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <indicators/color.hpp>
#include <indicators/details/stream_helper.hpp>
#include <indicators/cursor_control.hpp>

#include <algorithm>
#include <atomic>
Expand All @@ -12,8 +13,8 @@
#include <indicators/terminal_size.hpp>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <tuple>
Expand All @@ -33,7 +34,7 @@ class BlockProgressBar {
typename std::enable_if<details::are_settings_from_tuple<
Settings, typename std::decay<Args>::type...>::value,
void *>::type = nullptr>
explicit BlockProgressBar(Args &&... args)
explicit BlockProgressBar(Args &&...args)
: settings_(details::get<details::ProgressBarOption::foreground_color>(
option::ForegroundColor{Color::unspecified}, std::forward<Args>(args)...),
details::get<details::ProgressBarOption::bar_width>(option::BarWidth{100},
Expand Down Expand Up @@ -123,7 +124,7 @@ class BlockProgressBar {
size_t current() {
std::lock_guard<std::mutex> lock{mutex_};
return (std::min)(static_cast<size_t>(progress_),
size_t(get_value<details::ProgressBarOption::max_progress>()));
size_t(get_value<details::ProgressBarOption::max_progress>()));
}

bool is_completed() const { return get_value<details::ProgressBarOption::completed>(); }
Expand All @@ -133,22 +134,28 @@ class BlockProgressBar {
print_progress();
}

size_t extra_wrapped_lines() {
std::lock_guard<std::mutex> lock{mutex_};
return extra_wrapped_lines_;
}

private:
template <details::ProgressBarOption id>
auto get_value() -> decltype((details::get_value<id>(std::declval<Settings &>()).value)) {
return details::get_value<id>(settings_).value;
}

template <details::ProgressBarOption id>
auto get_value() const
-> decltype((details::get_value<id>(std::declval<const Settings &>()).value)) {
auto
get_value() const -> decltype((details::get_value<id>(std::declval<const Settings &>()).value)) {
return details::get_value<id>(settings_).value;
}

Settings settings_;
float progress_{0.0};
std::chrono::time_point<std::chrono::high_resolution_clock> start_time_point_;
std::mutex mutex_;
size_t extra_wrapped_lines_{0};

template <typename Indicator, size_t count> friend class MultiProgress;
template <typename Indicator> friend class DynamicProgress;
Expand Down Expand Up @@ -201,10 +208,9 @@ class BlockProgressBar {

if (saved_start_time) {
auto eta = std::chrono::nanoseconds(
progress_ > 0
? static_cast<long long>(std::ceil(float(elapsed.count()) *
max_progress / progress_))
: 0);
progress_ > 0 ? static_cast<long long>(
std::ceil(float(elapsed.count()) * max_progress / progress_))
: 0);
auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta);
details::write_duration(os, remaining);
} else {
Expand Down Expand Up @@ -244,6 +250,10 @@ class BlockProgressBar {
for (auto &style : get_value<details::ProgressBarOption::font_styles>())
details::set_font_style(os, style);

// Need to erase previously written text across multiple lines to solve
// issue https://github.com/p-ranav/indicators/issues/132
erase_lines(extra_wrapped_lines_);

const auto prefix_pair = get_prefix_text();
const auto prefix_text = prefix_pair.first;
const auto prefix_length = prefix_pair.second;
Expand All @@ -267,17 +277,19 @@ class BlockProgressBar {
const auto bar_width = get_value<details::ProgressBarOption::bar_width>();
const auto end_length = get_value<details::ProgressBarOption::end>().size();
const auto terminal_width = terminal_size().second;
// prefix + bar_width + postfix should be <= terminal_width
const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length);
const auto number_of_characters =
prefix_length + start_length + bar_width + end_length + postfix_length;
// If prefix + bar_width + postfix > terminal_width, lines will be wrapped
const int remaining = terminal_width - number_of_characters;
if (prefix_length == -1 || postfix_length == -1) {
os << "\r";
} else if (remaining > 0) {
os << std::string(remaining, ' ') << "\r";
} else if (remaining < 0) {
// Do nothing. Maybe in the future truncate postfix with ...
}
os.flush();

extra_wrapped_lines_ = details::extra_wrapped_lines(number_of_characters);

if (progress_ > max_progress) {
get_value<details::ProgressBarOption::completed>() = true;
}
Expand Down
9 changes: 9 additions & 0 deletions include/indicators/cursor_control.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#ifndef INDICATORS_CURSOR_CONTROL
#define INDICATORS_CURSOR_CONTROL

#include <indicators/cursor_movement.hpp>

#if defined(_MSC_VER)
#if !defined(NOMINMAX)
#define NOMINMAX
Expand Down Expand Up @@ -61,6 +63,13 @@ static inline void erase_line() {

#endif

static inline void erase_lines(size_t lines) {
for (size_t i = 0; i < lines; ++i) {
move_up(1);
erase_line();
}
}

} // namespace indicators

#endif
12 changes: 12 additions & 0 deletions include/indicators/details/stream_helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <indicators/display_width.hpp>
#include <indicators/setting.hpp>
#include <indicators/termcolor.hpp>
#include <indicators/terminal_size.hpp>

#include <algorithm>
#include <chrono>
Expand Down Expand Up @@ -217,6 +218,17 @@ class IndeterminateProgressScaleWriter {
std::string lead;
};

inline size_t extra_wrapped_lines(size_t number_of_characters)
{
const auto number_of_columns = indicators::terminal_width();
const auto extra_lines = number_of_characters / number_of_columns;
// cursor does not wrap when writing to the last column
if ((extra_lines > 0) && (number_of_characters % number_of_columns) == 0) {
return extra_lines-1;
}
return extra_lines;
}

} // namespace details
} // namespace indicators

Expand Down
20 changes: 16 additions & 4 deletions include/indicators/dynamic_progress.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
#ifndef INDICATORS_DYNAMIC_PROGRESS
#define INDICATORS_DYNAMIC_PROGRESS

#include <atomic>
#include <functional>
#include <indicators/color.hpp>
#include <indicators/setting.hpp>
#include <indicators/cursor_control.hpp>
#include <indicators/cursor_movement.hpp>
#include <indicators/details/stream_helper.hpp>

#include <atomic>
#include <functional>
#include <numeric>
#include <iostream>
#include <mutex>
#include <vector>
Expand Down Expand Up @@ -103,9 +105,19 @@ template <typename Indicator> class DynamicProgress {
started_ = true;
} else {
// Don't hide any bars
if (started_)
move_up(static_cast<int>(total_count_));
if (started_) {
// move all the way up to start of first progress bar
const auto wrapped_lines = std::accumulate(begin(bars_), end(bars_), 0, [](size_t acc, auto &bar) {
return acc + bar.get().extra_wrapped_lines();
});
move_up(total_count_ + wrapped_lines);
}
for (auto &bar : bars_) {
auto wrapped_line = bar.get().extra_wrapped_lines();
if (wrapped_line > 0) {
// for each bar before calling `print_progress`, cursor needs to be on last line that bar printed
move_down(wrapped_line);
}
bar.get().print_progress(true);
std::cout << "\n";
}
Expand Down
20 changes: 16 additions & 4 deletions include/indicators/indeterminate_progress_bar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#define INDICATORS_INDETERMINATE_PROGRESS_BAR

#include <indicators/details/stream_helper.hpp>
#include <indicators/cursor_control.hpp>

#include <algorithm>
#include <atomic>
Expand Down Expand Up @@ -138,6 +139,11 @@ class IndeterminateProgressBar {
print_progress();
}

size_t extra_wrapped_lines() {
std::lock_guard<std::mutex> lock{mutex_};
return extra_wrapped_lines_;
}

private:
template <details::ProgressBarOption id>
auto get_value() -> decltype((details::get_value<id>(std::declval<Settings &>()).value)) {
Expand All @@ -155,6 +161,7 @@ class IndeterminateProgressBar {
Settings settings_;
std::chrono::nanoseconds elapsed_;
std::mutex mutex_;
size_t extra_wrapped_lines_{0};

template <typename Indicator, size_t count> friend class MultiProgress;
template <typename Indicator> friend class DynamicProgress;
Expand Down Expand Up @@ -191,6 +198,10 @@ class IndeterminateProgressBar {

for (auto &style : get_value<details::ProgressBarOption::font_styles>())
details::set_font_style(os, style);

// Need to erase previously written text across multiple lines to solve
// issue https://github.com/p-ranav/indicators/issues/132
erase_lines(extra_wrapped_lines_);

const auto prefix_pair = get_prefix_text();
const auto prefix_text = prefix_pair.first;
Expand All @@ -217,17 +228,18 @@ class IndeterminateProgressBar {
const auto bar_width = get_value<details::ProgressBarOption::bar_width>();
const auto end_length = get_value<details::ProgressBarOption::end>().size();
const auto terminal_width = terminal_size().second;
// prefix + bar_width + postfix should be <= terminal_width
const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length);
const auto number_of_characters = prefix_length + start_length + bar_width + end_length + postfix_length;
// If prefix + bar_width + postfix > terminal_width, lines will be wrapped
const int remaining = terminal_width - number_of_characters;
if (prefix_length == -1 || postfix_length == -1) {
os << "\r";
} else if (remaining > 0) {
os << std::string(remaining, ' ') << "\r";
} else if (remaining < 0) {
// Do nothing. Maybe in the future truncate postfix with ...
}
os.flush();

extra_wrapped_lines_ = details::extra_wrapped_lines(number_of_characters);

if (get_value<details::ProgressBarOption::completed>() &&
!from_multi_progress) // Don't std::endl if calling from MultiProgress
os << termcolor::reset << std::endl;
Expand Down
17 changes: 15 additions & 2 deletions include/indicators/multi_progress.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <iostream>
#include <mutex>
#include <vector>
#include <numeric>

#include <indicators/color.hpp>
#include <indicators/cursor_movement.hpp>
Expand Down Expand Up @@ -65,9 +66,21 @@ template <typename Indicator, size_t count> class MultiProgress {
public:
void print_progress() {
std::lock_guard<std::mutex> lock{mutex_};
if (started_)
move_up(count);

if (started_) {
// move all the way up to start of first progress bar
const auto wrapped_lines = std::accumulate(begin(bars_), end(bars_), 0, [](size_t acc, auto &bar) {
return acc + bar.get().extra_wrapped_lines();
});
move_up(count + wrapped_lines);
}

for (auto &bar : bars_) {
auto wrapped_line = bar.get().extra_wrapped_lines();
if (wrapped_line > 0) {
// for each bar before calling `print_progress`, cursor needs to be on last line that bar printed
move_down(wrapped_line);
}
bar.get().print_progress(true);
std::cout << "\n";
}
Expand Down
20 changes: 16 additions & 4 deletions include/indicators/progress_bar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#define INDICATORS_PROGRESS_BAR

#include <indicators/details/stream_helper.hpp>
#include <indicators/cursor_control.hpp>

#include <algorithm>
#include <atomic>
Expand Down Expand Up @@ -180,6 +181,11 @@ class ProgressBar {
print_progress();
}

size_t extra_wrapped_lines() {
std::lock_guard<std::mutex> lock{mutex_};
return extra_wrapped_lines_;
}

private:
template <details::ProgressBarOption id>
auto get_value()
Expand All @@ -198,6 +204,7 @@ class ProgressBar {
std::chrono::nanoseconds elapsed_;
std::chrono::time_point<std::chrono::high_resolution_clock> start_time_point_;
std::mutex mutex_;
size_t extra_wrapped_lines_{0};

template <typename Indicator, size_t count> friend class MultiProgress;
template <typename Indicator> friend class DynamicProgress;
Expand Down Expand Up @@ -310,6 +317,10 @@ class ProgressBar {
for (auto &style : get_value<details::ProgressBarOption::font_styles>())
details::set_font_style(os, style);

// Need to erase previously written text across multiple lines to solve
// issue https://github.com/p-ranav/indicators/issues/132
erase_lines(extra_wrapped_lines_);

const auto prefix_pair = get_prefix_text();
const auto prefix_text = prefix_pair.first;
const auto prefix_length = prefix_pair.second;
Expand All @@ -336,17 +347,18 @@ class ProgressBar {
const auto bar_width = get_value<details::ProgressBarOption::bar_width>();
const auto end_length = get_value<details::ProgressBarOption::end>().size();
const auto terminal_width = terminal_size().second;
// prefix + bar_width + postfix should be <= terminal_width
const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length);
const auto number_of_characters = prefix_length + start_length + bar_width + end_length + postfix_length;
// If prefix + bar_width + postfix > terminal_width, lines will be wrapped
const int remaining = terminal_width - number_of_characters;
if (prefix_length == -1 || postfix_length == -1) {
os << "\r";
} else if (remaining > 0) {
os << std::string(remaining, ' ') << "\r";
} else if (remaining < 0) {
// Do nothing. Maybe in the future truncate postfix with ...
}
os.flush();

extra_wrapped_lines_ = details::extra_wrapped_lines(number_of_characters);

if ((type == ProgressType::incremental && progress_ >= max_progress) ||
(type == ProgressType::decremental && progress_ <= min_progress)) {
get_value<details::ProgressBarOption::completed>() = true;
Expand Down

0 comments on commit 925a5bd

Please sign in to comment.