Skip to content

Commit 925a5bd

Browse files
committed
Fix progress bar display when terminal width is too small
When the terminal width is too small, we run into problems as mentioned in #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 #132
1 parent 222382c commit 925a5bd

File tree

7 files changed

+109
-27
lines changed

7 files changed

+109
-27
lines changed

include/indicators/block_progress_bar.hpp

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <indicators/color.hpp>
66
#include <indicators/details/stream_helper.hpp>
7+
#include <indicators/cursor_control.hpp>
78

89
#include <algorithm>
910
#include <atomic>
@@ -12,8 +13,8 @@
1213
#include <indicators/terminal_size.hpp>
1314
#include <iomanip>
1415
#include <iostream>
15-
#include <sstream>
1616
#include <mutex>
17+
#include <sstream>
1718
#include <string>
1819
#include <thread>
1920
#include <tuple>
@@ -33,7 +34,7 @@ class BlockProgressBar {
3334
typename std::enable_if<details::are_settings_from_tuple<
3435
Settings, typename std::decay<Args>::type...>::value,
3536
void *>::type = nullptr>
36-
explicit BlockProgressBar(Args &&... args)
37+
explicit BlockProgressBar(Args &&...args)
3738
: settings_(details::get<details::ProgressBarOption::foreground_color>(
3839
option::ForegroundColor{Color::unspecified}, std::forward<Args>(args)...),
3940
details::get<details::ProgressBarOption::bar_width>(option::BarWidth{100},
@@ -123,7 +124,7 @@ class BlockProgressBar {
123124
size_t current() {
124125
std::lock_guard<std::mutex> lock{mutex_};
125126
return (std::min)(static_cast<size_t>(progress_),
126-
size_t(get_value<details::ProgressBarOption::max_progress>()));
127+
size_t(get_value<details::ProgressBarOption::max_progress>()));
127128
}
128129

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

137+
size_t extra_wrapped_lines() {
138+
std::lock_guard<std::mutex> lock{mutex_};
139+
return extra_wrapped_lines_;
140+
}
141+
136142
private:
137143
template <details::ProgressBarOption id>
138144
auto get_value() -> decltype((details::get_value<id>(std::declval<Settings &>()).value)) {
139145
return details::get_value<id>(settings_).value;
140146
}
141147

142148
template <details::ProgressBarOption id>
143-
auto get_value() const
144-
-> decltype((details::get_value<id>(std::declval<const Settings &>()).value)) {
149+
auto
150+
get_value() const -> decltype((details::get_value<id>(std::declval<const Settings &>()).value)) {
145151
return details::get_value<id>(settings_).value;
146152
}
147153

148154
Settings settings_;
149155
float progress_{0.0};
150156
std::chrono::time_point<std::chrono::high_resolution_clock> start_time_point_;
151157
std::mutex mutex_;
158+
size_t extra_wrapped_lines_{0};
152159

153160
template <typename Indicator, size_t count> friend class MultiProgress;
154161
template <typename Indicator> friend class DynamicProgress;
@@ -201,10 +208,9 @@ class BlockProgressBar {
201208

202209
if (saved_start_time) {
203210
auto eta = std::chrono::nanoseconds(
204-
progress_ > 0
205-
? static_cast<long long>(std::ceil(float(elapsed.count()) *
206-
max_progress / progress_))
207-
: 0);
211+
progress_ > 0 ? static_cast<long long>(
212+
std::ceil(float(elapsed.count()) * max_progress / progress_))
213+
: 0);
208214
auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta);
209215
details::write_duration(os, remaining);
210216
} else {
@@ -244,6 +250,10 @@ class BlockProgressBar {
244250
for (auto &style : get_value<details::ProgressBarOption::font_styles>())
245251
details::set_font_style(os, style);
246252

253+
// Need to erase previously written text across multiple lines to solve
254+
// issue https://github.com/p-ranav/indicators/issues/132
255+
erase_lines(extra_wrapped_lines_);
256+
247257
const auto prefix_pair = get_prefix_text();
248258
const auto prefix_text = prefix_pair.first;
249259
const auto prefix_length = prefix_pair.second;
@@ -267,17 +277,19 @@ class BlockProgressBar {
267277
const auto bar_width = get_value<details::ProgressBarOption::bar_width>();
268278
const auto end_length = get_value<details::ProgressBarOption::end>().size();
269279
const auto terminal_width = terminal_size().second;
270-
// prefix + bar_width + postfix should be <= terminal_width
271-
const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length);
280+
const auto number_of_characters =
281+
prefix_length + start_length + bar_width + end_length + postfix_length;
282+
// If prefix + bar_width + postfix > terminal_width, lines will be wrapped
283+
const int remaining = terminal_width - number_of_characters;
272284
if (prefix_length == -1 || postfix_length == -1) {
273285
os << "\r";
274286
} else if (remaining > 0) {
275287
os << std::string(remaining, ' ') << "\r";
276-
} else if (remaining < 0) {
277-
// Do nothing. Maybe in the future truncate postfix with ...
278288
}
279289
os.flush();
280290

291+
extra_wrapped_lines_ = details::extra_wrapped_lines(number_of_characters);
292+
281293
if (progress_ > max_progress) {
282294
get_value<details::ProgressBarOption::completed>() = true;
283295
}

include/indicators/cursor_control.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#ifndef INDICATORS_CURSOR_CONTROL
33
#define INDICATORS_CURSOR_CONTROL
44

5+
#include <indicators/cursor_movement.hpp>
6+
57
#if defined(_MSC_VER)
68
#if !defined(NOMINMAX)
79
#define NOMINMAX
@@ -61,6 +63,13 @@ static inline void erase_line() {
6163

6264
#endif
6365

66+
static inline void erase_lines(size_t lines) {
67+
for (size_t i = 0; i < lines; ++i) {
68+
move_up(1);
69+
erase_line();
70+
}
71+
}
72+
6473
} // namespace indicators
6574

6675
#endif

include/indicators/details/stream_helper.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <indicators/display_width.hpp>
66
#include <indicators/setting.hpp>
77
#include <indicators/termcolor.hpp>
8+
#include <indicators/terminal_size.hpp>
89

910
#include <algorithm>
1011
#include <chrono>
@@ -217,6 +218,17 @@ class IndeterminateProgressScaleWriter {
217218
std::string lead;
218219
};
219220

221+
inline size_t extra_wrapped_lines(size_t number_of_characters)
222+
{
223+
const auto number_of_columns = indicators::terminal_width();
224+
const auto extra_lines = number_of_characters / number_of_columns;
225+
// cursor does not wrap when writing to the last column
226+
if ((extra_lines > 0) && (number_of_characters % number_of_columns) == 0) {
227+
return extra_lines-1;
228+
}
229+
return extra_lines;
230+
}
231+
220232
} // namespace details
221233
} // namespace indicators
222234

include/indicators/dynamic_progress.hpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
#ifndef INDICATORS_DYNAMIC_PROGRESS
33
#define INDICATORS_DYNAMIC_PROGRESS
44

5-
#include <atomic>
6-
#include <functional>
75
#include <indicators/color.hpp>
86
#include <indicators/setting.hpp>
97
#include <indicators/cursor_control.hpp>
108
#include <indicators/cursor_movement.hpp>
119
#include <indicators/details/stream_helper.hpp>
10+
11+
#include <atomic>
12+
#include <functional>
13+
#include <numeric>
1214
#include <iostream>
1315
#include <mutex>
1416
#include <vector>
@@ -103,9 +105,19 @@ template <typename Indicator> class DynamicProgress {
103105
started_ = true;
104106
} else {
105107
// Don't hide any bars
106-
if (started_)
107-
move_up(static_cast<int>(total_count_));
108+
if (started_) {
109+
// move all the way up to start of first progress bar
110+
const auto wrapped_lines = std::accumulate(begin(bars_), end(bars_), 0, [](size_t acc, auto &bar) {
111+
return acc + bar.get().extra_wrapped_lines();
112+
});
113+
move_up(total_count_ + wrapped_lines);
114+
}
108115
for (auto &bar : bars_) {
116+
auto wrapped_line = bar.get().extra_wrapped_lines();
117+
if (wrapped_line > 0) {
118+
// for each bar before calling `print_progress`, cursor needs to be on last line that bar printed
119+
move_down(wrapped_line);
120+
}
109121
bar.get().print_progress(true);
110122
std::cout << "\n";
111123
}

include/indicators/indeterminate_progress_bar.hpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#define INDICATORS_INDETERMINATE_PROGRESS_BAR
44

55
#include <indicators/details/stream_helper.hpp>
6+
#include <indicators/cursor_control.hpp>
67

78
#include <algorithm>
89
#include <atomic>
@@ -138,6 +139,11 @@ class IndeterminateProgressBar {
138139
print_progress();
139140
}
140141

142+
size_t extra_wrapped_lines() {
143+
std::lock_guard<std::mutex> lock{mutex_};
144+
return extra_wrapped_lines_;
145+
}
146+
141147
private:
142148
template <details::ProgressBarOption id>
143149
auto get_value() -> decltype((details::get_value<id>(std::declval<Settings &>()).value)) {
@@ -155,6 +161,7 @@ class IndeterminateProgressBar {
155161
Settings settings_;
156162
std::chrono::nanoseconds elapsed_;
157163
std::mutex mutex_;
164+
size_t extra_wrapped_lines_{0};
158165

159166
template <typename Indicator, size_t count> friend class MultiProgress;
160167
template <typename Indicator> friend class DynamicProgress;
@@ -191,6 +198,10 @@ class IndeterminateProgressBar {
191198

192199
for (auto &style : get_value<details::ProgressBarOption::font_styles>())
193200
details::set_font_style(os, style);
201+
202+
// Need to erase previously written text across multiple lines to solve
203+
// issue https://github.com/p-ranav/indicators/issues/132
204+
erase_lines(extra_wrapped_lines_);
194205

195206
const auto prefix_pair = get_prefix_text();
196207
const auto prefix_text = prefix_pair.first;
@@ -217,17 +228,18 @@ class IndeterminateProgressBar {
217228
const auto bar_width = get_value<details::ProgressBarOption::bar_width>();
218229
const auto end_length = get_value<details::ProgressBarOption::end>().size();
219230
const auto terminal_width = terminal_size().second;
220-
// prefix + bar_width + postfix should be <= terminal_width
221-
const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length);
231+
const auto number_of_characters = prefix_length + start_length + bar_width + end_length + postfix_length;
232+
// If prefix + bar_width + postfix > terminal_width, lines will be wrapped
233+
const int remaining = terminal_width - number_of_characters;
222234
if (prefix_length == -1 || postfix_length == -1) {
223235
os << "\r";
224236
} else if (remaining > 0) {
225237
os << std::string(remaining, ' ') << "\r";
226-
} else if (remaining < 0) {
227-
// Do nothing. Maybe in the future truncate postfix with ...
228238
}
229239
os.flush();
230240

241+
extra_wrapped_lines_ = details::extra_wrapped_lines(number_of_characters);
242+
231243
if (get_value<details::ProgressBarOption::completed>() &&
232244
!from_multi_progress) // Don't std::endl if calling from MultiProgress
233245
os << termcolor::reset << std::endl;

include/indicators/multi_progress.hpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <iostream>
77
#include <mutex>
88
#include <vector>
9+
#include <numeric>
910

1011
#include <indicators/color.hpp>
1112
#include <indicators/cursor_movement.hpp>
@@ -65,9 +66,21 @@ template <typename Indicator, size_t count> class MultiProgress {
6566
public:
6667
void print_progress() {
6768
std::lock_guard<std::mutex> lock{mutex_};
68-
if (started_)
69-
move_up(count);
69+
70+
if (started_) {
71+
// move all the way up to start of first progress bar
72+
const auto wrapped_lines = std::accumulate(begin(bars_), end(bars_), 0, [](size_t acc, auto &bar) {
73+
return acc + bar.get().extra_wrapped_lines();
74+
});
75+
move_up(count + wrapped_lines);
76+
}
77+
7078
for (auto &bar : bars_) {
79+
auto wrapped_line = bar.get().extra_wrapped_lines();
80+
if (wrapped_line > 0) {
81+
// for each bar before calling `print_progress`, cursor needs to be on last line that bar printed
82+
move_down(wrapped_line);
83+
}
7184
bar.get().print_progress(true);
7285
std::cout << "\n";
7386
}

include/indicators/progress_bar.hpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#define INDICATORS_PROGRESS_BAR
44

55
#include <indicators/details/stream_helper.hpp>
6+
#include <indicators/cursor_control.hpp>
67

78
#include <algorithm>
89
#include <atomic>
@@ -180,6 +181,11 @@ class ProgressBar {
180181
print_progress();
181182
}
182183

184+
size_t extra_wrapped_lines() {
185+
std::lock_guard<std::mutex> lock{mutex_};
186+
return extra_wrapped_lines_;
187+
}
188+
183189
private:
184190
template <details::ProgressBarOption id>
185191
auto get_value()
@@ -198,6 +204,7 @@ class ProgressBar {
198204
std::chrono::nanoseconds elapsed_;
199205
std::chrono::time_point<std::chrono::high_resolution_clock> start_time_point_;
200206
std::mutex mutex_;
207+
size_t extra_wrapped_lines_{0};
201208

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

320+
// Need to erase previously written text across multiple lines to solve
321+
// issue https://github.com/p-ranav/indicators/issues/132
322+
erase_lines(extra_wrapped_lines_);
323+
313324
const auto prefix_pair = get_prefix_text();
314325
const auto prefix_text = prefix_pair.first;
315326
const auto prefix_length = prefix_pair.second;
@@ -336,17 +347,18 @@ class ProgressBar {
336347
const auto bar_width = get_value<details::ProgressBarOption::bar_width>();
337348
const auto end_length = get_value<details::ProgressBarOption::end>().size();
338349
const auto terminal_width = terminal_size().second;
339-
// prefix + bar_width + postfix should be <= terminal_width
340-
const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length);
350+
const auto number_of_characters = prefix_length + start_length + bar_width + end_length + postfix_length;
351+
// If prefix + bar_width + postfix > terminal_width, lines will be wrapped
352+
const int remaining = terminal_width - number_of_characters;
341353
if (prefix_length == -1 || postfix_length == -1) {
342354
os << "\r";
343355
} else if (remaining > 0) {
344356
os << std::string(remaining, ' ') << "\r";
345-
} else if (remaining < 0) {
346-
// Do nothing. Maybe in the future truncate postfix with ...
347357
}
348358
os.flush();
349359

360+
extra_wrapped_lines_ = details::extra_wrapped_lines(number_of_characters);
361+
350362
if ((type == ProgressType::incremental && progress_ >= max_progress) ||
351363
(type == ProgressType::decremental && progress_ <= min_progress)) {
352364
get_value<details::ProgressBarOption::completed>() = true;

0 commit comments

Comments
 (0)