diff --git a/include/indicators/block_progress_bar.hpp b/include/indicators/block_progress_bar.hpp index fcffdaf..aa7047a 100644 --- a/include/indicators/block_progress_bar.hpp +++ b/include/indicators/block_progress_bar.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -12,8 +13,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -33,7 +34,7 @@ class BlockProgressBar { typename std::enable_if::type...>::value, void *>::type = nullptr> - explicit BlockProgressBar(Args &&... args) + explicit BlockProgressBar(Args &&...args) : settings_(details::get( option::ForegroundColor{Color::unspecified}, std::forward(args)...), details::get(option::BarWidth{100}, @@ -123,7 +124,7 @@ class BlockProgressBar { size_t current() { std::lock_guard lock{mutex_}; return (std::min)(static_cast(progress_), - size_t(get_value())); + size_t(get_value())); } bool is_completed() const { return get_value(); } @@ -133,6 +134,11 @@ class BlockProgressBar { print_progress(); } + size_t extra_wrapped_lines() { + std::lock_guard lock{mutex_}; + return extra_wrapped_lines_; + } + private: template auto get_value() -> decltype((details::get_value(std::declval()).value)) { @@ -140,8 +146,8 @@ class BlockProgressBar { } template - auto get_value() const - -> decltype((details::get_value(std::declval()).value)) { + auto + get_value() const -> decltype((details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } @@ -149,6 +155,7 @@ class BlockProgressBar { float progress_{0.0}; std::chrono::time_point start_time_point_; std::mutex mutex_; + size_t extra_wrapped_lines_{0}; template friend class MultiProgress; template friend class DynamicProgress; @@ -201,10 +208,9 @@ class BlockProgressBar { if (saved_start_time) { auto eta = std::chrono::nanoseconds( - progress_ > 0 - ? static_cast(std::ceil(float(elapsed.count()) * - max_progress / progress_)) - : 0); + progress_ > 0 ? static_cast( + std::ceil(float(elapsed.count()) * max_progress / progress_)) + : 0); auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); details::write_duration(os, remaining); } else { @@ -244,6 +250,10 @@ class BlockProgressBar { for (auto &style : get_value()) 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; @@ -267,17 +277,19 @@ class BlockProgressBar { const auto bar_width = get_value(); const auto end_length = get_value().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() = true; } diff --git a/include/indicators/cursor_control.hpp b/include/indicators/cursor_control.hpp index 641156f..0bb7d91 100644 --- a/include/indicators/cursor_control.hpp +++ b/include/indicators/cursor_control.hpp @@ -2,6 +2,8 @@ #ifndef INDICATORS_CURSOR_CONTROL #define INDICATORS_CURSOR_CONTROL +#include + #if defined(_MSC_VER) #if !defined(NOMINMAX) #define NOMINMAX @@ -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 \ No newline at end of file diff --git a/include/indicators/details/stream_helper.hpp b/include/indicators/details/stream_helper.hpp index 48d06b8..62e3503 100644 --- a/include/indicators/details/stream_helper.hpp +++ b/include/indicators/details/stream_helper.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -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 diff --git a/include/indicators/dynamic_progress.hpp b/include/indicators/dynamic_progress.hpp index 49d7d89..a062a90 100644 --- a/include/indicators/dynamic_progress.hpp +++ b/include/indicators/dynamic_progress.hpp @@ -2,13 +2,15 @@ #ifndef INDICATORS_DYNAMIC_PROGRESS #define INDICATORS_DYNAMIC_PROGRESS -#include -#include #include #include #include #include #include + +#include +#include +#include #include #include #include @@ -103,9 +105,19 @@ template class DynamicProgress { started_ = true; } else { // Don't hide any bars - if (started_) - move_up(static_cast(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"; } diff --git a/include/indicators/indeterminate_progress_bar.hpp b/include/indicators/indeterminate_progress_bar.hpp index f77dd5a..835204f 100644 --- a/include/indicators/indeterminate_progress_bar.hpp +++ b/include/indicators/indeterminate_progress_bar.hpp @@ -3,6 +3,7 @@ #define INDICATORS_INDETERMINATE_PROGRESS_BAR #include +#include #include #include @@ -138,6 +139,11 @@ class IndeterminateProgressBar { print_progress(); } + size_t extra_wrapped_lines() { + std::lock_guard lock{mutex_}; + return extra_wrapped_lines_; + } + private: template auto get_value() -> decltype((details::get_value(std::declval()).value)) { @@ -155,6 +161,7 @@ class IndeterminateProgressBar { Settings settings_; std::chrono::nanoseconds elapsed_; std::mutex mutex_; + size_t extra_wrapped_lines_{0}; template friend class MultiProgress; template friend class DynamicProgress; @@ -191,6 +198,10 @@ class IndeterminateProgressBar { for (auto &style : get_value()) 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; @@ -217,17 +228,18 @@ class IndeterminateProgressBar { const auto bar_width = get_value(); const auto end_length = get_value().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() && !from_multi_progress) // Don't std::endl if calling from MultiProgress os << termcolor::reset << std::endl; diff --git a/include/indicators/multi_progress.hpp b/include/indicators/multi_progress.hpp index 16a9dc4..a10b550 100644 --- a/include/indicators/multi_progress.hpp +++ b/include/indicators/multi_progress.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -65,9 +66,21 @@ template class MultiProgress { public: void print_progress() { std::lock_guard 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"; } diff --git a/include/indicators/progress_bar.hpp b/include/indicators/progress_bar.hpp index ce22957..4fe1aab 100644 --- a/include/indicators/progress_bar.hpp +++ b/include/indicators/progress_bar.hpp @@ -3,6 +3,7 @@ #define INDICATORS_PROGRESS_BAR #include +#include #include #include @@ -180,6 +181,11 @@ class ProgressBar { print_progress(); } + size_t extra_wrapped_lines() { + std::lock_guard lock{mutex_}; + return extra_wrapped_lines_; + } + private: template auto get_value() @@ -198,6 +204,7 @@ class ProgressBar { std::chrono::nanoseconds elapsed_; std::chrono::time_point start_time_point_; std::mutex mutex_; + size_t extra_wrapped_lines_{0}; template friend class MultiProgress; template friend class DynamicProgress; @@ -310,6 +317,10 @@ class ProgressBar { for (auto &style : get_value()) 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; @@ -336,17 +347,18 @@ class ProgressBar { const auto bar_width = get_value(); const auto end_length = get_value().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() = true;