Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix progress bar display when terminal width is too small #133

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
erase_line();
move_up(1);
}
}

} // namespace indicators

#endif
2 changes: 1 addition & 1 deletion include/indicators/cursor_movement.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ static inline void move(int x, int y) {
}

static inline void move_up(int lines) { move(0, -lines); }
static inline void move_down(int lines) { move(0, -lines); }
static inline void move_down(int lines) { move(0, lines); }
static inline void move_right(int cols) { move(cols, 0); }
static inline void move_left(int cols) { move(-cols, 0); }

Expand Down
16 changes: 16 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,21 @@ class IndeterminateProgressScaleWriter {
std::string lead;
};

inline size_t extra_wrapped_lines(size_t number_of_characters)
{
const auto number_of_columns = indicators::terminal_width();
if (number_of_columns == 0) {
return 0;
}

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