winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 43e70faca24ce0bf4f941a7a3358fbc701228958
parent b43f92331f753c853e2ff1d6d33ffa425a27e6d3
Author: chausner <15180557+chausner@users.noreply.github.com>
Date:   Thu, 22 Apr 2021 20:46:29 +0200

Implement VT-based progress reporting (#883)

* Implement VT-based progress reporting

* Update GitHub spelling checker dictionary

* Throw E_INVALIDARG instead of E_UNEXPECTED

Co-authored-by: JohnMcPMS <johnmcp@microsoft.com>

* Rename ProgressState -> State, ProgressBar -> Construct, change parameter to uint32_t

Co-authored-by: chausner <chausner@users.noreply.github.com>
Co-authored-by: JohnMcPMS <johnmcp@microsoft.com>
Diffstat:
M.github/actions/spelling/allow.txt | 4+++-
Msrc/AppInstallerCLICore/ExecutionProgress.cpp | 57+++++++++++++++++++++++++++++++++++++++------------------
Msrc/AppInstallerCLICore/ExecutionProgress.h | 4+---
Msrc/AppInstallerCLICore/VTSupport.cpp | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/VTSupport.h | 16++++++++++++++++
5 files changed, 118 insertions(+), 22 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -48,6 +48,7 @@ cnt codepage COMMANDBARFLYOUT Commandline +conemu config Configurability constexpr @@ -421,7 +422,7 @@ tdbuild tdd tellg tempdb -terabyate +terabyte testcontainer testmoniker Testrun @@ -511,6 +512,7 @@ wix wmain woah wofstream +workaround workflow wostream wpfn diff --git a/src/AppInstallerCLICore/ExecutionProgress.cpp b/src/AppInstallerCLICore/ExecutionProgress.cpp @@ -19,7 +19,7 @@ namespace AppInstaller::CLI::Execution BytesFormatData s_bytesFormatData[] = { - // Multi-terabyate installers should be fairly rare for the foreseeable future... + // Multi-terabyte installers should be fairly rare for the foreseeable future... { 40, "TB"sv }, { 30, "GB"sv }, { 20, "MB"sv }, @@ -167,7 +167,7 @@ namespace AppInstaller::CLI::Execution if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) { m_spinnerRunning = true; - m_spinnerJob = std::async(std::launch::async, (UseVT() ? &IndefiniteSpinner::ShowSpinnerInternalWithVT : &IndefiniteSpinner::ShowSpinnerInternalNoVT), this); + m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); } } @@ -180,7 +180,7 @@ namespace AppInstaller::CLI::Execution } } - void IndefiniteSpinner::ShowSpinnerInternalNoVT() + void IndefiniteSpinner::ShowSpinnerInternal() { char spinnerChars[] = { '-', '\\', '|', '/' }; @@ -188,28 +188,37 @@ namespace AppInstaller::CLI::Execution // showing anything, or a progress task to skip straight to progress. Sleep(100); - // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. - m_out << " "; - - for (size_t i = 0; !m_canceled; ++i) + if (!m_canceled) { - constexpr size_t repetitionCount = 20; - ApplyStyle(i % repetitionCount, repetitionCount, true); - m_out << '\b' << spinnerChars[i % ARRAYSIZE(spinnerChars)] << std::flush; - Sleep(250); + if (UseVT()) + { + // Additional VT-based progress reporting, for terminals that support it + m_out << Progress::Construct(Progress::State::Indeterminate); + } + + // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. + m_out << " "; + + for (size_t i = 0; !m_canceled; ++i) + { + constexpr size_t repetitionCount = 20; + ApplyStyle(i % repetitionCount, repetitionCount, true); + m_out << '\b' << spinnerChars[i % ARRAYSIZE(spinnerChars)] << std::flush; + Sleep(250); + } + + m_out << "\b \r"; + + if (UseVT()) + { + m_out << Progress::Construct(Progress::State::None); + } } - m_out << "\b \r"; m_canceled = false; m_spinnerRunning = false; } - void IndefiniteSpinner::ShowSpinnerInternalWithVT() - { - // Nothing special to do at the moment, can use NoVT version. - ShowSpinnerInternalNoVT(); - } - void ProgressBar::ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) { if (current < m_lastCurrent) @@ -242,6 +251,15 @@ namespace AppInstaller::CLI::Execution { m_out << std::endl; } + + if (UseVT()) + { + // We always clear the VT-based progress bar, even if hideProgressWhenDone is false + // since it would be confusing for users if progress continues to be shown after winget exits + // (it is typically not automatically cleared by terminals on process exit) + m_out << Progress::Construct(Progress::State::None); + } + m_isVisible = false; } } @@ -349,6 +367,9 @@ namespace AppInstaller::CLI::Execution m_out << static_cast<int>(percentage * 100) << '%'; break; } + + // Additional VT-based progress reporting, for terminals that support it + m_out << Progress::Construct(Progress::State::Normal, static_cast<int>(percentage * 100)); } else { diff --git a/src/AppInstallerCLICore/ExecutionProgress.h b/src/AppInstallerCLICore/ExecutionProgress.h @@ -55,9 +55,7 @@ namespace AppInstaller::CLI::Execution std::atomic<bool> m_spinnerRunning = false; std::future<void> m_spinnerJob; - void ShowSpinnerInternalNoVT(); - - void ShowSpinnerInternalWithVT(); + void ShowSpinnerInternal(); }; // Displays progress diff --git a/src/AppInstallerCLICore/VTSupport.cpp b/src/AppInstallerCLICore/VTSupport.cpp @@ -164,4 +164,63 @@ namespace AppInstaller::CLI::VirtualTerminal const Sequence EraseLineBackward{ AICLI_VT_CSI "1K" }; const Sequence EraseLineEntirely{ AICLI_VT_CSI "2K" }; } + + namespace Progress + { + ConstructedSequence Construct(State state, std::optional<uint32_t> percentage) + { + // See https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC + + THROW_HR_IF(E_BOUNDS, percentage.has_value() && percentage > 100u); + + // Workaround some quirks in the Windows Terminal implementation of the progress OSC sequence + switch (state) + { + case State::None: + case State::Indeterminate: + // Windows Terminal does not recognize the OSC sequence if the progress value is left out. + // As a workaround, we can specify an arbitrary value since it does not matter for None and Indeterminate states. + percentage = percentage.value_or(0); + break; + case State::Normal: + case State::Error: + case State::Paused: + // Windows Terminal does not support switching progress states without also setting a progress value at the same time, + // so we disallow this case for now. + THROW_HR_IF(E_INVALIDARG, !percentage.has_value()); + break; + } + + int stateId; + switch (state) + { + case State::None: + stateId = 0; + break; + case State::Indeterminate: + stateId = 3; + break; + case State::Normal: + stateId = 1; + break; + case State::Error: + stateId = 2; + break; + case State::Paused: + stateId = 4; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + std::ostringstream result; + result << AICLI_VT_OSC "9;4;" << stateId << ";"; + if (percentage.has_value()) + { + result << percentage.value(); + } + result << AICLI_VT_ESCAPE << "\\"; + return ConstructedSequence{ result.str() }; + } + } } diff --git a/src/AppInstallerCLICore/VTSupport.h b/src/AppInstallerCLICore/VTSupport.h @@ -3,7 +3,9 @@ #pragma once #include <AppInstallerLanguageUtilities.h> +#include <cstdint> #include <iostream> +#include <optional> #include <string> @@ -133,6 +135,20 @@ namespace AppInstaller::CLI::VirtualTerminal extern const Sequence EraseLineBackward; extern const Sequence EraseLineEntirely; } + + namespace Progress + { + enum class State + { + None, + Indeterminate, + Normal, + Paused, + Error + }; + + ConstructedSequence Construct(State state, std::optional<uint32_t> percentage = std::nullopt); + } } inline std::ostream& operator<<(std::ostream& o, const AppInstaller::CLI::VirtualTerminal::Sequence& s)