From da330ffd5ae81e665d9958cff0dd797a59cbb6f1 Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Tue, 19 May 2026 14:05:37 -0400 Subject: [PATCH 1/8] Improved details for build and pull --- src/shared/inc/stringshared.h | 24 ++++++++++ src/windows/inc/docker_schema.h | 4 +- .../wslc/services/BuildImageCallback.cpp | 41 +++++++++++++++-- .../wslc/services/BuildImageCallback.h | 3 ++ .../wslc/services/ImageProgressCallback.cpp | 44 +++++++++++++++++-- src/windows/wslcsession/WSLCSession.cpp | 23 ++++++++-- 6 files changed, 127 insertions(+), 12 deletions(-) diff --git a/src/shared/inc/stringshared.h b/src/shared/inc/stringshared.h index 8187101e7..b547e27f0 100644 --- a/src/shared/inc/stringshared.h +++ b/src/shared/inc/stringshared.h @@ -825,6 +825,30 @@ struct CaseInsensitiveCompare } }; +inline std::wstring FormatBytes(uint64_t bytes) +{ + constexpr double c_kB = 1024.0; + constexpr double c_MB = 1024.0 * 1024.0; + constexpr double c_GB = 1024.0 * 1024.0 * 1024.0; + + if (bytes >= static_cast(c_GB)) + { + return std::format(L"{:.2f} GB", bytes / c_GB); + } + else if (bytes >= static_cast(c_MB)) + { + return std::format(L"{:.2f} MB", bytes / c_MB); + } + else if (bytes >= static_cast(c_kB)) + { + return std::format(L"{:.2f} KB", bytes / c_kB); + } + else + { + return std::format(L"{} B", bytes); + } +} + } // namespace wsl::shared::string template <> diff --git a/src/windows/inc/docker_schema.h b/src/windows/inc/docker_schema.h index 2cadfd8d5..e6fb2a3d1 100644 --- a/src/windows/inc/docker_schema.h +++ b/src/windows/inc/docker_schema.h @@ -584,8 +584,10 @@ struct BuildKitStatus { std::string id; std::string vertex; + int64_t current{}; + int64_t total{}; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(BuildKitStatus, id, vertex); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(BuildKitStatus, id, vertex, current, total); }; struct BuildKitLog diff --git a/src/windows/wslc/services/BuildImageCallback.cpp b/src/windows/wslc/services/BuildImageCallback.cpp index 847a3a90f..02ce10db1 100644 --- a/src/windows/wslc/services/BuildImageCallback.cpp +++ b/src/windows/wslc/services/BuildImageCallback.cpp @@ -64,9 +64,10 @@ void BuildImageCallback::CollapseWindow() m_lines.clear(); m_pendingLine.clear(); + m_pullLines.clear(); } -HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG /*current*/, ULONGLONG /*total*/) +HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG /*current*/, ULONGLONG total) try { if (status == nullptr || *status == '\0') @@ -91,11 +92,37 @@ try // accepting any non-empty id, so future or unrelated id usage defaults to permanent. const bool isLog = (id != nullptr && std::string_view{id} == "log"); + // Pull/download progress: update the per-entry map so Redraw can show each entry + // on a single line that updates in place. + if (!isLog && id != nullptr && *id != '\0' && total > 0) + { + m_pullLines[id] = status; + + auto now = std::chrono::steady_clock::now(); + if (now - m_lastRedraw >= c_redrawInterval) + { + Redraw(); + m_lastRedraw = now; + } + + return S_OK; + } + if (!isLog) { - // Permanent line: collapse the scrolling window then print directly. + // Permanent build-step line: collapse the scrolling window then print in teal. CollapseWindow(); - WriteTerminal(MultiByteToWide(status)); + auto wide = MultiByteToWide(status); + if (!wide.empty() && wide.back() == L'\n') + { + wide.insert(0, L"\033[36m"); + wide.insert(wide.size() - 1, L"\033[0m"); + } + else + { + wide = std::format(L"\033[36m{}\033[0m", wide); + } + WriteTerminal(wide); return S_OK; } @@ -217,10 +244,16 @@ void BuildImageCallback::Redraw() appendLine(m_pendingLine); } + // Render per-entry pull progress (each entry updates in place via the map). + for (const auto& [key, line] : m_pullLines) + { + appendLine(line); + } + buffer += L"\033[22m\033[?25h"; WriteTerminal(buffer); - m_displayedLines = displayCount; + m_displayedLines = displayCount + static_cast(m_pullLines.size()); } } // namespace wsl::windows::wslc::services diff --git a/src/windows/wslc/services/BuildImageCallback.h b/src/windows/wslc/services/BuildImageCallback.h index df45addbe..243d18bd8 100644 --- a/src/windows/wslc/services/BuildImageCallback.h +++ b/src/windows/wslc/services/BuildImageCallback.h @@ -15,6 +15,7 @@ Module Name: #include "ChangeTerminalMode.h" #include "SessionService.h" #include +#include namespace wsl::windows::wslc::services { class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback @@ -54,6 +55,8 @@ class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback std::string m_pendingLine; SHORT m_displayedLines = 0; std::chrono::steady_clock::time_point m_lastRedraw{}; + // Per-entry pull progress lines, keyed by entry id. Updated in place by Redraw. + std::map m_pullLines; // Captured at construction so the destructor can detect destruction during exception unwinding. int m_uncaughtExceptions = std::uncaught_exceptions(); }; diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index 7cee4674f..c13a62fa4 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -84,15 +84,53 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, std::wstring line; if (total != 0) { - line = std::format(L"{} '{}': {}%", status, id, current * 100 / total); + constexpr int c_progressBarWidth = 30; + + int filled = 0; + if (current >= total) + { + filled = c_progressBarWidth; + } + else + { + auto ratio = static_cast(current) / static_cast(total); + filled = static_cast(ratio * c_progressBarWidth); + } + + filled = std::clamp(filled, 0, c_progressBarWidth); + + std::wstring bar(c_progressBarWidth, L' '); + for (int i = 0; i < filled; ++i) + { + bar[i] = L'='; + } + + if (filled < c_progressBarWidth) + { + bar[filled] = L'>'; + } + + line = std::format( + L"{}: {} [{}] {}/{}", + id, + status, + bar, + wsl::shared::string::FormatBytes(current), + wsl::shared::string::FormatBytes(total)); } else if (current != 0) { - line = std::format(L"{} '{}': {}s", status, id, current); + line = std::format(L"{}: {} {}s", id, status, current); } else { - line = std::format(L"{} '{}'", status, id); + line = std::format(L"{}: {}", id, status); + } + + // Truncate to console width to prevent wrapping that would break cursor repositioning. + if (line.size() > static_cast(info.dwSize.X)) + { + line.resize(info.dwSize.X); } // Erase any previously written char on that line. diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index f9ca9ffd6..bd29a6b1d 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -827,10 +827,10 @@ try return " [" + name + "] "; }; - auto reportProgress = [&](const std::string& message, const char* id = "") { + auto reportProgress = [&](const std::string& message, const char* id = "", ULONGLONG current = 0, ULONGLONG total = 0) { if (ProgressCallback != nullptr) { - THROW_IF_FAILED(ProgressCallback->OnProgress(message.c_str(), id, 0, 0)); + THROW_IF_FAILED(ProgressCallback->OnProgress(message.c_str(), id, current, total)); } }; @@ -924,8 +924,23 @@ try for (const auto& entry : status.statuses) { - if (auto it = digestToStageName.find(entry.vertex); - it != digestToStageName.end() && !entry.id.empty() && reportedSteps.insert(entry.id).second) + auto it = digestToStageName.find(entry.vertex); + if (it == digestToStageName.end() || entry.id.empty()) + { + continue; + } + + if (entry.total > 0) + { + auto current = wsl::shared::string::FormatBytes(entry.current); + auto total = wsl::shared::string::FormatBytes(entry.total); + reportProgress( + std::format("{}{} {} / {}", logPrefix(it->second), entry.id, current, total), + entry.id.c_str(), + static_cast(entry.current), + static_cast(entry.total)); + } + else if (reportedSteps.insert(entry.id).second) { flushLine(); reportProgress(logPrefix(it->second) + entry.id + "\n"); From 07c7cfc175bdcb0328846d3d0ad90b1d36458cc5 Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Tue, 19 May 2026 14:13:08 -0400 Subject: [PATCH 2/8] Updated clang format --- src/windows/wslc/services/ImageProgressCallback.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index c13a62fa4..0911ef466 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -111,12 +111,7 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, } line = std::format( - L"{}: {} [{}] {}/{}", - id, - status, - bar, - wsl::shared::string::FormatBytes(current), - wsl::shared::string::FormatBytes(total)); + L"{}: {} [{}] {}/{}", id, status, bar, wsl::shared::string::FormatBytes(current), wsl::shared::string::FormatBytes(total)); } else if (current != 0) { From 41d419a8f4382b513da32446e8bd2e89c36cdaf6 Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Tue, 19 May 2026 15:01:17 -0400 Subject: [PATCH 3/8] PR fixes --- src/shared/inc/stringshared.h | 6 +++--- src/windows/wslc/services/ImageProgressCallback.cpp | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/shared/inc/stringshared.h b/src/shared/inc/stringshared.h index b547e27f0..978bd9cbd 100644 --- a/src/shared/inc/stringshared.h +++ b/src/shared/inc/stringshared.h @@ -827,9 +827,9 @@ struct CaseInsensitiveCompare inline std::wstring FormatBytes(uint64_t bytes) { - constexpr double c_kB = 1024.0; - constexpr double c_MB = 1024.0 * 1024.0; - constexpr double c_GB = 1024.0 * 1024.0 * 1024.0; + constexpr double c_kB = 1000.0; + constexpr double c_MB = 1000.0 * 1000.0; + constexpr double c_GB = 1000.0 * 1000.0 * 1000.0; if (bytes >= static_cast(c_GB)) { diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index 0911ef466..b136a2d82 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -126,6 +126,13 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, if (line.size() > static_cast(info.dwSize.X)) { line.resize(info.dwSize.X); + + // Avoid splitting a surrogate pair — if the last code unit is a high surrogate, + // drop it so we don't emit an invalid UTF-16 sequence. + if (!line.empty() && IS_HIGH_SURROGATE(line.back())) + { + line.pop_back(); + } } // Erase any previously written char on that line. From 9fb96f7cf09e0d309878ebc7133cad993125e27b Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Tue, 19 May 2026 15:30:03 -0400 Subject: [PATCH 4/8] PR fixes --- src/windows/wslc/services/ImageProgressCallback.cpp | 9 ++++++--- src/windows/wslcsession/WSLCSession.cpp | 12 +++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index b136a2d82..24c3343f8 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -122,10 +122,13 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, line = std::format(L"{}: {}", id, status); } + // Use the visible window width (not the buffer width) to prevent wrapping. + const auto visibleWidth = std::max(0, info.srWindow.Right - info.srWindow.Left + 1); + // Truncate to console width to prevent wrapping that would break cursor repositioning. - if (line.size() > static_cast(info.dwSize.X)) + if (line.size() > static_cast(visibleWidth)) { - line.resize(info.dwSize.X); + line.resize(visibleWidth); // Avoid splitting a surrogate pair — if the last code unit is a high surrogate, // drop it so we don't emit an invalid UTF-16 sequence. @@ -136,7 +139,7 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, } // Erase any previously written char on that line. - while (line.size() < info.dwSize.X) + while (line.size() < static_cast(visibleWidth)) { line += L' '; } diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index bd29a6b1d..3ff85f02e 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -932,13 +932,11 @@ try if (entry.total > 0) { - auto current = wsl::shared::string::FormatBytes(entry.current); - auto total = wsl::shared::string::FormatBytes(entry.total); - reportProgress( - std::format("{}{} {} / {}", logPrefix(it->second), entry.id, current, total), - entry.id.c_str(), - static_cast(entry.current), - static_cast(entry.total)); + auto currentBytes = static_cast(std::max(entry.current, 0)); + auto totalBytes = static_cast(std::max(entry.total, 0)); + auto current = wsl::shared::string::FormatBytes(currentBytes); + auto total = wsl::shared::string::FormatBytes(totalBytes); + reportProgress(std::format("{}{} {} / {}", logPrefix(it->second), entry.id, current, total), entry.id.c_str(), currentBytes, totalBytes); } else if (reportedSteps.insert(entry.id).second) { From 059c66432f335f2eacb03d1e424efa345f4d3eed Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Tue, 19 May 2026 14:40:49 -0400 Subject: [PATCH 5/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/windows/wslc/services/BuildImageCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslc/services/BuildImageCallback.cpp b/src/windows/wslc/services/BuildImageCallback.cpp index 02ce10db1..53ae659d2 100644 --- a/src/windows/wslc/services/BuildImageCallback.cpp +++ b/src/windows/wslc/services/BuildImageCallback.cpp @@ -253,7 +253,7 @@ void BuildImageCallback::Redraw() buffer += L"\033[22m\033[?25h"; WriteTerminal(buffer); - m_displayedLines = displayCount + static_cast(m_pullLines.size()); + m_displayedLines = displayCount; } } // namespace wsl::windows::wslc::services From 819e9835617f2539ebc106538be0822401462cdf Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Wed, 20 May 2026 14:48:04 -0400 Subject: [PATCH 6/8] Added in PR fixes --- .../wslc/services/BuildImageCallback.cpp | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/windows/wslc/services/BuildImageCallback.cpp b/src/windows/wslc/services/BuildImageCallback.cpp index 53ae659d2..781616503 100644 --- a/src/windows/wslc/services/BuildImageCallback.cpp +++ b/src/windows/wslc/services/BuildImageCallback.cpp @@ -67,7 +67,7 @@ void BuildImageCallback::CollapseWindow() m_pullLines.clear(); } -HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG /*current*/, ULONGLONG total) +HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total) try { if (status == nullptr || *status == '\0') @@ -84,6 +84,12 @@ try if (m_verbose || !m_isConsole) { + // Skip pull progress updates when output is redirected, show only major steps + if (id != nullptr && *id != '\0' && total > 0) + { + return S_OK; + } + wprintf(L"%hs", status); return S_OK; } @@ -113,15 +119,13 @@ try // Permanent build-step line: collapse the scrolling window then print in teal. CollapseWindow(); auto wide = MultiByteToWide(status); - if (!wide.empty() && wide.back() == L'\n') - { - wide.insert(0, L"\033[36m"); - wide.insert(wide.size() - 1, L"\033[0m"); - } - else + std::wstring terminator; + while (!wide.empty() && (wide.back() == L'\n' || wide.back() == L'\r')) { - wide = std::format(L"\033[36m{}\033[0m", wide); + terminator.insert(terminator.begin(), wide.back()); + wide.pop_back(); } + wide = std::format(L"\033[36m{}\033[0m{}", wide, terminator); WriteTerminal(wide); return S_OK; } @@ -194,14 +198,16 @@ void BuildImageCallback::Redraw() // to std::wstring::resize). const SHORT consoleWidth = std::max(0, info.srWindow.Right - info.srWindow.Left); - // Determine how many completed lines to show, leaving room for the pending line. + // Determine how many completed lines to show, leaving room for the pending line and pull progress. const bool showPending = !m_pendingLine.empty(); + const SHORT pullCount = static_cast(m_pullLines.size()); SHORT completedCount = static_cast(m_lines.size()); - if (showPending && completedCount >= c_maxDisplayLines) + const SHORT reservedLines = (showPending ? 1 : 0) + pullCount; + if (completedCount + reservedLines > c_maxDisplayLines) { - completedCount = c_maxDisplayLines - 1; + completedCount = std::max(0, c_maxDisplayLines - reservedLines); } - const SHORT displayCount = completedCount + (showPending ? 1 : 0); + const SHORT displayCount = completedCount + reservedLines; // Build the entire frame in one buffer to minimize console writes. Hide the cursor // during the redraw so the user doesn't see it bouncing through the cursor movement, From 2e644a0616cf59812055a867a5dff0dcc50d2adb Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Thu, 21 May 2026 10:57:19 -0400 Subject: [PATCH 7/8] Changed color to green --- src/windows/wslc/services/BuildImageCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslc/services/BuildImageCallback.cpp b/src/windows/wslc/services/BuildImageCallback.cpp index 781616503..2ffe0efbb 100644 --- a/src/windows/wslc/services/BuildImageCallback.cpp +++ b/src/windows/wslc/services/BuildImageCallback.cpp @@ -125,7 +125,7 @@ try terminator.insert(terminator.begin(), wide.back()); wide.pop_back(); } - wide = std::format(L"\033[36m{}\033[0m{}", wide, terminator); + wide = std::format(L"\033[92m{}\033[0m{}", wide, terminator); WriteTerminal(wide); return S_OK; } From dab9f350bb9d4df5df6e840b23cfeb228e35f19c Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Thu, 21 May 2026 11:46:06 -0400 Subject: [PATCH 8/8] PR updates --- .../wslc/services/BuildImageCallback.cpp | 123 +++++++++--------- .../wslc/services/BuildImageCallback.h | 2 +- .../wslc/services/ImageProgressCallback.cpp | 20 +-- 3 files changed, 66 insertions(+), 79 deletions(-) diff --git a/src/windows/wslc/services/BuildImageCallback.cpp b/src/windows/wslc/services/BuildImageCallback.cpp index 2ffe0efbb..3e4590ce7 100644 --- a/src/windows/wslc/services/BuildImageCallback.cpp +++ b/src/windows/wslc/services/BuildImageCallback.cpp @@ -82,25 +82,23 @@ try return S_OK; } + const std::string_view idView = (id != nullptr) ? id : std::string_view{}; + const bool isLog = (idView == "log"); + const bool isPullProgress = (!idView.empty() && total > 0 && !isLog); + if (m_verbose || !m_isConsole) { // Skip pull progress updates when output is redirected, show only major steps - if (id != nullptr && *id != '\0' && total > 0) + if (!isPullProgress) { - return S_OK; + wprintf(L"%hs", status); } - - wprintf(L"%hs", status); return S_OK; } - // Match the specific "log" sentinel sent by WSLCSession::BuildImage rather than - // accepting any non-empty id, so future or unrelated id usage defaults to permanent. - const bool isLog = (id != nullptr && std::string_view{id} == "log"); - // Pull/download progress: update the per-entry map so Redraw can show each entry // on a single line that updates in place. - if (!isLog && id != nullptr && *id != '\0' && total > 0) + if (isPullProgress) { m_pullLines[id] = status; @@ -114,76 +112,73 @@ try return S_OK; } - if (!isLog) - { - // Permanent build-step line: collapse the scrolling window then print in teal. - CollapseWindow(); - auto wide = MultiByteToWide(status); - std::wstring terminator; - while (!wide.empty() && (wide.back() == L'\n' || wide.back() == L'\r')) - { - terminator.insert(terminator.begin(), wide.back()); - wide.pop_back(); - } - wide = std::format(L"\033[92m{}\033[0m{}", wide, terminator); - WriteTerminal(wide); - return S_OK; - } - - // Log line: add to the scrolling window. - for (const char* p = status; *p != '\0'; ++p) + if (isLog) { - if (*p == '\n') + // Log line: add to the scrolling window. + for (const char* p = status; *p != '\0'; ++p) { - // Store with the trailing newline so the byte count matches what is replayed. - // Cap retained log output to avoid unbounded growth on very long builds. - m_allLines.push_back(m_pendingLine + '\n'); - m_allLinesBytes += m_allLines.back().size(); - while (m_allLinesBytes > c_maxAllLinesBytes && !m_allLines.empty()) + if (*p == '\n') { - m_allLinesBytes -= m_allLines.front().size(); - m_allLines.pop_front(); - } + // Store with the trailing newline so the byte count matches what is replayed. + // Cap retained log output to avoid unbounded growth on very long builds. + m_allLines.push_back(m_pendingLine + '\n'); + m_allLinesBytes += m_allLines.back().size(); + while (m_allLinesBytes > c_maxAllLinesBytes && !m_allLines.empty()) + { + m_allLinesBytes -= m_allLines.front().size(); + m_allLines.pop_front(); + } - m_lines.push_back(std::move(m_pendingLine)); - m_pendingLine.clear(); - if (m_lines.size() > c_maxDisplayLines) - { - m_lines.pop_front(); + m_lines.push_back(std::move(m_pendingLine)); + m_pendingLine.clear(); + if (m_lines.size() > c_maxDisplayLines) + { + m_lines.pop_front(); + } } - } - else if (*p == '\r') - { - // \r\n is a line ending; standalone \r overwrites the current line. - if (*(p + 1) != '\n') + else if (*p == '\r') { - // Flush a throttled redraw before clearing so \r-based progress - // updates are visible even when batched in a single OnProgress call. - auto now = std::chrono::steady_clock::now(); - if (!m_pendingLine.empty() && now - m_lastRedraw >= c_redrawInterval) + // \r\n is a line ending; standalone \r overwrites the current line. + if (*(p + 1) != '\n') { - Redraw(); - m_lastRedraw = now; + // Flush a throttled redraw before clearing so \r-based progress + // updates are visible even when batched in a single OnProgress call. + auto now = std::chrono::steady_clock::now(); + if (!m_pendingLine.empty() && now - m_lastRedraw >= c_redrawInterval) + { + Redraw(); + m_lastRedraw = now; + } + m_pendingLine.clear(); } - m_pendingLine.clear(); + } + else + { + m_pendingLine += *p; } } - else + + // Throttle redraws to avoid blocking the server's IO loop with console writes + // during rapid output. Lines accumulate in the deque immediately; the display + // catches up at ~20fps. + auto now = std::chrono::steady_clock::now(); + if (now - m_lastRedraw >= c_redrawInterval) { - m_pendingLine += *p; + Redraw(); + m_lastRedraw = now; } - } - // Throttle redraws to avoid blocking the server's IO loop with console writes - // during rapid output. Lines accumulate in the deque immediately; the display - // catches up at ~20fps. - auto now = std::chrono::steady_clock::now(); - if (now - m_lastRedraw >= c_redrawInterval) - { - Redraw(); - m_lastRedraw = now; + return S_OK; } + // Else is a build step + CollapseWindow(); + auto wide = MultiByteToWide(status); + const auto bodyLength = wide.find_last_not_of(L"\r\n") + 1; + const auto newlines = wide.substr(bodyLength); + wide.resize(bodyLength); + + WriteTerminal(std::format(L"\033[92m{}\033[0m{}", wide, newlines)); return S_OK; } CATCH_RETURN(); diff --git a/src/windows/wslc/services/BuildImageCallback.h b/src/windows/wslc/services/BuildImageCallback.h index 243d18bd8..c696ff502 100644 --- a/src/windows/wslc/services/BuildImageCallback.h +++ b/src/windows/wslc/services/BuildImageCallback.h @@ -55,7 +55,7 @@ class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback std::string m_pendingLine; SHORT m_displayedLines = 0; std::chrono::steady_clock::time_point m_lastRedraw{}; - // Per-entry pull progress lines, keyed by entry id. Updated in place by Redraw. + // Per-entry pull progress lines, keyed by entry id. Updated in place by Redraw. std::map so order is consistent. std::map m_pullLines; // Captured at construction so the destructor can detect destruction during exception unwinding. int m_uncaughtExceptions = std::uncaught_exceptions(); diff --git a/src/windows/wslc/services/ImageProgressCallback.cpp b/src/windows/wslc/services/ImageProgressCallback.cpp index 24c3343f8..a8bea2154 100644 --- a/src/windows/wslc/services/ImageProgressCallback.cpp +++ b/src/windows/wslc/services/ImageProgressCallback.cpp @@ -99,16 +99,11 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, filled = std::clamp(filled, 0, c_progressBarWidth); - std::wstring bar(c_progressBarWidth, L' '); - for (int i = 0; i < filled; ++i) - { - bar[i] = L'='; - } - - if (filled < c_progressBarWidth) - { - bar[filled] = L'>'; - } + std::wstring bar; + bar.reserve(c_progressBarWidth); + bar.append(filled, L'='); + bar.append(L">"); + bar.resize(c_progressBarWidth, L' '); line = std::format( L"{}: {} [{}] {}/{}", id, status, bar, wsl::shared::string::FormatBytes(current), wsl::shared::string::FormatBytes(total)); @@ -139,10 +134,7 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id, } // Erase any previously written char on that line. - while (line.size() < static_cast(visibleWidth)) - { - line += L' '; - } + line.resize(visibleWdith, L' '); return line; }