libmain/progress-bar: implement synchronized updates

If tearing occurs while the progress bar is rendering, flickering can occur.
This is particularly visible in multiline mode. Use the synchronized updates
specification [1] to make updates atomic on supported terminal emulators. On
unsupported terminals, the escape sequences should be ignored, leaving the
behaviour effectively unchanged (but there's nothing we could do better in this
situation).

[1] https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec

Change-Id: Ic91ca40422c73ac9f9e088c7cb78c1b8c28adc4e
This commit is contained in:
Alois Wohlschlager
2025-03-09 16:36:02 +01:00
parent 0b2d58a925
commit a13521bb1f
+7
View File
@@ -320,6 +320,9 @@ void ProgressBar::update(State & state)
void ProgressBar::eraseProgressDisplay(State & state)
{
if (state.paused == 0) {
writeLogsToStderr("\e[?2026h"); // begin synchronized update
}
if (printMultiline && (state.lastLines >= 1)) {
// FIXME: make sure this works on windows
writeLogsToStderr(fmt("\e[G\e[%dF\e[J", state.lastLines));
@@ -414,6 +417,8 @@ std::chrono::milliseconds ProgressBar::restoreProgressDisplay(State & state)
}
}
writeLogsToStderr("\e[?2026l"); // end synchronized update
return nextWakeup;
}
@@ -548,8 +553,10 @@ std::optional<char> ProgressBar::ask(std::string_view msg)
auto state(state_.lock());
if (state->paused > 0 || !isatty(STDIN_FILENO)) return {};
eraseProgressDisplay(*state);
writeLogsToStderr("\e[?2026l"); // end synchronized update
std::cerr << msg;
auto s = trim(readLine(STDIN_FILENO));
writeLogsToStderr("\e[?2026h"); // begin synchronized update
if (s.size() != 1) return {};
restoreProgressDisplay(*state);
return s[0];