From 2693210ead2bf8aaccac6b67074c55ece9e20cf2 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 13 Mar 2025 00:27:12 +0100 Subject: [PATCH 001/177] Fix a handoff deadlock if layout completes synchronously (#18676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I've received a dump from an affected user, and it showed that the layout event in TerminalPage was raised synchronously. This meant that during page initialization, the handoff listener was started while still being stuck inside the handoff listener. This resulted in a deadlock. This PR fixes the issue by not holding the lock across handoff callback calls. Closes #18634 ## Validation Steps Performed * Can't repro ❌ --- .../TerminalConnection/CTerminalHandoff.cpp | 38 ++++++------------- .../TerminalConnection/CTerminalHandoff.h | 6 +-- .../TerminalConnection/ConptyConnection.cpp | 10 ++--- .../TerminalConnection/ConptyConnection.h | 1 - .../TerminalConnection/ConptyConnection.idl | 1 - 5 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp index a6edb23243..01b25c9ffe 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp @@ -14,6 +14,13 @@ static DWORD g_cTerminalHandoffRegistration = 0; // Mutex so we only do start/stop/establish one at a time. static std::shared_mutex _mtx; +// This is the callback that will be called when a connection is received. +// Call this once during startup and don't ever change it again (race condition). +void CTerminalHandoff::s_setCallback(NewHandoffFunction callback) noexcept +{ + _pfnHandoff = callback; +} + // Routine Description: // - Starts listening for TerminalHandoff requests by registering // our class and interface with COM. @@ -21,24 +28,19 @@ static std::shared_mutex _mtx; // - pfnHandoff - Function to callback when a handoff is received // Return Value: // - S_OK, E_NOT_VALID_STATE (start called when already started) or relevant COM registration error. -HRESULT CTerminalHandoff::s_StartListening(NewHandoffFunction pfnHandoff) +HRESULT CTerminalHandoff::s_StartListening() try { std::unique_lock lock{ _mtx }; - RETURN_HR_IF(E_NOT_VALID_STATE, _pfnHandoff != nullptr); - const auto classFactory = Make>(); - - RETURN_IF_NULL_ALLOC(classFactory); + RETURN_LAST_ERROR_IF_NULL(classFactory); ComPtr unk; RETURN_IF_FAILED(classFactory.As(&unk)); RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_cTerminalHandoffRegistration)); - _pfnHandoff = pfnHandoff; - return S_OK; } CATCH_RETURN() @@ -53,15 +55,6 @@ CATCH_RETURN() HRESULT CTerminalHandoff::s_StopListening() { std::unique_lock lock{ _mtx }; - return s_StopListeningLocked(); -} - -// See s_StopListening() -HRESULT CTerminalHandoff::s_StopListeningLocked() -{ - RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); - - _pfnHandoff = nullptr; if (g_cTerminalHandoffRegistration) { @@ -92,22 +85,15 @@ HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE si { try { - std::unique_lock lock{ _mtx }; - - // s_StopListeningLocked sets _pfnHandoff to nullptr. - // localPfnHandoff is tested for nullness below. -#pragma warning(suppress : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23). - auto localPfnHandoff = _pfnHandoff; - // Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call. // COM does not automatically clean that up for us. We must do it. - LOG_IF_FAILED(s_StopListeningLocked()); + LOG_IF_FAILED(s_StopListening()); // Report an error if no one registered a handoff function before calling this. - THROW_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff); + THROW_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); // Call registered handler from when we started listening. - THROW_IF_FAILED(localPfnHandoff(in, out, signal, reference, server, client, startupInfo)); + THROW_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo)); #pragma warning(suppress : 26477) TraceLoggingWrite( diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.h b/src/cascadia/TerminalConnection/CTerminalHandoff.h index 440a2636f2..004b3f5274 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.h +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.h @@ -38,11 +38,11 @@ struct __declspec(uuid(__CLSID_CTerminalHandoff)) #pragma endregion - static HRESULT s_StartListening(NewHandoffFunction pfnHandoff); - static HRESULT s_StopListening(); + static void s_setCallback(NewHandoffFunction callback) noexcept; + static HRESULT s_StartListening(); private: - static HRESULT s_StopListeningLocked(); + static HRESULT s_StopListening(); }; // Disable warnings from the CoCreatableClass macro as the value it provides for diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 0a0b26aa93..95517373c7 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -780,12 +780,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void ConptyConnection::StartInboundListener() { - THROW_IF_FAILED(CTerminalHandoff::s_StartListening(&ConptyConnection::NewHandoff)); - } + static const auto init = []() noexcept { + CTerminalHandoff::s_setCallback(&ConptyConnection::NewHandoff); + return true; + }(); - void ConptyConnection::StopInboundListener() - { - THROW_IF_FAILED(CTerminalHandoff::s_StopListening()); + CTerminalHandoff::s_StartListening(); } // Function Description: diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index ca67588f64..69edd7b7a8 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -36,7 +36,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation WORD ShowWindow() const noexcept; static void StartInboundListener(); - static void StopInboundListener(); static winrt::event_token NewConnection(const NewConnectionHandler& handler); static void NewConnection(const winrt::event_token& token); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index 09466b9d11..e1ed83cc0c 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -23,7 +23,6 @@ namespace Microsoft.Terminal.TerminalConnection static event NewConnectionHandler NewConnection; static void StartInboundListener(); - static void StopInboundListener(); static Windows.Foundation.Collections.ValueSet CreateSettings(String cmdline, String startingDirectory, From 32ae00f71a10b727c355c49699a1b78dfd9e26cf Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 13 Mar 2025 00:29:36 +0100 Subject: [PATCH 002/177] Fix a ConPTY startup hang with 0-param DA1 responses (#18681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since `WaitForDA1` would wait until `_deviceAttributes` is non-zero, we must ensure it's actually non-zero at the end of this handler, even if there are no parameters. ## Validation Steps Performed * Mod the Terminal DA1 to be `\x1b[?6c`. No hang ✅ * Mod the Terminal DA1 to be `\x1b[?61c`. No hang ✅ --- .../parser/InputStateMachineEngine.cpp | 19 +++++++++++++------ .../parser/InputStateMachineEngine.hpp | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index af781a9c8d..aadcb0d5e0 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -471,15 +471,22 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter // We catch it here and store the information for later retrieval. if (_deviceAttributes.load(std::memory_order_relaxed) == 0) { - til::enumset attributes; + til::enumset attributes{ DeviceAttribute::__some__ }; // The first parameter denotes the conformance level. - if (parameters.at(0).value() >= 61) + const auto len = parameters.size(); + if (len >= 2 && parameters.at(0).value() >= 61) { - parameters.subspan(1).for_each([&](auto p) { - attributes.set(static_cast(p)); - return true; - }); + // NOTE: VTParameters::for_each will replace empty spans with a single default value. + // This means we could not distinguish between no parameters and a single default parameter. + for (size_t i = 1; i < len; i++) + { + const auto value = parameters.at(i).value(); + if (value > 0 && value < 64) + { + attributes.set(static_cast(value)); + } + } } _deviceAttributes.fetch_or(attributes.bits(), std::memory_order_relaxed); diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 7787fe5c0a..e91737d107 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -51,6 +51,10 @@ namespace Microsoft::Console::VirtualTerminal enum class DeviceAttribute : uint64_t { + // Special value to indicate that InputStateMachineEngine::_deviceAttributes has been set. + // 0 in this case means 1<<0 == 1, which in turn means that _deviceAttributes is non-zero. + __some__ = 0, + Columns132 = 1, PrinterPort = 2, Sixel = 4, From f023b3bfd2a578615d2d94d8991fc274076c665e Mon Sep 17 00:00:00 2001 From: aphistra <102989060+aphistra@users.noreply.github.com> Date: Thu, 13 Mar 2025 16:46:16 -0500 Subject: [PATCH 003/177] Remove unused MUXCustomBuildTasks package (#18683) It has a Component Governance alert (and no license), plus we aren't using it. --- build/packages.config | 1 - 1 file changed, 1 deletion(-) diff --git a/build/packages.config b/build/packages.config index cc0e0ada0b..3572da0e54 100644 --- a/build/packages.config +++ b/build/packages.config @@ -1,6 +1,5 @@ - From 7d8f7eb42994829e9f6f7a9d1d2a572b4b13e4ef Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 14 Mar 2025 17:03:26 -0500 Subject: [PATCH 004/177] Add support for language override to unpackaged/portable builds (#18684) It turns out that we *can* support language overrides--fairly easily, in fact!--by simply changing the default Language qualifier. I elected not to change how packaged language override works until we are certain this works properly everywhere. Consider it a healthy distrust of the Windows App Platform. Closes #18419 Closes #18336 Closes #17619 --- src/cascadia/TerminalApp/AppLogic.cpp | 10 ++++-- src/cascadia/TerminalApp/pch.h | 1 + .../TerminalSettingsEditor/Launch.xaml | 3 +- .../LaunchViewModel.cpp | 33 +++++++------------ .../TerminalSettingsEditor/LaunchViewModel.h | 1 - .../LaunchViewModel.idl | 1 - 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index b892c10cb5..b7e0eae91a 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -331,8 +331,16 @@ namespace winrt::TerminalApp::implementation void AppLogic::_ApplyLanguageSettingChange() noexcept try { + const auto language = _settings.GlobalSettings().Language(); + if (!IsPackaged()) { + if (!language.empty()) + { + // We cannot use the packaged app API, PrimaryLanguageOverride, but we *can* tell the resource loader + // to set the Language for all loaded resources to the user's preferred language. + winrt::Windows::ApplicationModel::Resources::Core::ResourceContext::SetGlobalQualifierValue(L"Language", language); + } return; } @@ -340,8 +348,6 @@ namespace winrt::TerminalApp::implementation // NOTE: PrimaryLanguageOverride throws if this instance is unpackaged. const auto primaryLanguageOverride = ApplicationLanguages::PrimaryLanguageOverride(); - const auto language = _settings.GlobalSettings().Language(); - if (primaryLanguageOverride != language) { ApplicationLanguages::PrimaryLanguageOverride(language); diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h index 394990ee34..85b7090a93 100644 --- a/src/cascadia/TerminalApp/pch.h +++ b/src/cascadia/TerminalApp/pch.h @@ -27,6 +27,7 @@ #include #include +#include #include #include #include diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 8aeca2f92b..44feee7016 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -140,8 +140,7 @@ - + diff --git a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp index 440f43211a..cf99cadb9a 100644 --- a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp @@ -82,16 +82,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return language.NativeName(); } - // Returns whether the language selector is available/shown. - // - // winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride() - // doesn't work for unpackaged applications. The corresponding code in TerminalApp is disabled. - // It would be confusing for our users if we presented a dysfunctional language selector. - bool LaunchViewModel::LanguageSelectorAvailable() - { - return IsPackaged(); - } - // Returns the list of languages the user may override the application language with. // The returned list are BCP 47 language tags like {"und", "en-US", "de-DE", "es-ES", ...}. // "und" is short for "undefined" and is synonymous for "Use system language" in this code. @@ -102,12 +92,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _languageList; } - if (!LanguageSelectorAvailable()) - { - _languageList = {}; - return _languageList; - } - // In order to return the language list this code does the following: // [1] Get all possible languages we want to allow the user to choose. // We have to acquire languages from multiple sources, creating duplicates. See below at [1]. @@ -179,19 +163,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _currentLanguage; } - if (!LanguageSelectorAvailable()) + winrt::hstring currentLanguage; + if (IsPackaged()) { - _currentLanguage = {}; - return _currentLanguage; + // NOTE: PrimaryLanguageOverride throws if this instance is unpackaged. + currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride(); + } + else + { + if (_Settings.GlobalSettings().HasLanguage()) + { + currentLanguage = _Settings.GlobalSettings().Language(); + } } - // NOTE: PrimaryLanguageOverride throws if this instance is unpackaged. - auto currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride(); if (currentLanguage.empty()) { currentLanguage = systemLanguageTag; } - _currentLanguage = winrt::box_value(currentLanguage); return _currentLanguage; } diff --git a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h index d96046524f..98851b74dc 100644 --- a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h @@ -20,7 +20,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // "Deutsch (Deutschland)". This works independently of the user's locale. static winrt::hstring LanguageDisplayConverter(const winrt::hstring& tag); - bool LanguageSelectorAvailable(); winrt::Windows::Foundation::Collections::IObservableVector LanguageList(); winrt::Windows::Foundation::IInspectable CurrentLanguage(); void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag); diff --git a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl index 3781c24b87..fe39cbccb1 100644 --- a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl @@ -10,7 +10,6 @@ namespace Microsoft.Terminal.Settings.Editor runtimeclass LaunchViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { static String LanguageDisplayConverter(String tag); - Boolean LanguageSelectorAvailable { get; }; Windows.Foundation.Collections.IObservableVector LanguageList { get; }; IInspectable CurrentLanguage; From 70f85a4a35ea4b050d5d8bd9a2f3f7f177f990ed Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 14 Mar 2025 23:06:01 +0100 Subject: [PATCH 005/177] Fix a shutdown race condition in ControlCore (#18632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I found multiple issues while investigating this: * Render thread shutdown is racy, because it doesn't actually stop the render thread. * Lifetime management in `ControlCore` failed to account for the circular dependency of render thread --> renderer --> render data --> terminal --> renderer --> render thread. Fixed by reordering the `ControlCore` members to ensure their correct destruction. * Ensured that the connection setter calls close on the previous connection. (Hopefully) Closes #18598 ## Validation Steps Performed * Can't repro the original failure ❌ * Opening and closing tabs as fast as possible doesn't crash anymore ✅ * Detaching and reattaching a tab producing continuous output ✅ --- .../TerminalApp/DebugTapConnection.cpp | 9 +- src/cascadia/TerminalControl/ControlCore.cpp | 91 +++--- src/cascadia/TerminalControl/ControlCore.h | 128 ++++---- .../TerminalControl/ControlInteractivity.cpp | 2 +- src/cascadia/TerminalControl/HwndTerminal.cpp | 8 +- src/host/srvinit.cpp | 11 +- src/host/ut_host/ScreenBufferTests.cpp | 4 - src/host/ut_host/SearchTests.cpp | 6 - src/inc/test/CommonState.hpp | 14 - src/interactivity/onecore/ConIoSrvComm.cpp | 53 +++- src/renderer/base/renderer.cpp | 72 +---- src/renderer/base/renderer.hpp | 18 +- src/renderer/base/thread.cpp | 297 +++--------------- src/renderer/base/thread.hpp | 23 +- src/renderer/inc/DummyRenderer.hpp | 2 +- 15 files changed, 253 insertions(+), 485 deletions(-) diff --git a/src/cascadia/TerminalApp/DebugTapConnection.cpp b/src/cascadia/TerminalApp/DebugTapConnection.cpp index 3a4f670c38..41416c3b98 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.cpp +++ b/src/cascadia/TerminalApp/DebugTapConnection.cpp @@ -60,9 +60,12 @@ namespace winrt::Microsoft::TerminalApp::implementation DebugTapConnection::DebugTapConnection(ITerminalConnection wrappedConnection) { - _outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { this, &DebugTapConnection::_OutputHandler }); - _stateChangedRevoker = wrappedConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*e*/) { - StateChanged.raise(*this, nullptr); + _outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { get_weak(), &DebugTapConnection::_OutputHandler }); + _stateChangedRevoker = wrappedConnection.StateChanged(winrt::auto_revoke, [weak = get_weak()](auto&& /*s*/, auto&& /*e*/) { + if (const auto self = weak.get()) + { + self->StateChanged.raise(*self, nullptr); + } }); _wrappedConnection = wrappedConnection; } diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 72a8e85ad5..7c57fda7d0 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -142,23 +142,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation // If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach // the UIA Engine to the renderer. This prevents us from signaling changes to the cursor or buffer. { - // First create the render thread. - // Then stash a local pointer to the render thread so we can initialize it and enable it - // to paint itself *after* we hand off its ownership to the renderer. - // We split up construction and initialization of the render thread object this way - // because the renderer and render thread have circular references to each other. - auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>(); - auto* const localPointerToThread = renderThread.get(); - // Now create the renderer and initialize the render thread. const auto& renderSettings = _terminal->GetRenderSettings(); - _renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get(), nullptr, 0, std::move(renderThread)); + _renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get()); _renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); }); _renderer->SetFrameColorChangedCallback([this]() { _rendererTabColorChanged(); }); _renderer->SetRendererEnteredErrorStateCallback([this]() { RendererEnteredErrorState.raise(nullptr, nullptr); }); - - THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get())); } UpdateSettings(settings, unfocusedAppearance); @@ -186,7 +176,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // thread is a workaround for us to hit GH#12607 less often. shared->outputIdle = std::make_unique>( std::chrono::milliseconds{ 100 }, - [weakTerminal = std::weak_ptr{ _terminal }, weakThis = get_weak(), dispatcher = _dispatcher]() { + [this, weakThis = get_weak(), dispatcher = _dispatcher]() { dispatcher.TryEnqueue(DispatcherQueuePriority::Normal, [weakThis]() { if (const auto self = weakThis.get(); self && !self->_IsClosing()) { @@ -194,22 +184,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation } }); - if (const auto t = weakTerminal.lock()) - { - const auto lock = t->LockForWriting(); - t->UpdatePatternsUnderLock(); - } + // We can't use a `weak_ptr` to `_terminal` here, because it takes significant + // dependency on the lifetime of `this` (primarily on our `_renderer`). + // and a `weak_ptr` would allow it to outlive `this`. + // Theoretically `debounced_func_trailing` should call `WaitForThreadpoolTimerCallbacks()` + // with cancel=true on destruction, which should ensure that our use of `this` here is safe. + const auto lock = _terminal->LockForWriting(); + _terminal->UpdatePatternsUnderLock(); }); // If you rapidly show/hide Windows Terminal, something about GotFocus()/LostFocus() gets broken. // We'll then receive easily 10+ such calls from WinUI the next time the application is shown. shared->focusChanged = std::make_unique>( std::chrono::milliseconds{ 25 }, - [weakThis = get_weak()](const bool focused) { - if (const auto core{ weakThis.get() }) - { - core->_focusChanged(focused); - } + [this](const bool focused) { + // Theoretically `debounced_func_trailing` should call `WaitForThreadpoolTimerCallbacks()` + // with cancel=true on destruction, which should ensure that our use of `this` here is safe. + _focusChanged(focused); }); // Scrollbar updates are also expensive (XAML), so we'll throttle them as well. @@ -224,19 +215,35 @@ namespace winrt::Microsoft::Terminal::Control::implementation }); } + // Safely disconnects event handlers from the connection and closes it. This is necessary because + // WinRT event revokers don't prevent pending calls from proceeding (thread-safe but not race-free). + void ControlCore::_closeConnection() + { + _connectionOutputEventRevoker.revoke(); + _connectionStateChangedRevoker.revoke(); + + // One of the tasks for `ITerminalConnection::Close()` is to block until all pending + // callback calls have completed. This solves the race-condition issue mentioned above. + if (_connection) + { + _connection.Close(); + _connection = nullptr; + } + } + ControlCore::~ControlCore() { Close(); - _renderer.reset(); - _renderEngine.reset(); + // See notes about the _renderer member in the header file. + _renderer->TriggerTeardown(); } void ControlCore::Detach() { // Disable the renderer, so that it doesn't try to start any new frames // for our engines while we're not attached to anything. - _renderer->WaitForPaintCompletionAndDisable(INFINITE); + _renderer->TriggerTeardown(); // Clear out any throttled funcs that we had wired up to run on this UI // thread. These will be recreated in _setupDispatcherAndCallbacks, when @@ -276,8 +283,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto oldState = ConnectionState(); // rely on ControlCore's automatic null handling // revoke ALL old handlers immediately - _connectionOutputEventRevoker.revoke(); - _connectionStateChangedRevoker.revoke(); + _closeConnection(); _connection = newConnection; if (_connection) @@ -366,7 +372,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); const auto width = vp.Width(); const auto height = vp.Height(); - _connection.Resize(height, width); + + if (_connection) + { + _connection.Resize(height, width); + } if (_owningHwnd != 0) { @@ -420,6 +430,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { if (_initializedTerminal.load(std::memory_order_relaxed)) { + // The lock must be held, because it calls into IRenderData which is shared state. const auto lock = _terminal->LockForWriting(); _renderer->EnablePainting(); } @@ -434,7 +445,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::_sendInputToConnection(std::wstring_view wstr) { - _connection.WriteInput(winrt_wstring_to_array_view(wstr)); + if (_connection) + { + _connection.WriteInput(winrt_wstring_to_array_view(wstr)); + } } // Method Description: @@ -471,7 +485,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const wchar_t CtrlD = 0x4; const wchar_t Enter = '\r'; - if (_connection.State() >= winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Closed) + if (_connection && _connection.State() >= winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Closed) { if (ch == CtrlD) { @@ -1122,7 +1136,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - _connection.Resize(vp.Height(), vp.Width()); + if (_connection) + { + _connection.Resize(vp.Height(), vp.Width()); + } // TermControl will call Search() once the OutputIdle even fires after 100ms. // Until then we need to hide the now-stale search results from the renderer. @@ -1794,12 +1811,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Ensure Close() doesn't hang, waiting for MidiAudio to finish playing an hour long song. _midiAudio.BeginSkip(); - - // Stop accepting new output and state changes before we disconnect everything. - _connectionOutputEventRevoker.revoke(); - _connectionStateChangedRevoker.revoke(); - _connection.Close(); } + + _closeConnection(); } void ControlCore::PersistToPath(const wchar_t* path) const @@ -1896,7 +1910,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto weakThis{ get_weak() }; - // Concurrent read of _dispatcher is safe, because Detach() calls WaitForPaintCompletionAndDisable() + // Concurrent read of _dispatcher is safe, because Detach() calls TriggerTeardown() // which blocks until this call returns. _dispatcher will only be changed afterwards. co_await wil::resume_foreground(_dispatcher); @@ -1947,8 +1961,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::ResumeRendering() { + // The lock must be held, because it calls into IRenderData which is shared state. const auto lock = _terminal->LockForWriting(); - _renderer->ResetErrorStateAndResume(); + _renderer->EnablePainting(); } bool ControlCore::IsVtMouseModeEnabled() const diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 4881585df4..8e99e26155 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -311,65 +311,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::shared_ptr> updateScrollBar; }; - std::atomic _initializedTerminal{ false }; - bool _closing{ false }; - - TerminalConnection::ITerminalConnection _connection{ nullptr }; - TerminalConnection::ITerminalConnection::TerminalOutput_revoker _connectionOutputEventRevoker; - TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker; - - winrt::com_ptr _settings{ nullptr }; - - std::shared_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr }; - std::wstring _pendingResponses; - - // NOTE: _renderEngine must be ordered before _renderer. - // - // As _renderer has a dependency on _renderEngine (through a raw pointer) - // we must ensure the _renderer is deallocated first. - // (C++ class members are destroyed in reverse order.) - std::unique_ptr<::Microsoft::Console::Render::Atlas::AtlasEngine> _renderEngine{ nullptr }; - std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr }; - - ::Search _searcher; - bool _snapSearchResultToSelection; - - winrt::handle _lastSwapChainHandle{ nullptr }; - - FontInfoDesired _desiredFont; - FontInfo _actualFont; - bool _builtinGlyphs = true; - bool _colorGlyphs = true; - CSSLengthPercentage _cellWidth; - CSSLengthPercentage _cellHeight; - - // storage location for the leading surrogate of a utf-16 surrogate pair - std::optional _leadingSurrogate{ std::nullopt }; - - std::optional _lastHoveredCell{ std::nullopt }; - // Track the last hyperlink ID we hovered over - uint16_t _lastHoveredId{ 0 }; - - bool _isReadOnly{ false }; - - std::optional::interval> _lastHoveredInterval{ std::nullopt }; - - // These members represent the size of the surface that we should be - // rendering to. - float _panelWidth{ 0 }; - float _panelHeight{ 0 }; - float _compositionScale{ 0 }; - - uint64_t _owningHwnd{ 0 }; - - winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr }; - til::shared_mutex _shared; - - til::point _contextMenuBufferPosition{ 0, 0 }; - - Windows::Foundation::Collections::IVector _cachedQuickFixes{ nullptr }; - void _setupDispatcherAndCallbacks(); + void _closeConnection(); bool _setFontSizeUnderLock(float fontSize); void _updateFont(); @@ -396,12 +339,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _terminalWindowSizeChanged(int32_t width, int32_t height); safe_void_coroutine _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); - #pragma endregion - MidiAudio _midiAudio; - winrt::Windows::System::DispatcherQueueTimer _midiAudioSkipTimer{ nullptr }; - #pragma region RendererCallbacks void _rendererWarning(const HRESULT hr, wil::zwstring_view parameter); safe_void_coroutine _renderEngineSwapChainChanged(const HANDLE handle); @@ -412,6 +351,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _raiseReadOnlyWarning(); void _updateAntiAliasingMode(); void _connectionOutputHandler(const hstring& hstr); + void _connectionStateChangedHandler(const TerminalConnection::ITerminalConnection&, const Windows::Foundation::IInspectable&); void _updateHoveredCell(const std::optional terminalPosition); void _setOpacity(const float opacity, const bool focused = true); @@ -443,6 +383,70 @@ namespace winrt::Microsoft::Terminal::Control::implementation return _closing; } + // Caches responses generated by our VT parser (= improved batching). + std::wstring _pendingResponses; + + // Font stuff. + FontInfoDesired _desiredFont; + FontInfo _actualFont; + bool _builtinGlyphs = true; + bool _colorGlyphs = true; + CSSLengthPercentage _cellWidth; + CSSLengthPercentage _cellHeight; + + // Rendering stuff. + winrt::handle _lastSwapChainHandle{ nullptr }; + uint64_t _owningHwnd{ 0 }; + float _panelWidth{ 0 }; + float _panelHeight{ 0 }; + float _compositionScale{ 0 }; + + // Audio stuff. + MidiAudio _midiAudio; + winrt::Windows::System::DispatcherQueueTimer _midiAudioSkipTimer{ nullptr }; + + // Other stuff. + winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr }; + winrt::com_ptr _settings{ nullptr }; + til::point _contextMenuBufferPosition{ 0, 0 }; + Windows::Foundation::Collections::IVector _cachedQuickFixes{ nullptr }; + ::Search _searcher; + std::optional::interval> _lastHoveredInterval; + std::optional _leadingSurrogate; + std::optional _lastHoveredCell; + uint16_t _lastHoveredId{ 0 }; + std::atomic _initializedTerminal{ false }; + bool _isReadOnly{ false }; + bool _closing{ false }; + + // ---------------------------------------------------------------------------------------- + // These are ordered last to ensure they're destroyed first. + // This ensures that their respective contents stops taking dependency on the above. + // I recommend reading the following paragraphs in reverse order. + // ---------------------------------------------------------------------------------------- + + // ↑ This one is tricky - all of these are raw pointers: + // 1. _terminal depends on _renderer (for invalidations) + // 2. _renderer depends on _terminal (for IRenderData) + // = circular dependency = architectural flaw (lifetime issues) = TODO + // 3. _renderer depends on _renderEngine (AtlasEngine) + // To solve the knot, we manually stop the renderer in the destructor, + // which breaks 2. We can proceed then proceed to break 1. and then 3. + std::unique_ptr<::Microsoft::Console::Render::Atlas::AtlasEngine> _renderEngine{ nullptr }; // 3. + std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr }; // 3. + std::shared_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr }; // 1. + + // ↑ MOST IMPORTANTLY: `_outputIdle` takes dependency on the raw `this` pointer (necessarily). + // Destroying SharedState here will block until all pending `debounced_func_trailing` calls are completed. + til::shared_mutex _shared; + + // ↑ Prevent any more unnecessary `_outputIdle` calls. + // Technically none of these members are destroyed here. Instead, the destructor will call Close() + // which calls _closeConnection() which in turn manually & safely destroys them in the correct order. + TerminalConnection::ITerminalConnection::TerminalOutput_revoker _connectionOutputEventRevoker; + TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker; + TerminalConnection::ITerminalConnection _connection{ nullptr }; + friend class ControlUnitTests::ControlCoreTests; friend class ControlUnitTests::ControlInteractivityTests; bool _inUnitTests{ false }; diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index 6587f9777b..f289912145 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -73,7 +73,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // // To alleviate, make sure to disable the UIA engine and remove it, // and ALSO disable the renderer. Core.Detach will take care of the - // WaitForPaintCompletionAndDisable (which will stop the renderer + // TriggerTeardown (which will stop the renderer // after all current engines are done painting). // // Simply disabling the UIA engine is not enough, because it's diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index fe7c138ce4..d2aa1e1796 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -206,14 +206,10 @@ HRESULT HwndTerminal::Initialize() _terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>(); const auto lock = _terminal->LockForWriting(); - auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>(); - auto* const localPointerToThread = renderThread.get(); auto& renderSettings = _terminal->GetRenderSettings(); renderSettings.SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, RGB(12, 12, 12)); renderSettings.SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, RGB(204, 204, 204)); - _renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get(), nullptr, 0, std::move(renderThread)); - RETURN_HR_IF_NULL(E_POINTER, localPointerToThread); - RETURN_IF_FAILED(localPointerToThread->Initialize(_renderer.get())); + _renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get()); auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); RETURN_IF_FAILED(engine->SetHwnd(_hwnd.get())); @@ -234,7 +230,7 @@ HRESULT HwndTerminal::Initialize() _terminal->Create({ 80, 25 }, 9001, *_renderer); _terminal->SetWriteInputCallback([=](std::wstring_view input) noexcept { _WriteTextToConnection(input); }); - localPointerToThread->EnablePainting(); + _renderer->EnablePainting(); _multiClickTime = std::chrono::milliseconds{ GetDoubleClickTime() }; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index a6627bde99..cd68c921e9 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -842,16 +842,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, { if (!gci.IsInVtIoMode()) { - auto renderThread = std::make_unique(); - // stash a local pointer to the thread here - - // We're going to give ownership of the thread to the Renderer, - // but the thread also need to be told who its renderer is, - // and we can't do that until the renderer is constructed. - auto* const localPointerToThread = renderThread.get(); - - g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, std::move(renderThread)); - - THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender)); + g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData); // Set up the renderer to be used to calculate the width of a glyph, // should we be unable to figure out its width another way. diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index a81d47ec93..31ec3a747f 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -44,7 +44,6 @@ class ScreenBufferTests m_state->InitEvents(); m_state->PrepareGlobalFont({ 1, 1 }); - m_state->PrepareGlobalRenderer(); m_state->PrepareGlobalInputBuffer(); m_state->PrepareGlobalScreenBuffer(); @@ -54,7 +53,6 @@ class ScreenBufferTests TEST_CLASS_CLEANUP(ClassCleanup) { m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalRenderer(); m_state->CleanupGlobalInputBuffer(); delete m_state; @@ -581,8 +579,6 @@ void ScreenBufferTests::TestResetClearTabStops() // Reset the screen buffer to test the defaults. m_state->CleanupNewTextBufferInfo(); m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalRenderer(); - m_state->PrepareGlobalRenderer(); m_state->PrepareGlobalScreenBuffer(); m_state->PrepareNewTextBufferInfo(); diff --git a/src/host/ut_host/SearchTests.cpp b/src/host/ut_host/SearchTests.cpp index d37c3556d4..d7ba067e76 100644 --- a/src/host/ut_host/SearchTests.cpp +++ b/src/host/ut_host/SearchTests.cpp @@ -23,20 +23,14 @@ class SearchTests TEST_CLASS_SETUP(ClassSetup) { m_state = new CommonState(); - - m_state->PrepareGlobalRenderer(); m_state->PrepareGlobalScreenBuffer(); - return true; } TEST_CLASS_CLEANUP(ClassCleanup) { m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalRenderer(); - delete m_state; - return true; } diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp index e79bc74d3e..66ddfb5b00 100644 --- a/src/inc/test/CommonState.hpp +++ b/src/inc/test/CommonState.hpp @@ -66,20 +66,6 @@ public: m_pFontInfo = { L"Consolas", 0, 0, coordFontSize, 0 }; } - void PrepareGlobalRenderer() - { - Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals(); - CONSOLE_INFORMATION& gci = g.getConsoleInformation(); - g.pRender = new Microsoft::Console::Render::Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr); - } - - void CleanupGlobalRenderer() - { - Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals(); - delete g.pRender; - g.pRender = nullptr; - } - void PrepareGlobalScreenBuffer(const til::CoordType viewWidth = s_csWindowWidth, const til::CoordType viewHeight = s_csWindowHeight, const til::CoordType bufferWidth = s_csBufferWidth, diff --git a/src/interactivity/onecore/ConIoSrvComm.cpp b/src/interactivity/onecore/ConIoSrvComm.cpp index 3c543e442f..c782e25303 100644 --- a/src/interactivity/onecore/ConIoSrvComm.cpp +++ b/src/interactivity/onecore/ConIoSrvComm.cpp @@ -393,7 +393,58 @@ VOID ConIoSrvComm::HandleFocusEvent(const CIS_EVENT* const Event) { // Wait for the currently running paint operation, if any, // and prevent further attempts to render. - Renderer->WaitForPaintCompletionAndDisable(1000); + // + // When rendering takes place via DirectX, and a console application + // currently owns the screen, and a new console application is launched (or + // the user switches to another console application), the new application + // cannot take over the screen until the active one relinquishes it. This + // blocking mechanism goes as follows: + // + // 1. The console input thread of the new console application connects to + // ConIoSrv; + // 2. While servicing the new connection request, ConIoSrv sends an event to + // the active application letting it know that it has lost focus; + // 3.1 ConIoSrv waits for a reply from the client application; + // 3.2 Meanwhile, the active application receives the focus event and calls + // this method, waiting for the current paint operation to + // finish. + // + // This means that the new application is waiting on the connection request + // reply from ConIoSrv, ConIoSrv is waiting on the active application to + // acknowledge the lost focus event to reply to the new application, and the + // console input thread in the active application is waiting on the renderer + // thread to finish its current paint operation. + // + // Question: what should happen if the wait on the paint operation times + // out? + // + // There are three options: + // + // 1. On timeout, the active console application could reply with an error + // message and terminate itself, effectively relinquishing control of the + // display; + // + // 2. ConIoSrv itself could time out on waiting for a reply, and forcibly + // terminate the active console application; + // + // 3. Let the wait time out and let the user deal with it. Because the wait + // occurs on a single iteration of the renderer thread, it seemed to me that + // the likelihood of failure is extremely small, especially since the client + // console application that the active conhost instance is servicing has no + // say over what happens in the renderer thread, only by proxy. Thus, the + // chance of failure (timeout) is minimal and since the OneCoreUAP console + // is not a massively used piece of software, it didn’t seem that it would + // be a good use of time to build the requisite infrastructure to deal with + // a timeout here, at least not for now. In case of a timeout DirectX will + // catch the mistake of a new application attempting to acquire the display + // while another one still owns it and will flag it as a DWM bug. Right now, + // the active application will wait one second for the paint operation to + // finish. + // + // TODO: MSFT: 11833883 - Determine action when wait on paint operation via + // DirectX on OneCoreUAP times out while switching console + // applications. + Renderer->TriggerTeardown(); // Relinquish control of the graphics device (only one // DirectX application may control the device at any one diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 31ab5a410e..90f6d5936f 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -25,35 +25,12 @@ static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 }; // - Creates a new renderer controller for a console. // Arguments: // - pData - The interface to console data structures required for rendering -// - pEngine - The output engine for targeting each rendering frame // Return Value: // - An instance of a Renderer. -Renderer::Renderer(const RenderSettings& renderSettings, - IRenderData* pData, - _In_reads_(cEngines) IRenderEngine** const rgpEngines, - const size_t cEngines, - std::unique_ptr thread) : +Renderer::Renderer(const RenderSettings& renderSettings, IRenderData* pData) : _renderSettings(renderSettings), - _pData(pData), - _pThread{ std::move(thread) } + _pData(pData) { - for (size_t i = 0; i < cEngines; i++) - { - AddRenderEngine(rgpEngines[i]); - } -} - -// Routine Description: -// - Destroys an instance of a renderer -// Arguments: -// - -// Return Value: -// - -Renderer::~Renderer() -{ - // RenderThread blocks until it has shut down. - _destructing = true; - _pThread.reset(); } IRenderData* Renderer::GetRenderData() const noexcept @@ -72,11 +49,6 @@ IRenderData* Renderer::GetRenderData() const noexcept auto tries = maxRetriesForRenderEngine; while (tries > 0) { - if (_destructing) - { - return S_FALSE; - } - // BODGY: Optimally we would want to retry per engine, but that causes different // problems (intermittent inconsistent states between text renderer and UIA output, // not being able to lock the cursor location, etc.). @@ -91,7 +63,7 @@ IRenderData* Renderer::GetRenderData() const noexcept if (--tries == 0) { // Stop trying. - _pThread->DisablePainting(); + _thread.DisablePainting(); if (_pfnRendererEnteredErrorState) { _pfnRendererEnteredErrorState(); @@ -207,12 +179,8 @@ CATCH_RETURN() void Renderer::NotifyPaintFrame() noexcept { - // If we're running in the unittests, we might not have a render thread. - if (_pThread) - { - // The thread will provide throttling for us. - _pThread->NotifyPaint(); - } + // The thread will provide throttling for us. + _thread.NotifyPaint(); } // Routine Description: @@ -315,7 +283,7 @@ void Renderer::TriggerRedrawAll(const bool backgroundChanged, const bool frameCh void Renderer::TriggerTeardown() noexcept { // We need to shut down the paint thread on teardown. - _pThread->WaitForPaintCompletionAndDisable(INFINITE); + _thread.TriggerTeardown(); } // Routine Description: @@ -637,25 +605,7 @@ void Renderer::EnablePainting() // When the renderer is constructed, the initial viewport won't be available yet, // but once EnablePainting is called it should be safe to retrieve. _viewport = _pData->GetViewport(); - - // When running the unit tests, we may be using a render without a render thread. - if (_pThread) - { - _pThread->EnablePainting(); - } -} - -// Routine Description: -// - Waits for the current paint operation to complete, if any, up to the specified timeout. -// - Resets an event in the render thread that precludes it from advancing, thus disabling rendering. -// - If no paint operation is currently underway, returns immediately. -// Arguments: -// - dwTimeoutMs - Milliseconds to wait for the current paint operation to complete, if any (can be INFINITE). -// Return Value: -// - -void Renderer::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) -{ - _pThread->WaitForPaintCompletionAndDisable(dwTimeoutMs); + _thread.EnablePainting(); } // Routine Description: @@ -1437,14 +1387,6 @@ void Renderer::SetRendererEnteredErrorStateCallback(std::function pfn) _pfnRendererEnteredErrorState = std::move(pfn); } -// Method Description: -// - Attempts to restart the renderer. -void Renderer::ResetErrorStateAndResume() -{ - // because we're not stateful (we could be in the future), all we want to do is reenable painting. - EnablePainting(); -} - void Renderer::UpdateHyperlinkHoveredId(uint16_t id) noexcept { _hyperlinkHoveredId = id; diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 8921937462..d5f6be56d1 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -28,13 +28,7 @@ namespace Microsoft::Console::Render class Renderer { public: - Renderer(const RenderSettings& renderSettings, - IRenderData* pData, - _In_reads_(cEngines) IRenderEngine** const pEngine, - const size_t cEngines, - std::unique_ptr thread); - - ~Renderer(); + Renderer(const RenderSettings& renderSettings, IRenderData* pData); IRenderData* GetRenderData() const noexcept; @@ -71,7 +65,6 @@ namespace Microsoft::Console::Render bool IsGlyphWideByFont(const std::wstring_view glyph); void EnablePainting(); - void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs); void WaitUntilCanRender(); void AddRenderEngine(_In_ IRenderEngine* const pEngine); @@ -80,7 +73,6 @@ namespace Microsoft::Console::Render void SetBackgroundColorChangedCallback(std::function pfn); void SetFrameColorChangedCallback(std::function pfn); void SetRendererEnteredErrorStateCallback(std::function pfn); - void ResetErrorStateAndResume(); void UpdateHyperlinkHoveredId(uint16_t id) noexcept; void UpdateLastHoveredInterval(const std::optional::interval>& newInterval); @@ -121,23 +113,25 @@ namespace Microsoft::Console::Render const RenderSettings& _renderSettings; std::array _engines{}; IRenderData* _pData = nullptr; // Non-ownership pointer - std::unique_ptr _pThread; static constexpr size_t _firstSoftFontChar = 0xEF20; size_t _lastSoftFontChar = 0; uint16_t _hyperlinkHoveredId = 0; std::optional::interval> _hoveredInterval; Microsoft::Console::Types::Viewport _viewport; - CursorOptions _currentCursorOptions; + CursorOptions _currentCursorOptions{}; std::optional _compositionCache; std::vector _clusterBuffer; std::function _pfnBackgroundColorChanged; std::function _pfnFrameColorChanged; std::function _pfnRendererEnteredErrorState; - bool _destructing = false; bool _forceUpdateViewport = false; til::point_span _lastSelectionPaintSpan{}; size_t _lastSelectionPaintSize{}; std::vector _lastSelectionRectsByViewport{}; + + // Ordered last, so that it gets destroyed first. + // This ensures that the render thread stops accessing us. + RenderThread _thread{ this }; }; } diff --git a/src/renderer/base/thread.cpp b/src/renderer/base/thread.cpp index 904cf2075a..467bef7358 100644 --- a/src/renderer/base/thread.cpp +++ b/src/renderer/base/thread.cpp @@ -10,212 +10,40 @@ using namespace Microsoft::Console::Render; -RenderThread::RenderThread() : - _pRenderer(nullptr), - _hThread(nullptr), - _hEvent(nullptr), - _hPaintCompletedEvent(nullptr), - _fKeepRunning(true), - _hPaintEnabledEvent(nullptr), - _fNextFrameRequested(false), - _fWaiting(false) +RenderThread::RenderThread(Renderer* renderer) : + renderer(renderer) { } RenderThread::~RenderThread() { - if (_hThread) - { - _fKeepRunning = false; // stop loop after final run - EnablePainting(); // if we want to get the last frame out, we need to make sure it's enabled - SignalObjectAndWait(_hEvent, _hThread, INFINITE, FALSE); // signal final paint and wait for thread to finish. - - CloseHandle(_hThread); - _hThread = nullptr; - } - - if (_hEvent) - { - CloseHandle(_hEvent); - _hEvent = nullptr; - } - - if (_hPaintEnabledEvent) - { - CloseHandle(_hPaintEnabledEvent); - _hPaintEnabledEvent = nullptr; - } - - if (_hPaintCompletedEvent) - { - CloseHandle(_hPaintCompletedEvent); - _hPaintCompletedEvent = nullptr; - } -} - -// Method Description: -// - Create all of the Events we'll need, and the actual thread we'll be doing -// work on. -// Arguments: -// - pRendererParent: the Renderer that owns this thread, and which we should -// trigger frames for. -// Return Value: -// - S_OK if we succeeded, else an HRESULT corresponding to a failure to create -// an Event or Thread. -[[nodiscard]] HRESULT RenderThread::Initialize(Renderer* const pRendererParent) noexcept -{ - _pRenderer = pRendererParent; - - auto hr = S_OK; - // Create event before thread as thread will start immediately. - if (SUCCEEDED(hr)) - { - auto hEvent = CreateEventW(nullptr, // non-inheritable security attributes - FALSE, // auto reset event - FALSE, // initially unsignaled - nullptr // no name - ); - - if (hEvent == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hEvent = hEvent; - } - } - - if (SUCCEEDED(hr)) - { - auto hPaintEnabledEvent = CreateEventW(nullptr, - TRUE, // manual reset event - FALSE, // initially signaled - nullptr); - - if (hPaintEnabledEvent == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hPaintEnabledEvent = hPaintEnabledEvent; - } - } - - if (SUCCEEDED(hr)) - { - auto hPaintCompletedEvent = CreateEventW(nullptr, - TRUE, // manual reset event - TRUE, // initially signaled - nullptr); - - if (hPaintCompletedEvent == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hPaintCompletedEvent = hPaintCompletedEvent; - } - } - - if (SUCCEEDED(hr)) - { - auto hThread = CreateThread(nullptr, // non-inheritable security attributes - 0, // use default stack size - s_ThreadProc, - this, - 0, // create immediately - nullptr // we don't need the thread ID - ); - - if (hThread == nullptr) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - else - { - _hThread = hThread; - - // SetThreadDescription only works on 1607 and higher. If we cannot find it, - // then it's no big deal. Just skip setting the description. - auto func = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"kernel32.dll"), SetThreadDescription); - if (func) - { - LOG_IF_FAILED(func(hThread, L"Rendering Output Thread")); - } - } - } - - return hr; + TriggerTeardown(); } DWORD WINAPI RenderThread::s_ThreadProc(_In_ LPVOID lpParameter) { const auto pContext = static_cast(lpParameter); - - if (pContext != nullptr) - { - return pContext->_ThreadProc(); - } - else - { - return (DWORD)E_INVALIDARG; - } + return pContext->_ThreadProc(); } DWORD WINAPI RenderThread::_ThreadProc() { - while (_fKeepRunning) + while (true) { + _enable.wait(); + // Between waiting on _hEvent and calling PaintFrame() there should be a minimal delay, // so that a key press progresses to a drawing operation as quickly as possible. // As such, we wait for the renderer to complete _before_ waiting on _hEvent. - _pRenderer->WaitUntilCanRender(); + renderer->WaitUntilCanRender(); - WaitForSingleObject(_hPaintEnabledEvent, INFINITE); - - if (!_fNextFrameRequested.exchange(false, std::memory_order_acq_rel)) + _redraw.wait(); + if (!_keepRunning.load(std::memory_order_relaxed)) { - // <-- - // If `NotifyPaint` is called at this point, then it will not - // set the event because `_fWaiting` is not `true` yet so we have - // to check again below. - - _fWaiting.store(true, std::memory_order_release); - - // check again now (see comment above) - if (!_fNextFrameRequested.exchange(false, std::memory_order_acq_rel)) - { - // Wait until a next frame is requested. - WaitForSingleObject(_hEvent, INFINITE); - } - - // <-- - // If `NotifyPaint` is called at this point, then it _will_ set - // the event because `_fWaiting` is `true`, but we're not waiting - // anymore! - // This can probably happen quite often: imagine a scenario where - // we are waiting, and the terminal calls `NotifyPaint` twice - // very quickly. - // In that case, both calls might end up calling `SetEvent`. The - // first one will resume this thread and the second one will - // `SetEvent` the event. So the next time we wait, the event will - // already be set and we won't actually wait. - // Because it can happen often, and because rendering is an - // expensive operation, we should reset the event to not render - // again if nothing changed. - - _fWaiting.store(false, std::memory_order_release); - - // see comment above - ResetEvent(_hEvent); + break; } - ResetEvent(_hPaintCompletedEvent); - LOG_IF_FAILED(_pRenderer->PaintFrame()); - SetEvent(_hPaintCompletedEvent); + LOG_IF_FAILED(renderer->PaintFrame()); } return S_OK; @@ -223,79 +51,54 @@ DWORD WINAPI RenderThread::_ThreadProc() void RenderThread::NotifyPaint() noexcept { - if (_fWaiting.load(std::memory_order_acquire)) - { - SetEvent(_hEvent); - } - else - { - _fNextFrameRequested.store(true, std::memory_order_release); - } + _redraw.SetEvent(); } +// Spawns a new rendering thread if none exists yet. void RenderThread::EnablePainting() noexcept { - SetEvent(_hPaintEnabledEvent); + const auto guard = _threadMutex.lock_exclusive(); + + _enable.SetEvent(); + + if (!_thread) + { + _keepRunning.store(true, std::memory_order_relaxed); + + _thread.reset(CreateThread(nullptr, 0, s_ThreadProc, this, 0, nullptr)); + THROW_LAST_ERROR_IF(!_thread); + + // SetThreadDescription only works on 1607 and higher. If we cannot find it, + // then it's no big deal. Just skip setting the description. + const auto func = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"kernel32.dll"), SetThreadDescription); + if (func) + { + LOG_IF_FAILED(func(_thread.get(), L"Rendering Output Thread")); + } + } } void RenderThread::DisablePainting() noexcept { - ResetEvent(_hPaintEnabledEvent); + _enable.ResetEvent(); } -void RenderThread::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) noexcept +// Stops the rendering thread, and waits for it to finish. +void RenderThread::TriggerTeardown() noexcept { - // When rendering takes place via DirectX, and a console application - // currently owns the screen, and a new console application is launched (or - // the user switches to another console application), the new application - // cannot take over the screen until the active one relinquishes it. This - // blocking mechanism goes as follows: - // - // 1. The console input thread of the new console application connects to - // ConIoSrv; - // 2. While servicing the new connection request, ConIoSrv sends an event to - // the active application letting it know that it has lost focus; - // 3.1 ConIoSrv waits for a reply from the client application; - // 3.2 Meanwhile, the active application receives the focus event and calls - // this method, waiting for the current paint operation to - // finish. - // - // This means that the new application is waiting on the connection request - // reply from ConIoSrv, ConIoSrv is waiting on the active application to - // acknowledge the lost focus event to reply to the new application, and the - // console input thread in the active application is waiting on the renderer - // thread to finish its current paint operation. - // - // Question: what should happen if the wait on the paint operation times - // out? - // - // There are three options: - // - // 1. On timeout, the active console application could reply with an error - // message and terminate itself, effectively relinquishing control of the - // display; - // - // 2. ConIoSrv itself could time out on waiting for a reply, and forcibly - // terminate the active console application; - // - // 3. Let the wait time out and let the user deal with it. Because the wait - // occurs on a single iteration of the renderer thread, it seemed to me that - // the likelihood of failure is extremely small, especially since the client - // console application that the active conhost instance is servicing has no - // say over what happens in the renderer thread, only by proxy. Thus, the - // chance of failure (timeout) is minimal and since the OneCoreUAP console - // is not a massively used piece of software, it didn’t seem that it would - // be a good use of time to build the requisite infrastructure to deal with - // a timeout here, at least not for now. In case of a timeout DirectX will - // catch the mistake of a new application attempting to acquire the display - // while another one still owns it and will flag it as a DWM bug. Right now, - // the active application will wait one second for the paint operation to - // finish. - // - // TODO: MSFT: 11833883 - Determine action when wait on paint operation via - // DirectX on OneCoreUAP times out while switching console - // applications. + const auto guard = _threadMutex.lock_exclusive(); - ResetEvent(_hPaintEnabledEvent); - WaitForSingleObject(_hPaintCompletedEvent, dwTimeoutMs); + if (_thread) + { + // The render thread first waits for the event and then checks _keepRunning. By doing it + // in reverse order here, we ensure that it's impossible for the render thread to miss this. + _keepRunning.store(false, std::memory_order_relaxed); + _redraw.SetEvent(); + _enable.SetEvent(); + + WaitForSingleObject(_thread.get(), INFINITE); + _thread.reset(); + } + + DisablePainting(); } diff --git a/src/renderer/base/thread.hpp b/src/renderer/base/thread.hpp index 982eccd99b..fbc921465d 100644 --- a/src/renderer/base/thread.hpp +++ b/src/renderer/base/thread.hpp @@ -21,30 +21,23 @@ namespace Microsoft::Console::Render class RenderThread { public: - RenderThread(); + RenderThread(Renderer* renderer); ~RenderThread(); - [[nodiscard]] HRESULT Initialize(Renderer* const pRendererParent) noexcept; - void NotifyPaint() noexcept; void EnablePainting() noexcept; void DisablePainting() noexcept; - void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) noexcept; + void TriggerTeardown() noexcept; private: static DWORD WINAPI s_ThreadProc(_In_ LPVOID lpParameter); DWORD WINAPI _ThreadProc(); - HANDLE _hThread; - HANDLE _hEvent; - - HANDLE _hPaintEnabledEvent; - HANDLE _hPaintCompletedEvent; - - Renderer* _pRenderer; // Non-ownership pointer - - bool _fKeepRunning; - std::atomic _fNextFrameRequested; - std::atomic _fWaiting; + Renderer* renderer; + wil::slim_event_manual_reset _enable; + wil::slim_event_auto_reset _redraw; + wil::srwlock _threadMutex; + wil::unique_handle _thread; + std::atomic _keepRunning{ false }; }; } diff --git a/src/renderer/inc/DummyRenderer.hpp b/src/renderer/inc/DummyRenderer.hpp index b1e43b9d5f..dd03e54450 100644 --- a/src/renderer/inc/DummyRenderer.hpp +++ b/src/renderer/inc/DummyRenderer.hpp @@ -18,7 +18,7 @@ class DummyRenderer final : public Microsoft::Console::Render::Renderer { public: DummyRenderer(Microsoft::Console::Render::IRenderData* pData = nullptr) : - Microsoft::Console::Render::Renderer(_renderSettings, pData, nullptr, 0, nullptr) {} + Microsoft::Console::Render::Renderer(_renderSettings, pData) {} Microsoft::Console::Render::RenderSettings _renderSettings; }; From a86c90a045f0850cfc0d9aef550f8606ac0b205a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89l=C3=A9a=20Dufresne?= <74742695+eleadufresne@users.noreply.github.com> Date: Fri, 14 Mar 2025 17:00:10 -0700 Subject: [PATCH 006/177] Preview actions from the command palette on mouse hover (#18518) Allow users to preview color schemes by hovering over them with the mouse pointer in the Command Palette. - This PR handles issue #18238. - This extends the previously closed issue #6689, which allowed the `UP` and `DOWN` arrows to trigger a preview of color schemes in the Command Palette. This works by attaching event handlers for `PointerEntered` and `PointerExited` to `ListViewItem` containers. When the mouse pointer moves into the item's bounding area, the `PreviewAction` handler is triggered to showcase the hovered color scheme. Conversely, when the mouse pointer leaves the item's area, the `PreviewAction` is executed on the selected item (generally from the `UP` and `DOWN` arrows). **Important note:** - This also provides previews for the other features that the `ActionPreviewHandler` handles, such as the background opacity of the terminal. ## Validation Steps Performed - Hover a color scheme, and it becomes the active one. - Pressing `ESC` at any point to dismiss the command palette, and the scheme returns to the previous one. - I did not add any additional test, though all existing ColorScheme tests passed. Closes #18238 --- src/cascadia/TerminalApp/CommandPalette.cpp | 80 +++++++++++++++++++++ src/cascadia/TerminalApp/CommandPalette.h | 6 ++ 2 files changed, 86 insertions(+) diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index bcee25c72d..6d5540703f 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -530,6 +530,79 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - This event is called when the user's mouse pointer enters an individual + // item from the list. We'll get the item that was hovered and "preview" + // the command that the user hovered. To do that, we'll dispatch the switch + // to tab command for this tab, but not dismiss the switcher. + // + // Arguments: + // - sender: the UI element that raised the event. + // Return Value: + // - + void CommandPalette::_listItemPointerEntered(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& /*args*/) + { + // cancel any pending exit timer to prevent an unwanted preview revert + if (_pointerExitTimer) + { + _pointerExitTimer.Stop(); + } + + const auto listViewItem = sender.try_as(); + if (_currentMode == CommandPaletteMode::ActionMode && listViewItem) + { + const auto enteredItem = listViewItem.Content(); + if (const auto filteredCommand{ enteredItem.try_as() }) + { + if (const auto actionPaletteItem{ filteredCommand.Item().try_as() }) + { + // immediately preview the hovered command + PreviewAction.raise(*this, actionPaletteItem.Command()); + } + } + } + } + + // Method Description: + // - This event is called when the user's mouse pointer exits an individual + // item from the list. We then revert to previewing the selected item rather + // than the hovered one, using a short delay (via a DispatcherTimer) to smooth + // transitions when rapidly moving between items. + // + // Arguments: + // - + // Return Value: + // - + void CommandPalette::_listItemPointerExited(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& /*args*/) + { + // if there is no exit timer, create one + if (!_pointerExitTimer) + { + _pointerExitTimer = winrt::Windows::UI::Xaml::DispatcherTimer(); + _pointerExitTimer.Interval(std::chrono::milliseconds(10)); + _pointerExitTimer.Tick([this](auto const&, auto const&) { + // when the timer ticks, revert the preview to the selected command + const auto selectedCommand = _filteredActionsView().SelectedItem(); + if (const auto filteredCommand{ selectedCommand.try_as() }) + { + if (_currentMode == CommandPaletteMode::ActionMode && filteredCommand) + { + if (const auto actionPaletteItem{ filteredCommand.Item().try_as() }) + { + PreviewAction.raise(*this, actionPaletteItem.Command()); + } + } + } + _pointerExitTimer.Stop(); + }); + } + + // restart the timer + _pointerExitTimer.Start(); + } + void CommandPalette::_listItemSelectionChanged(const Windows::Foundation::IInspectable& /*sender*/, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& e) { // We don't care about... @@ -1214,6 +1287,9 @@ namespace winrt::TerminalApp::implementation ParentCommandName(L""); _currentNestedCommands.Clear(); + + // revert any preview + _filteredActionsView().SelectedIndex(-1); PreviewAction.raise(*this, nullptr); } @@ -1306,6 +1382,10 @@ namespace winrt::TerminalApp::implementation else { itemContainer.DataContext(args.Item()); + + // attach the pointer event handlers to the container + itemContainer.PointerEntered({ this, &CommandPalette::_listItemPointerEntered }); + itemContainer.PointerExited({ this, &CommandPalette::_listItemPointerExited }); } } diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index 58e34bb1f9..0da7ee1334 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -78,6 +78,8 @@ namespace winrt::TerminalApp::implementation Windows::Foundation::Collections::IVector _commandsToFilter(); + winrt::Windows::UI::Xaml::DispatcherTimer _pointerExitTimer{ nullptr }; // timer to debounce pointer exit events (used to smooth preview transitions) + bool _lastFilterTextWasEmpty{ true }; void _populateCommands(); @@ -103,6 +105,10 @@ namespace winrt::TerminalApp::implementation void _listItemClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::ItemClickEventArgs& e); + void _listItemPointerEntered(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& args); + + void _listItemPointerExited(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& args); + void _listItemSelectionChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& e); void _moveBackButtonClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs&); From 6e8924237351d57eccf7af6726ab1746f1404401 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 18 Mar 2025 13:26:31 -0500 Subject: [PATCH 007/177] Multiple fixes to address DD CodeQL requirements (#18451) After taking in 1.22, our CodeQL process caught a few locations where we weren't following the right guidance: - Performing integer comparisons of different sizes which could lead to an infinite loop if the larger integer goes out of range of the smaller integer - Not checking HResult of a called method Co-authored-by: aphistra <102989060+aphistra@users.noreply.github.com> --- src/buffer/out/Row.cpp | 2 +- src/renderer/gdi/paint.cpp | 3 ++- src/terminal/adapter/SixelParser.cpp | 2 +- src/terminal/adapter/charsets.hpp | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 63ba83f4cd..859d3b9f58 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -431,7 +431,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= size()); // If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row. - const auto finalColumnInRow = limitRight.value_or(size() - 1); + const auto finalColumnInRow = gsl::narrow_cast(limitRight.value_or(size() - 1)); auto currentColor = it->TextAttr(); uint16_t colorUses = 0; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 86db0de940..20c27b1e61 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -574,7 +574,8 @@ try } const auto cpt = gsl::narrow_cast(points.size()); - return PolyBezier(_hdcMemoryContext, points.data(), cpt); + RETURN_HR_IF(E_FAIL, !PolyBezier(_hdcMemoryContext, points.data(), cpt)); + return S_OK; }; if (lines.test(GridLines::Left)) diff --git a/src/terminal/adapter/SixelParser.cpp b/src/terminal/adapter/SixelParser.cpp index 979dd42371..60e7fecaee 100644 --- a/src/terminal/adapter/SixelParser.cpp +++ b/src/terminal/adapter/SixelParser.cpp @@ -610,7 +610,7 @@ void SixelParser::_updateTextColors() // the text output as well. if (_conformanceLevel <= 3 && _maxColors > 2 && _colorTableChanged) [[unlikely]] { - for (IndexType tableIndex = 0; tableIndex < _maxColors; tableIndex++) + for (IndexType tableIndex = 0; _maxColors <= 16 && tableIndex < _maxColors; tableIndex++) { _dispatcher.SetColorTableEntry(tableIndex, _colorFromIndex(tableIndex)); } diff --git a/src/terminal/adapter/charsets.hpp b/src/terminal/adapter/charsets.hpp index bc81e31e72..72b441facc 100644 --- a/src/terminal/adapter/charsets.hpp +++ b/src/terminal/adapter/charsets.hpp @@ -19,7 +19,7 @@ namespace Microsoft::Console::VirtualTerminal public: constexpr CharSet(const std::initializer_list> replacements) { - for (auto i = L'\0'; i < _translationTable.size(); i++) + for (auto i = L'\0'; i < gsl::narrow_cast(_translationTable.size()); i++) _translationTable.at(i) = BaseChar + i; for (auto replacement : replacements) _translationTable.at(replacement.first - BaseChar) = replacement.second; From 75d8fc29f5d2db63abbc6135e276287f30958097 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 20 Mar 2025 19:57:06 -0500 Subject: [PATCH 008/177] Delay-load icu so that we don't fail to start up on Windows <1903 (#18707) --- src/cascadia/TerminalControl/dll/TerminalControl.vcxproj | 2 +- src/host/exe/Host.EXE.vcxproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj index 22d276adc1..e6cd662dd1 100644 --- a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj @@ -95,7 +95,7 @@ delayimp.lib;Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies) - uiautomationcore.dll;%(DelayLoadDLLs) + uiautomationcore.dll;icu.dll;%(DelayLoadDLLs) From 6eb6512d12cf3c38d69dba6e6d6b01617e13cda0 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Fri, 21 Mar 2025 21:57:04 +0500 Subject: [PATCH 009/177] Add WinGet configuration files for building terminal (#18645) PR adds a WinGet configuration file to install the necessary dependencies in order to build terminal locally. The configuration file enables developer mode, installs PowerShell 7, Visual Studio 2022 & all the required workloads from the .vsconfig file (in accordance with the dependencies listed in the README). ## Validation Steps Performed Tested the configuration file by spinning up a clean Win11 Pro VM in azure and then doing the following: 1. Install latest WinGet on the VM using WinGet sandbox script. Install git and clone the repo 2. Run `winget configure .config/configuration.winget` (this should work by just double-clicking the file in explorer too) 3. After the configuration is completed, open the solution in the now installed Visual Studio and build. The build is successful and I could start terminal with F5 Co-authored-by: Demitrius Nelon --- .config/configuration.vsEnterprise.winget | 43 +++++++++++++++++++++ .config/configuration.vsProfessional.winget | 43 +++++++++++++++++++++ .config/configuration.winget | 43 +++++++++++++++++++++ .github/actions/spelling/expect/expect.txt | 1 + README.md | 13 +++++++ 5 files changed, 143 insertions(+) create mode 100644 .config/configuration.vsEnterprise.winget create mode 100644 .config/configuration.vsProfessional.winget create mode 100644 .config/configuration.winget diff --git a/.config/configuration.vsEnterprise.winget b/.config/configuration.vsEnterprise.winget new file mode 100644 index 0000000000..cf7f657d11 --- /dev/null +++ b/.config/configuration.vsEnterprise.winget @@ -0,0 +1,43 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +# Reference: https://github.com/microsoft/terminal/blob/main/README.md#developer-guidance +properties: + resources: + - resource: Microsoft.Windows.Developer/DeveloperMode + directives: + description: Enable Developer Mode + allowPrerelease: true + # Requires elevation for the set operation + securityContext: elevated + settings: + Ensure: Present + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: powershell + directives: + description: Install PowerShell 7 + # Requires elevation for the set operation (i.e., installation) + securityContext: elevated + settings: + id: Microsoft.PowerShell + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: vsPackage + directives: + description: Install Visual Studio 2022 Enterprise (any edition is OK) + # Requires elevation for the set operation (i.e., installation) + securityContext: elevated + settings: + id: Microsoft.VisualStudio.2022.Enterprise + source: winget + - resource: Microsoft.VisualStudio.DSC/VSComponents + dependsOn: + - vsPackage + directives: + description: Install required VS workloads from project .vsconfig file + allowPrerelease: true + # Requires elevation for the get and set operations + securityContext: elevated + settings: + productId: Microsoft.VisualStudio.Product.Enterprise + channelId: VisualStudio.17.Release + vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig' + configurationVersion: 0.2.0 diff --git a/.config/configuration.vsProfessional.winget b/.config/configuration.vsProfessional.winget new file mode 100644 index 0000000000..d0b7508f36 --- /dev/null +++ b/.config/configuration.vsProfessional.winget @@ -0,0 +1,43 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +# Reference: https://github.com/microsoft/terminal/blob/main/README.md#developer-guidance +properties: + resources: + - resource: Microsoft.Windows.Developer/DeveloperMode + directives: + description: Enable Developer Mode + allowPrerelease: true + # Requires elevation for the set operation + securityContext: elevated + settings: + Ensure: Present + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: powershell + directives: + description: Install PowerShell 7 + # Requires elevation for the set operation (i.e., installation) + securityContext: elevated + settings: + id: Microsoft.PowerShell + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: vsPackage + directives: + description: Install Visual Studio 2022 Professional (any edition is OK) + # Requires elevation for the set operation (i.e., installation) + securityContext: elevated + settings: + id: Microsoft.VisualStudio.2022.Professional + source: winget + - resource: Microsoft.VisualStudio.DSC/VSComponents + dependsOn: + - vsPackage + directives: + description: Install required VS workloads from project .vsconfig file + allowPrerelease: true + # Requires elevation for the get and set operations + securityContext: elevated + settings: + productId: Microsoft.VisualStudio.Product.Professional + channelId: VisualStudio.17.Release + vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig' + configurationVersion: 0.2.0 diff --git a/.config/configuration.winget b/.config/configuration.winget new file mode 100644 index 0000000000..1dcd7cf829 --- /dev/null +++ b/.config/configuration.winget @@ -0,0 +1,43 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +# Reference: https://github.com/microsoft/terminal/blob/main/README.md#developer-guidance +properties: + resources: + - resource: Microsoft.Windows.Developer/DeveloperMode + directives: + description: Enable Developer Mode + allowPrerelease: true + # Requires elevation for the set operation + securityContext: elevated + settings: + Ensure: Present + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: powershell + directives: + description: Install PowerShell 7 + # Requires elevation for the set operation (i.e., installation) + securityContext: elevated + settings: + id: Microsoft.PowerShell + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: vsPackage + directives: + description: Install Visual Studio 2022 Community (any edition is OK) + # Requires elevation for the set operation (i.e., installation) + securityContext: elevated + settings: + id: Microsoft.VisualStudio.2022.Community + source: winget + - resource: Microsoft.VisualStudio.DSC/VSComponents + dependsOn: + - vsPackage + directives: + description: Install required VS workloads from project .vsconfig file + allowPrerelease: true + # Requires elevation for the get and set operations + securityContext: elevated + settings: + productId: Microsoft.VisualStudio.Product.Community + channelId: VisualStudio.17.Release + vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig' + configurationVersion: 0.2.0 diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index e78cb97139..f8468d1fca 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1945,6 +1945,7 @@ VPACKMANIFESTDIRECTORY VPR VREDRAW vsc +vsconfig vscprintf VSCROLL vsdevshell diff --git a/README.md b/README.md index 71b0daad26..e178ad2eb3 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,19 @@ If you would like to ask a question that you feel doesn't warrant an issue ## Prerequisites +You can configure your environment to build Terminal in one of two ways: + +### Using WinGet configuration file + +After cloning the repository, you can use a [WinGet configuration file](https://learn.microsoft.com/en-us/windows/package-manager/configuration/#use-a-winget-configuration-file-to-configure-your-machine) +to set up your environment. The [default configuration file](.config/configuration.winget) installs Visual Studio 2022 Community & rest of the required tools. There are two other variants of the configuration file available in the [.config](.config) directory for Enterprise & Professional editions of Visual Studio 2022. To run the default configuration file, you can either double-click the file from explorer or run the following command: + +```powershell +winget configure .config\configuration.winget +``` + +### Manual configuration + * You must be running Windows 10 2004 (build >= 10.0.19041.0) or later to run Windows Terminal * You must [enable Developer Mode in the Windows Settings From f34dbbf3ac1c4bd646732aa7b694553e4db97d64 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 26 Mar 2025 17:37:21 -0500 Subject: [PATCH 010/177] vcpkg: add an overlay port for fmt 11.1.4; enable /W3 (#18729) This pull request brings us up to fmt 11.1.4 and enables `FMT_PEDANTIC`. `FMT_PEDANTIC` turns on `/W3`, which is required by our local feudal lords who will automatically file bugs on us if we don't build with enough warnings enabled. --- .../fmt/fix-write-batch.patch | 13 +++++++ dep/vcpkg-overlay-ports/fmt/portfile.cmake | 38 +++++++++++++++++++ dep/vcpkg-overlay-ports/fmt/usage | 8 ++++ dep/vcpkg-overlay-ports/fmt/vcpkg.json | 17 +++++++++ vcpkg.json | 7 +++- 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 dep/vcpkg-overlay-ports/fmt/fix-write-batch.patch create mode 100644 dep/vcpkg-overlay-ports/fmt/portfile.cmake create mode 100644 dep/vcpkg-overlay-ports/fmt/usage create mode 100644 dep/vcpkg-overlay-ports/fmt/vcpkg.json diff --git a/dep/vcpkg-overlay-ports/fmt/fix-write-batch.patch b/dep/vcpkg-overlay-ports/fmt/fix-write-batch.patch new file mode 100644 index 0000000000..6bec3b8e4b --- /dev/null +++ b/dep/vcpkg-overlay-ports/fmt/fix-write-batch.patch @@ -0,0 +1,13 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 88c12148..967b53dd 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -260,7 +260,7 @@ if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") + join(netfxpath + "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\" + ".NETFramework\\v4.0") +- file(WRITE run-msbuild.bat " ++ file(WRITE "${CMAKE_BINARY_DIR}/run-msbuild.bat" " + ${MSBUILD_SETUP} + ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*") + endif () diff --git a/dep/vcpkg-overlay-ports/fmt/portfile.cmake b/dep/vcpkg-overlay-ports/fmt/portfile.cmake new file mode 100644 index 0000000000..42d554a3d5 --- /dev/null +++ b/dep/vcpkg-overlay-ports/fmt/portfile.cmake @@ -0,0 +1,38 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO fmtlib/fmt + REF "${VERSION}" + SHA512 573b7de1bd224b7b1b60d44808a843db35d4bc4634f72a9edcb52cf68e99ca66c744fd5d5c97b4336ba70b94abdabac5fc253b245d0d5cd8bbe2a096bf941e39 + HEAD_REF master + PATCHES + fix-write-batch.patch +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DFMT_CMAKE_DIR=share/fmt + -DFMT_TEST=OFF + -DFMT_DOC=OFF + -DFMT_PEDANTIC=ON +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup() +vcpkg_fixup_pkgconfig() +vcpkg_copy_pdbs() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL dynamic) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/fmt/base.h" + "defined(FMT_SHARED)" + "1" + ) +endif() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") diff --git a/dep/vcpkg-overlay-ports/fmt/usage b/dep/vcpkg-overlay-ports/fmt/usage new file mode 100644 index 0000000000..e5a9d70480 --- /dev/null +++ b/dep/vcpkg-overlay-ports/fmt/usage @@ -0,0 +1,8 @@ +The package fmt provides CMake targets: + + find_package(fmt CONFIG REQUIRED) + target_link_libraries(main PRIVATE fmt::fmt) + + # Or use the header-only version + find_package(fmt CONFIG REQUIRED) + target_link_libraries(main PRIVATE fmt::fmt-header-only) diff --git a/dep/vcpkg-overlay-ports/fmt/vcpkg.json b/dep/vcpkg-overlay-ports/fmt/vcpkg.json new file mode 100644 index 0000000000..d55fff5aa0 --- /dev/null +++ b/dep/vcpkg-overlay-ports/fmt/vcpkg.json @@ -0,0 +1,17 @@ +{ + "name": "fmt", + "version": "11.1.4", + "description": "{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams.", + "homepage": "https://github.com/fmtlib/fmt", + "license": "MIT", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} diff --git a/vcpkg.json b/vcpkg.json index 3536fad210..28b1d753e4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -17,7 +17,7 @@ "overrides": [ { "name": "fmt", - "version": "11.0.2" + "version": "11.1.4" }, { "name": "ms-gsl", @@ -36,5 +36,8 @@ "version": "0.30.3" } ], - "builtin-baseline": "fe1cde61e971d53c9687cf9a46308f8f55da19fa" + "builtin-baseline": "fe1cde61e971d53c9687cf9a46308f8f55da19fa", + "vcpkg-configuration": { + "overlay-ports": [ "./dep/vcpkg-overlay-ports" ] + } } From 22c509f426a7d2cdf616bc18143f5bc24f238c4f Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 1 Apr 2025 17:01:20 -0500 Subject: [PATCH 011/177] build: run official builds with the R1 network isolation policy (#18753) This required removing connections during the build to `nuget.org` and `powershellgallery.com`. The NuGet Tool task was downloading nuget from `nuget.org` unconditionally. The `AzureFileCopy` task was downloading `Az.Accounts` from `powershellgallery.com` unconditionally. Both of these tasks have better options nowadays. Tested and passed in OneBranch on 2025-04-01. --- .github/actions/spelling/expect/expect.txt | 2 ++ .../job-deploy-to-azure-storage.yml | 19 +++++++------------ ...sh-symbols-using-symbolrequestprod-api.yml | 5 ----- .../pipeline-onebranch-full-release-build.yml | 4 +++- .../steps-ensure-nuget-version.yml | 15 +++++++++++---- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index f8468d1fca..4356e36eae 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -86,6 +86,7 @@ autoscrolling Autowrap AVerify awch +AZCOPY azurecr AZZ backgrounded @@ -1416,6 +1417,7 @@ propvar propvariant propvarutil psa +PSCRED PSECURITY pseudoconsole pseudoterminal diff --git a/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml index e2ed380fd4..8c2836c059 100644 --- a/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml +++ b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml @@ -75,18 +75,13 @@ jobs: } displayName: "Wrangle Unpackaged builds into place, rename" - - powershell: |- - Get-PackageProvider -Name NuGet -ForceBootstrap - Install-Module -Verbose -AllowClobber -Force Az.Accounts, Az.Storage, Az.Network, Az.Resources, Az.Compute - displayName: Install Azure Module Dependencies - - - task: AzureFileCopy@6 + - task: AzurePowerShell@5 displayName: Publish to Storage Account inputs: - sourcePath: _out/* - Destination: AzureBlob azureSubscription: ${{ parameters.subscription }} - storage: ${{ parameters.storageAccount }} - ContainerName: ${{ parameters.storageContainer }} - AdditionalArgumentsForBlobCopy: "--content-type application/octet-stream" - + azurePowerShellVersion: LatestVersion + pwsh: true + ScriptType: InlineScript + Inline: |- + $Env:AZCOPY_AUTO_LOGIN_TYPE="PSCRED" + & AzCopy copy "_out\*" "https://${{ parameters.storageAccount }}.blob.core.windows.net/${{ parameters.storageContainer }}/" --content-type application/octet-stream diff --git a/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml b/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml index 12b0ddce86..a5ed41f737 100644 --- a/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml +++ b/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml @@ -52,11 +52,6 @@ jobs: itemPattern: '**/*.pdb' targetPath: '$(Build.SourcesDirectory)/bin' - - powershell: |- - Get-PackageProvider -Name NuGet -ForceBootstrap - Install-Module -Verbose -AllowClobber -Force Az.Accounts, Az.Storage, Az.Network, Az.Resources, Az.Compute - displayName: Install Azure Module Dependencies - # Transit the Azure token from the Service Connection into a secret variable for the rest of the pipeline to use. - task: AzurePowerShell@5 displayName: Generate an Azure Token diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml index ae017a9bec..0f09271a72 100644 --- a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -78,7 +78,9 @@ extends: template: v2/Microsoft.NonOfficial.yml@templates parameters: featureFlags: - WindowsHostVersion: 1ESWindows2022 + WindowsHostVersion: + Version: 2022 + Network: R1 platform: name: 'windows_undocked' product: 'Windows Terminal' diff --git a/build/pipelines/templates-v2/steps-ensure-nuget-version.yml b/build/pipelines/templates-v2/steps-ensure-nuget-version.yml index fb5cd75e4c..ea5eda5557 100644 --- a/build/pipelines/templates-v2/steps-ensure-nuget-version.yml +++ b/build/pipelines/templates-v2/steps-ensure-nuget-version.yml @@ -1,5 +1,12 @@ steps: -- task: NuGetToolInstaller@1 - displayName: Use NuGet 6.6.1 - inputs: - versionSpec: 6.6.1 +- ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}: + - pwsh: |- + Write-Host "Assuming NuGet is already installed..." + & nuget.exe help + displayName: Assume NuGet is fine + +- ${{ else }}: + - task: NuGetToolInstaller@1 + displayName: Use NuGet 6.6.1 + inputs: + versionSpec: 6.6.1 From 5f311506dcaa93c24214819e02fa027151c47fe1 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 9 Apr 2025 19:11:47 -0500 Subject: [PATCH 012/177] Add support for OSC 104, 110, 111, 112 and 117 (resets) (#18767) This pull request adds support for resetting the various color table entries and xterm resource values back to their defaults. Building on the default color table James introduced in #17879, it was relatively straightforward to add support for resetting specific entries. This implementation cleaves tightly to observed behavior in xterm(379) rather than observed behavior in libvte(0.70.6). They differ in the following ways: - xterm rejects any OSC [110..119] with any number of parameters; libvte accepts it but only resets the first color. - When passed a list of color indices to reset in 104, xterm resets any colors up until the first one which fails to parse as an integer and does _not_ reset the rest; libvte resets all parseable color indices. I was unable to verify how these reset commands interact with colors set via `DECAC Assign Color` so I went with the implementation that made the most sense: - Resetting the background color with `110` also restores the background color alias entry to its pre-`DECAC` value; this results in the perceived background color returning to e.g. index 0 in conhost and the `background` color in Terminal. - _ibid._ for the foreground color Refs #18695 Refs #17879 Closes #3719 --- src/renderer/base/RenderSettings.cpp | 19 ++++ src/renderer/inc/RenderSettings.hpp | 3 + src/terminal/adapter/ITermDispatch.hpp | 5 +- src/terminal/adapter/adaptDispatch.cpp | 53 +++++++++++ src/terminal/adapter/adaptDispatch.hpp | 5 +- src/terminal/adapter/termDispatch.hpp | 5 +- .../parser/OutputStateMachineEngine.cpp | 36 ++++++- .../parser/OutputStateMachineEngine.hpp | 6 +- .../parser/ut_parser/OutputEngineTest.cpp | 94 ++++++++++++++++++- 9 files changed, 217 insertions(+), 9 deletions(-) diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp index 0410211143..371b591b32 100644 --- a/src/renderer/base/RenderSettings.cpp +++ b/src/renderer/base/RenderSettings.cpp @@ -112,6 +112,20 @@ COLORREF RenderSettings::GetColorTableEntry(const size_t tableIndex) const return _colorTable.at(tableIndex); } +// Routine Description: +// - Restores all of the xterm-addressable colors to the ones saved in SaveDefaultSettings. +void RenderSettings::RestoreDefaultIndexed256ColorTable() +{ + std::copy_n(_defaultColorTable.begin(), 256, _colorTable.begin()); +} + +// Routine Description: +// - Restores a color table entry to the value saved in SaveDefaultSettings. +void RenderSettings::RestoreDefaultColorTableEntry(const size_t tableIndex) +{ + _colorTable.at(tableIndex) = _defaultColorTable.at(tableIndex); +} + // Routine Description: // - Sets the position in the color table for the given color alias and updates the color. // Arguments: @@ -159,6 +173,11 @@ size_t RenderSettings::GetColorAliasIndex(const ColorAlias alias) const noexcept return gsl::at(_colorAliasIndices, static_cast(alias)); } +void RenderSettings::RestoreDefaultColorAliasIndex(const ColorAlias alias) noexcept +{ + gsl::at(_colorAliasIndices, static_cast(alias)) = gsl::at(_defaultColorAliasIndices, static_cast(alias)); +} + // Routine Description: // - Calculates the RGB colors of a given text attribute, using the current // color table configuration and active render settings. diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp index a9c370133c..84443889bd 100644 --- a/src/renderer/inc/RenderSettings.hpp +++ b/src/renderer/inc/RenderSettings.hpp @@ -37,10 +37,13 @@ namespace Microsoft::Console::Render void ResetColorTable() noexcept; void SetColorTableEntry(const size_t tableIndex, const COLORREF color); COLORREF GetColorTableEntry(const size_t tableIndex) const; + void RestoreDefaultIndexed256ColorTable(); + void RestoreDefaultColorTableEntry(const size_t tableIndex); void SetColorAlias(const ColorAlias alias, const size_t tableIndex, const COLORREF color); COLORREF GetColorAlias(const ColorAlias alias) const; void SetColorAliasIndex(const ColorAlias alias, const size_t tableIndex) noexcept; size_t GetColorAliasIndex(const ColorAlias alias) const noexcept; + void RestoreDefaultColorAliasIndex(const ColorAlias alias) noexcept; std::pair GetAttributeColors(const TextAttribute& attr) const noexcept; std::pair GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept; COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 1576571aa9..da24f71b6f 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -78,8 +78,11 @@ public: virtual void TabSet(const VTParameter setType) = 0; // DECST8C virtual void SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCSetColorTable virtual void RequestColorTableEntry(const size_t tableIndex) = 0; // OSCGetColorTable - virtual void SetXtermColorResource(const size_t resource, const DWORD color) = 0; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor + virtual void ResetColorTable() = 0; // OSCResetColorTable + virtual void ResetColorTableEntry(const size_t tableIndex) = 0; // OSCResetColorTable + virtual void SetXtermColorResource(const size_t resource, const DWORD color) = 0; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor virtual void RequestXtermColorResource(const size_t resource) = 0; // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor + virtual void ResetXtermColorResource(const size_t resource) = 0; // OSCResetForegroundColor, OSCResetBackgroundColor, OSCResetCursorColor, OSCResetHighlightColor virtual void AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) = 0; // DECAC virtual void EraseInDisplay(const DispatchTypes::EraseType eraseType) = 0; // ED diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index fe1b3c9a8a..9158b962ab 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -3292,6 +3292,40 @@ void AdaptDispatch::RequestColorTableEntry(const size_t tableIndex) } } +void AdaptDispatch::ResetColorTable() +{ + _renderSettings.RestoreDefaultIndexed256ColorTable(); + if (_renderer) + { + // This is pessimistic because it's unlikely that the frame or background changed, + // but let's tell the renderer that both changed anyway. + _renderer->TriggerRedrawAll(true, true); + } +} + +// Method Description: +// - Restores a single color table entry to its default user-specified value +// Arguments: +// - tableIndex: The VT color table index +void AdaptDispatch::ResetColorTableEntry(const size_t tableIndex) +{ + _renderSettings.RestoreDefaultColorTableEntry(tableIndex); + + if (_renderer) + { + // If we're updating the background color, we need to let the renderer + // know, since it may want to repaint the window background to match. + const auto backgroundIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultBackground); + const auto backgroundChanged = (tableIndex == backgroundIndex); + + // Similarly for the frame color, the tab may need to be repainted. + const auto frameIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameBackground); + const auto frameChanged = (tableIndex == frameIndex); + + _renderer->TriggerRedrawAll(backgroundChanged, frameChanged); + } +} + // Method Description: // - Sets one Xterm Color Resource such as Default Foreground, Background, Cursor void AdaptDispatch::SetXtermColorResource(const size_t resource, const DWORD color) @@ -3338,6 +3372,25 @@ void AdaptDispatch::RequestXtermColorResource(const size_t resource) } } +// Method Description: +// - Restores to the original user-provided value one Xterm Color Resource such as Default Foreground, Background, Cursor +void AdaptDispatch::ResetXtermColorResource(const size_t resource) +{ + assert(resource >= 10); + const auto mappingIndex = resource - 10; + const auto& oscMapping = XtermResourceColorTableMappings.at(mappingIndex); + if (oscMapping.ColorTableIndex > 0) + { + if (oscMapping.AliasIndex >= 0) + { + // If this color reset applies to an aliased color, point the alias back at the original color + _renderSettings.RestoreDefaultColorAliasIndex(static_cast(oscMapping.AliasIndex)); + } + + ResetColorTableEntry(oscMapping.ColorTableIndex); + } +} + // Method Description: // DECAC - Assigns the foreground and background color indexes that should be // used for a given aspect of the user interface. diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 2f63e782ca..313611a732 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -133,8 +133,11 @@ namespace Microsoft::Console::VirtualTerminal void SetColorTableEntry(const size_t tableIndex, const DWORD color) override; // OSCSetColorTable void RequestColorTableEntry(const size_t tableIndex) override; // OSCGetColorTable - void SetXtermColorResource(const size_t resource, const DWORD color) override; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor + void ResetColorTable() override; // OSCResetColorTable + void ResetColorTableEntry(const size_t tableIndex) override; // OSCResetColorTable + void SetXtermColorResource(const size_t resource, const DWORD color) override; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor void RequestXtermColorResource(const size_t resource) override; // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor + void ResetXtermColorResource(const size_t resource) override; // OSCResetForegroundColor, OSCResetBackgroundColor, OSCResetCursorColor, OSCResetHighlightColor void AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) override; // DECAC void WindowManipulation(const DispatchTypes::WindowManipulationType function, diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index f6dab368f8..0f3908331b 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -71,8 +71,11 @@ public: void TabSet(const VTParameter /*setType*/) override {} // DECST8C void SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override {} // OSCSetColorTable void RequestColorTableEntry(const size_t /*tableIndex*/) override {} // OSCGetColorTable - void SetXtermColorResource(const size_t /*resource*/, const DWORD /*color*/) override {} // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor + void ResetColorTable() override {} // OSCResetColorTable + void ResetColorTableEntry(const size_t /*tableIndex*/) override {} // OSCResetColorTable + void SetXtermColorResource(const size_t /*resource*/, const DWORD /*color*/) override {} // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor void RequestXtermColorResource(const size_t /*resource*/) override {} // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor + void ResetXtermColorResource(const size_t /*resource*/) override {} // OSCResetForegroundColor, OSCResetBackgroundColor, OSCResetCursorColor, OSCResetHighlightColor void AssignColor(const DispatchTypes::ColorItem /*item*/, const VTInt /*fgIndex*/, const VTInt /*bgIndex*/) override {} // DECAC void EraseInDisplay(const DispatchTypes::EraseType /* eraseType*/) override {} // ED diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index c8a1367a26..4febc78ee8 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -810,10 +810,40 @@ bool OutputStateMachineEngine::ActionOscDispatch(const size_t parameter, const s } break; } - case OscActionCodes::ResetCursorColor: + case OscActionCodes::ResetColor: { - // The reset codes for xterm dynamic resources are the set codes + 100 - _dispatch->SetXtermColorResource(parameter - 100u, INVALID_COLOR); + if (string.empty()) + { + _dispatch->ResetColorTable(); + } + else + { + for (auto&& c : til::split_iterator{ string, L';' }) + { + if (const auto index{ til::parse_unsigned(c, 10) }; index) + { + _dispatch->ResetColorTableEntry(*index); + } + else + { + // NOTE: xterm stops at the first unparseable index whereas VTE keeps going. + break; + } + } + } + break; + } + case OscActionCodes::ResetForegroundColor: + case OscActionCodes::ResetBackgroundColor: + case OscActionCodes::ResetCursorColor: + case OscActionCodes::ResetHighlightColor: + { + // NOTE: xterm ignores the request if there's any parameters whereas VTE resets the provided index and ignores the rest + if (string.empty()) + { + // The reset codes for xterm dynamic resources are the set codes + 100 + _dispatch->ResetXtermColorResource(parameter - 100u); + } break; } case OscActionCodes::Hyperlink: diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index ab41e066cf..d36789e108 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -214,9 +214,11 @@ namespace Microsoft::Console::VirtualTerminal SetHighlightColor = 17, DECSWT_SetWindowTitle = 21, SetClipboard = 52, - ResetForegroundColor = 110, // Not implemented - ResetBackgroundColor = 111, // Not implemented + ResetColor = 104, + ResetForegroundColor = 110, + ResetBackgroundColor = 111, ResetCursorColor = 112, + ResetHighlightColor = 117, FinalTermAction = 133, VsCodeAction = 633, ITerm2Action = 1337, diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index 416ed6681d..f12dac966c 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -1163,7 +1163,8 @@ public: _hyperlinkMode{ false }, _options{ s_cMaxOptions, static_cast(s_uiGraphicsCleared) }, // fill with cleared option _colorTable{}, - _setColorTableEntry{ false } + _setColorTableEntry{ false }, + _resetAllColors{ false } { } @@ -1390,6 +1391,16 @@ public: _colorTableEntriesRequested.push_back(tableIndex); } + void ResetColorTable() noexcept override + { + _resetAllColors = true; + } + + void ResetColorTableEntry(const size_t tableIndex) noexcept override + { + _colorTableEntriesReset.push_back(tableIndex); + } + void SetXtermColorResource(const size_t resource, const DWORD color) override { _xtermResourcesChanged.push_back(resource); @@ -1401,6 +1412,11 @@ public: _xtermResourcesRequested.push_back(resource); } + void ResetXtermColorResource(const size_t resource) override + { + _xtermResourcesReset.push_back(resource); + } + void SetClipboard(wil::zwstring_view content) noexcept override { _copyContent = content; @@ -1476,8 +1492,11 @@ public: std::vector _xtermResourcesChanged; std::vector _xtermResourceValues; std::vector _xtermResourcesRequested; + std::vector _xtermResourcesReset; bool _setColorTableEntry; std::vector _colorTableEntriesRequested; + bool _resetAllColors; + std::vector _colorTableEntriesReset; bool _hyperlinkMode; std::wstring _copyContent; std::wstring _uri; @@ -3221,6 +3240,79 @@ class StateMachineExternalTest final pDispatch->ClearState(); } + TEST_METHOD(TestOscXtermResourceReset) + { + auto dispatch = std::make_unique(); + auto pDispatch = dispatch.get(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + mach.ProcessString(L"\033]110\033\\"); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesRequested.size()); + VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesReset.size()); + VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesReset[0]); + pDispatch->ClearState(); + + mach.ProcessString(L"\033]111;\033\\"); // dangling ; + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesRequested.size()); + VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesReset.size()); + VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesReset[0]); + pDispatch->ClearState(); + + mach.ProcessString(L"\033]111;110\033\\"); + // NOTE: this is xterm behavior - ignore the entire sequence if any params exist + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesRequested.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size()); + pDispatch->ClearState(); + } + + TEST_METHOD(TestOscColorTableReset) + { + auto dispatch = std::make_unique(); + auto pDispatch = dispatch.get(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + mach.ProcessString(L"\033]104\033\\"); + VERIFY_IS_TRUE(pDispatch->_resetAllColors); + VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesReset.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size()); + pDispatch->ClearState(); + + mach.ProcessString(L"\033]104;1;3;5;7;9\033\\"); + VERIFY_IS_FALSE(pDispatch->_resetAllColors); + VERIFY_ARE_EQUAL(5u, pDispatch->_colorTableEntriesReset.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size()); + VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesReset[0]); + VERIFY_ARE_EQUAL(3u, pDispatch->_colorTableEntriesReset[1]); + VERIFY_ARE_EQUAL(5u, pDispatch->_colorTableEntriesReset[2]); + VERIFY_ARE_EQUAL(7u, pDispatch->_colorTableEntriesReset[3]); + VERIFY_ARE_EQUAL(9u, pDispatch->_colorTableEntriesReset[4]); + pDispatch->ClearState(); + + // NOTE: xterm behavior - stop after first failed parse + mach.ProcessString(L"\033]104;1;a;3\033\\"); + VERIFY_IS_FALSE(pDispatch->_resetAllColors); + VERIFY_IS_FALSE(pDispatch->_setColorTableEntry); + VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesReset.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size()); + VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesReset[0]); + pDispatch->ClearState(); + + mach.ProcessString(L"\033]104;;;\033\\"); + VERIFY_IS_FALSE(pDispatch->_setColorTableEntry); + VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesReset.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size()); + VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size()); + pDispatch->ClearState(); + } + TEST_METHOD(TestOscSetWindowTitle) { BEGIN_TEST_METHOD_PROPERTIES() From ad19d2c967e896c5426b9d98dfdbc2c4279eb319 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna Kanjeevaram Date: Thu, 10 Apr 2025 06:03:40 +0530 Subject: [PATCH 013/177] Display local time instead of UTC while restoring previous session (#18775) Closes #18727 --- src/cascadia/TerminalControl/ControlCore.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7c57fda7d0..0a94352e72 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1831,9 +1831,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation } FILETIME lastWriteTime; + FILETIME localFileTime; SYSTEMTIME lastWriteSystemTime; - if (!GetFileTime(file.get(), nullptr, nullptr, &lastWriteTime) || - !FileTimeToSystemTime(&lastWriteTime, &lastWriteSystemTime)) + + // Get the last write time in UTC + if (!GetFileTime(file.get(), nullptr, nullptr, &lastWriteTime)) + { + return; + } + + // Convert UTC FILETIME to local FILETIME + if (!FileTimeToLocalFileTime(&lastWriteTime, &localFileTime)) + { + return; + } + + // Convert local FILETIME to SYSTEMTIME + if (!FileTimeToSystemTime(&localFileTime, &lastWriteSystemTime)) { return; } From f83b98e100ff9ba8dc244d820ab79604f20ed373 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna Kanjeevaram Date: Tue, 15 Apr 2025 00:38:14 +0530 Subject: [PATCH 014/177] Add a right margin to the suggestion description textblock (#18780) This ensures that the vertical scrollbar will not cover the description text. Closes #18545 --- src/cascadia/TerminalApp/SuggestionsControl.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/TerminalApp/SuggestionsControl.xaml b/src/cascadia/TerminalApp/SuggestionsControl.xaml index 47bcef82cc..9a34ea7a79 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.xaml +++ b/src/cascadia/TerminalApp/SuggestionsControl.xaml @@ -230,6 +230,7 @@ VerticalScrollMode="Enabled" Visibility="Visible"> From 6682bed311a75be4d1d8e117bf2f514eb763a547 Mon Sep 17 00:00:00 2001 From: abutcher-gh Date: Mon, 14 Apr 2025 22:11:51 +0100 Subject: [PATCH 015/177] TerminalControl: Support MinGW path translation style (C:\ -> C:/) (#18759) ## Summary of the Pull Request Support drag-n-drop path translation in the style used by MinGW programs. In particular for usage with shells like `ash` from busybox (https://frippery.org/busybox/). ## Detailed Description of the Pull Request / Additional comments Provides a new option "mingw" for "pathTranslationStyle". Shown as "MinGW" with translation documented as `(C:\ -> C:/)` in the UI. As per the other modes, this translates `\` to `/` but stops there. There is no prefix/drive translation. ## Validation Steps Performed Run using `busybox ash` shell. Dragged directories and files from both local disks and network shares onto terminal. All were appropriately single quoted and had their backslashes replaced with forward slashes. They were directly usable by the `ash` shell. Language files containing the other options have been updated to include the new one. ## PR Checklist - [ ] Closes #xxx - [ ] Tests added/passed - [x] Documentation updated - [Docs PR #849](https://github.com/MicrosoftDocs/terminal/pull/849) - [ ] Schema updated (if necessary) Co-authored-by: Adam Butcher --- doc/cascadia/profiles.schema.json | 5 +++-- src/cascadia/TerminalControl/IControlSettings.idl | 3 ++- src/cascadia/TerminalControl/TermControl.cpp | 6 ++++++ .../TerminalSettingsEditor/Resources/de-DE/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/es-ES/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/fr-FR/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/it-IT/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ja-JP/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/pt-BR/Resources.resw | 4 ++++ .../Resources/qps-ploc/Resources.resw | 4 ++++ .../Resources/qps-ploca/Resources.resw | 4 ++++ .../Resources/qps-plocm/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ru-RU/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/zh-CN/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/zh-TW/Resources.resw | 4 ++++ .../TerminalSettingsSerializationHelpers.h | 3 ++- 17 files changed, 65 insertions(+), 4 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 8726d8303c..d60db96aac 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -3114,12 +3114,13 @@ }, "pathTranslationStyle": { "default": "none", - "description": "Controls how file paths are transformed when they are dragged and dropped on the terminal. Possible values are \"none\", \"wsl\", \"cygwin\" and \"msys2\".", + "description": "Controls how file paths are transformed when they are dragged and dropped on the terminal. Possible values are \"none\", \"wsl\", \"cygwin\", \"msys2\" and \"mingw\".", "enum": [ "none", "wsl", "cygwin", - "msys2" + "msys2", + "mingw" ], "type": "string" } diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index ccd4445c48..ad621fdff8 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -26,7 +26,8 @@ namespace Microsoft.Terminal.Control None = 0, WSL, Cygwin, - MSYS2 + MSYS2, + MinGW, }; // Class Description: diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index a20b5f9d4c..60833fdd92 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -97,6 +97,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation /* WSL */ L"/mnt/", /* Cygwin */ L"/cygdrive/", /* MSYS2 */ L"/", + /* MinGW */ {}, }; static constexpr wil::zwstring_view sSingleQuoteEscape = L"'\\''"; static constexpr auto cchSingleQuoteEscape = sSingleQuoteEscape.size(); @@ -119,6 +120,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation pos += cchSingleQuoteEscape; } + if (translationStyle == PathTranslationStyle::MinGW) + { + return; + } + if (fullPath.size() >= 2 && fullPath.at(1) == L':') { // C:/foo/bar -> Cc/foo/bar diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 80b052f11d..679417567f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Profil wird nicht mehr erkannt diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 5d98ce728e..57525e58a5 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -2316,6 +2316,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Profile no longer detected diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index f03362e5b4..0be7181131 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + El perfil ya no se ha detectado diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index c491860f73..78d6d4f832 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Le profil n’est plus détecté diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index 1c69bc653f..c314989f0a 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Profilo non più rilevato diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index 32c791239a..d7f660c8de 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + プロファイルは検出されなくなりました diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index e3ab70e74c..207ba0e0f3 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Perfil não detectado diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index 38c4dd4bc8..b7933d2b72 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -2316,6 +2316,10 @@ MSYS2 (C:\ -> /c) !!! !! {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Ρřσƒīŀē ⁿο łøňġèя ðёτęςτęď !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index 38c4dd4bc8..b7933d2b72 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -2316,6 +2316,10 @@ MSYS2 (C:\ -> /c) !!! !! {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Ρřσƒīŀē ⁿο łøňġèя ðёτęςτęď !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index 38c4dd4bc8..b7933d2b72 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -2316,6 +2316,10 @@ MSYS2 (C:\ -> /c) !!! !! {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Ρřσƒīŀē ⁿο łøňġèя ðёτęςτęď !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index 58109ce443..9c6b2b8486 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + Профиль больше не обнаруживается diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index 8e3dd54e60..48f70b7149 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + 不再检测到配置文件 diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index 4bf8f371ff..647caf9c78 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2 (C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW (C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + 已不再偵測到設定檔 diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index fa355b39ba..448eae5e43 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -794,10 +794,11 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::DefaultInputScope) JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::PathTranslationStyle) { - static constexpr std::array mappings = { + static constexpr std::array mappings = { pair_type{ "none", ValueType::None }, pair_type{ "wsl", ValueType::WSL }, pair_type{ "cygwin", ValueType::Cygwin }, pair_type{ "msys2", ValueType::MSYS2 }, + pair_type{ "mingw", ValueType::MinGW }, }; }; From 90c312f7da9499cf97c4f0575db4e69ff8352ee9 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 14 Apr 2025 23:12:01 +0200 Subject: [PATCH 016/177] Fix wide char support for WriteConsoleOutputAttribute (#18796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we overwrite the attributes during the fill, we must retain the lead/trail byte attributes. Closes #18746 ## Validation Steps Performed * Added a unit test ✅ --- src/host/_output.cpp | 14 ++++++++-- src/host/ut_host/VtIoTests.cpp | 51 ++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/host/_output.cpp b/src/host/_output.cpp index a075f5a303..fda7ae2792 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -110,15 +110,25 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon switch (mode) { case FillConsoleMode::WriteAttribute: + { for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos) { - infoBuffer[columns].Attributes = input[inputPos]; + // Overwrite all attributes except for the lead/trail byte markers. + // Those are used by WriteConsoleOutputWImplHelper to correctly serialize the input. + constexpr auto LT = COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE; + auto& attributes = infoBuffer[columns].Attributes; + attributes = (input[inputPos] & ~LT) | (attributes & LT); } break; + } case FillConsoleMode::FillAttribute: for (const auto attr = input[0]; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos) { - infoBuffer[columns].Attributes = attr; + // Overwrite all attributes except for the lead/trail byte markers. + // Those are used by WriteConsoleOutputWImplHelper to correctly serialize the input. + constexpr auto LT = COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE; + auto& attributes = infoBuffer[columns].Attributes; + attributes = (attr & ~LT) | (attributes & LT); } break; case FillConsoleMode::WriteCharacter: diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 3ad87891a2..e557ed517d 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -52,6 +52,16 @@ static constexpr std::wstring_view s_initialContentVT{ // clang-format on }; +static constexpr std::wstring_view s_initialContentVTWide{ + // clang-format off + L"" + sgr_red("〇") sgr_blu("一") sgr_red("二") sgr_blu("三") "\r\n" + sgr_red("四") sgr_blu("五") sgr_red("六") sgr_blu("七") "\r\n" + sgr_blu("八") sgr_red("九") sgr_blu("十") sgr_red("百") "\r\n" + sgr_blu("千") sgr_red("万") sgr_blu("億") sgr_red("兆") + // clang-format on +}; + class ::Microsoft::Console::VirtualTerminal::VtIoTests { BEGIN_TEST_CLASS(VtIoTests) @@ -71,11 +81,11 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests return { &rxBuf[0], read }; } - void setupInitialContents() const + void setupInitialContents(bool wide) const { auto& sm = screenInfo->GetStateMachine(); sm.ProcessString(L"\033c"); - sm.ProcessString(s_initialContentVT); + sm.ProcessString(wide ? s_initialContentVTWide : s_initialContentVT); sm.ProcessString(L"\x1b[H" sgr_rst()); } @@ -277,7 +287,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests TEST_METHOD(WriteConsoleOutputAttribute) { - setupInitialContents(); + setupInitialContents(false); static constexpr std::array payload{ red, blu, red, blu }; static constexpr til::point target{ 6, 1 }; @@ -295,7 +305,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests TEST_METHOD(WriteConsoleOutputCharacterW) { - setupInitialContents(); + setupInitialContents(false); size_t written = 0; std::string_view expected; @@ -354,7 +364,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); - setupInitialContents(); + setupInitialContents(false); // Writing at the start of a line. THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 0, 0 }, cellsModified, false)); @@ -388,6 +398,25 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests VERIFY_ARE_EQUAL(expected, actual); } + TEST_METHOD(FillConsoleOutputAttributeWide) + { + setupInitialContents(true); + + size_t cellsModified = 0; + std::string_view expected; + std::string_view actual; + + // Writing nothing should produce nothing. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 4, { 2, 1 }, cellsModified, false)); + expected = + decsc() // + cup(2, 3) sgr_red("五六") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(4u, cellsModified); + VERIFY_ARE_EQUAL(expected, actual); + } + TEST_METHOD(FillConsoleOutputCharacterW) { size_t cellsModified = 0; @@ -407,7 +436,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); - setupInitialContents(); + setupInitialContents(false); // Writing at the start of a line. THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 3, { 0, 0 }, cellsModified, false)); @@ -453,7 +482,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests std::string_view expected; std::string_view actual; - setupInitialContents(); + setupInitialContents(false); // Scrolling from nowhere to somewhere are no-ops and should not emit anything. THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false)); @@ -487,7 +516,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // // m n M N o p O P // - setupInitialContents(); + setupInitialContents(false); // Scrolling from somewhere to somewhere. // @@ -626,7 +655,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests std::string_view expected; std::string_view actual; - setupInitialContents(); + setupInitialContents(false); // Scrolling from nowhere to somewhere are no-ops and should not emit anything. THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false)); @@ -660,7 +689,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // // m n M N o p O P // - setupInitialContents(); + setupInitialContents(false); // Scrolling from somewhere to somewhere. // @@ -797,7 +826,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests &screenInfoAlt)); routines.SetConsoleActiveScreenBufferImpl(*screenInfoAlt); - setupInitialContents(); + setupInitialContents(false); THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfoAlt, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)); readOutput(); From f7e853cd9f515ff0c7e52ab0eb0604bb95d47ba3 Mon Sep 17 00:00:00 2001 From: Dustin Hall <1168515+halldk@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:15:11 -0600 Subject: [PATCH 017/177] Add 2 additional error messages (#18462) Add additional information to 2 error scenarios when launching a different profile in the `ConptyConnection.cpp` file. - Requires Elevation - File Not Found Created a profile that required elevation and verified the error message. Created profile that passed a made up command and verified the error message. Closes #7186 --- .github/actions/spelling/allow/allow.txt | 1 + .../TerminalConnection/ConptyConnection.cpp | 14 ++++++++++++++ .../Resources/en-US/Resources.resw | 10 ++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 15717467d5..f9a16af64d 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -11,6 +11,7 @@ colorbrewer commandlines consvc copyable +CText dalet dcs deselection diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 95517373c7..613ad50c91 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -428,6 +428,20 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation TerminalOutput.raise(L"\r\n"); TerminalOutput.raise(badPathText); } + // If the requested action requires elevation, display appropriate message + else if (hr == HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED)) + { + const auto elevationText = RS_(L"ElevationRequired"); + TerminalOutput.raise(L"\r\n"); + TerminalOutput.raise(elevationText); + } + // If the requested executable was not found, display appropriate message + else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + const auto fileNotFoundText = RS_(L"FileNotFound"); + TerminalOutput.raise(L"\r\n"); + TerminalOutput.raise(fileNotFoundText); + } _transitionToState(ConnectionState::Failed); diff --git a/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw b/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw index 3532e31049..82c7291a60 100644 --- a/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw @@ -209,7 +209,7 @@ You can now close this terminal with Ctrl+D, or press Enter to restart. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [error {0} when launching `{1}'] @@ -220,4 +220,10 @@ Could not access starting directory "{0}" The first argument {0} is a path to a directory on the filesystem, as provided by the user. - + + The requested operation requires elevation. + + + The system cannot find the file specified. + + \ No newline at end of file From 8b01f546cb5c1d5895d0fa517fd6bbedc9991a1b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 15 Apr 2025 01:59:46 +0200 Subject: [PATCH 018/177] Backup and restore attributes during cooked reads (#18797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use DECSC/DECRC and XTPUSHSGR/XTPOPSGR while redrawing popups, since they're drawn using the current popup colors. I wish we could just use the reverse video rendition... Closes #18742 ## Validation Steps Performed * Run `color 3f` and then press F7 * Works fine in conhost (VtPipeTerm) ✅ * Works as expected (black background) in VS Code ✅ --- src/host/readDataCooked.cpp | 39 ++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 8b88f2d4a3..ea086b1230 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -1119,12 +1119,33 @@ void COOKED_READ_DATA::_redisplay() output.append(L"\x1b[J"); } - // Disable the cursor when opening a popup, reenable it when closing them. + // Backup the attributes (DECSC) and disable the cursor when opening a popup (DECTCEM). + // Restore the attributes (DECRC) reenable the cursor when closing them (DECTCEM). if (const auto popupOpened = !_popups.empty(); _popupOpened != popupOpened) { - wchar_t buf[] = L"\x1b[?25l"; - buf[5] = popupOpened ? 'l' : 'h'; - output.append(&buf[0], 6); + wchar_t buf[] = + // Back/restore cursor position & attributes (commonly supported) + L"\u001b7" + // Show/hide cursor (commonly supported) + "\u001b[?25l" + // The popup code uses XTPUSHSGR (CSI # {) / XTPOPSGR (CSI # }) to draw the popups in the popup-colors, + // while properly restoring the previous VT attributes. On terminals that support them, the following + // won't do anything. On other terminals however, it'll reset the attributes to default. + // This is important as the first thing the popup drawing code uses CSI K to erase the previous contents + // and CSI m to reset the attributes on terminals that don't support XTPUSHSGR/XTPOPSGR. In order for + // the first CSI K to behave as if there had a previous CSI m, we must emit an initial CSI m here. + // (rarely supported) + "\x1b[#{\x1b[m\x1b[#}"; + + buf[1] = popupOpened ? '7' : '8'; + buf[7] = popupOpened ? 'l' : 'h'; + + // When the popup closes we skip the XTPUSHSGR/XTPOPSGR sequence. This is crucial because we + // use DECRC to restore the cursor position and attributes with a widely supported sequence. + // If we emitted that XTPUSHSGR/XTPOPSGR sequence it would reset the attributes again. + const size_t len = popupOpened ? 19 : 8; + + output.append(buf, len); _popupOpened = popupOpened; } @@ -1595,10 +1616,10 @@ void COOKED_READ_DATA::_popupDrawPrompt(std::vector& lines, const til::Coo str.append(suffix); std::wstring line; - line.append(L"\x1b[K"); + line.append(L"\x1b[#{\x1b[K"); _appendPopupAttr(line); const auto res = _layoutLine(line, str, 0, 0, width); - line.append(L"\x1b[m"); + line.append(L"\x1b[m\x1b[#}"); lines.emplace_back(std::move(line), 0, 0, res.column); } @@ -1654,7 +1675,7 @@ void COOKED_READ_DATA::_popupDrawCommandList(std::vector& lines, const til const auto selected = index == cl.selected && !stackedCommandNumberPopup; std::wstring line; - line.append(L"\x1b[K"); + line.append(L"\x1b[#{\x1b[K"); _appendPopupAttr(line); wchar_t scrollbarChar = L' '; @@ -1681,7 +1702,7 @@ void COOKED_READ_DATA::_popupDrawCommandList(std::vector& lines, const til } else { - line.append(L"\x1b[m "); + line.append(L"\x1b[m\x1b[#} "); } fmt::format_to(std::back_inserter(line), FMT_COMPILE(L"{:{}}: "), index, indexWidth); @@ -1690,7 +1711,7 @@ void COOKED_READ_DATA::_popupDrawCommandList(std::vector& lines, const til if (selected) { - line.append(L"\x1b[m"); + line.append(L"\x1b[m\x1b[#}"); } line.append(L"\r\n"); From 354e05d7139d00ae4c6d7ca84caa074bfd030131 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 15 Apr 2025 02:57:30 +0200 Subject: [PATCH 019/177] Fix CRLF translation when DISABLE_NEWLINE_AUTO_RETURN is reset (#18781) We can't do the `pos.x != 0` check. Instead, I replaced it with a CR check to avoid redundant CRs during CRLF translation. Closes #18735 ## Validation Steps Performed * Run the repro in the linked issue --- src/host/_stream.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 85cadb1cfe..3090151540 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -269,17 +269,26 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t case UNICODE_LINEFEED: { auto pos = cursor.GetPosition(); - if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN) && pos.x != 0) + + // If DISABLE_NEWLINE_AUTO_RETURN is not set, any LF behaves like a CRLF. + if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN)) { pos.x = 0; - // This causes the current \n to be replaced with a \r\n in the ConPTY VT output. - wch = 0; - lastCharWrapped = true; + + // Setting wch=0 and lastCharWrapped=true will cause the code at the end + // of the loop to emit a CRLF. However, we only do this if the preceding + // character isn't already a CR. We don't want to emit CR CR LF after all. + if (it == beg || it[-1] != '\r') + { + wch = 0; + lastCharWrapped = true; + } } textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false); pos.y = pos.y + 1; AdjustCursorPosition(screenInfo, pos, psScrollY); + break; } case UNICODE_CARRIAGERETURN: From 0b4f9662c7f459857b08ef70c2cfb2b6e682c6c5 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 14 Apr 2025 21:17:57 -0700 Subject: [PATCH 020/177] Fix color selection off-by-one error and dangling Y-beam (#18798) --- src/buffer/out/textBuffer.cpp | 8 -------- src/buffer/out/textBuffer.hpp | 2 -- src/cascadia/TerminalControl/ControlCore.cpp | 1 + src/cascadia/TerminalCore/Terminal.cpp | 6 +++--- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index df30a3ffb2..7cd232ad53 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2061,14 +2061,6 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const } } -size_t TextBuffer::SpanLength(const til::point coordStart, const til::point coordEnd) const -{ - const auto bufferSize = GetSize(); - // The coords are inclusive, so to get the (inclusive) length we add 1. - const auto length = bufferSize.CompareInBounds(coordEnd, coordStart) + 1; - return gsl::narrow(length); -} - // Routine Description: // - Retrieves the plain text data between the specified coordinates. // Arguments: diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 775417caab..fca973f50d 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -199,8 +199,6 @@ public: std::wstring GetCustomIdFromId(uint16_t id) const; void CopyHyperlinkMaps(const TextBuffer& OtherBuffer); - size_t SpanLength(const til::point coordStart, const til::point coordEnd) const; - std::wstring GetPlainText(til::point start, til::point end) const; struct CopyRequest diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 0a94352e72..4bd41d58d0 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -2868,6 +2868,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // coloring other matches, then we need to make sure those get redrawn, // too. _renderer->TriggerRedrawAll(); + _updateSelectionUI(); } } } diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 9e6350df04..91def44107 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1571,10 +1571,10 @@ void Terminal::SerializeMainBuffer(const wchar_t* destination) const void Terminal::ColorSelection(const TextAttribute& attr, winrt::Microsoft::Terminal::Core::MatchMode matchMode) { - const auto colorSelection = [this](const til::point coordStart, const til::point coordEnd, const TextAttribute& attr) { + const auto colorSelection = [this](const til::point coordStartInclusive, const til::point coordEndExclusive, const TextAttribute& attr) { auto& textBuffer = _activeBuffer(); - const auto spanLength = textBuffer.SpanLength(coordStart, coordEnd); - textBuffer.Write(OutputCellIterator(attr, spanLength), coordStart); + const auto spanLength = textBuffer.GetSize().CompareInBounds(coordEndExclusive, coordStartInclusive, true); + textBuffer.Write(OutputCellIterator(attr, spanLength), coordStartInclusive); }; for (const auto [start, end] : _GetSelectionSpans()) From 3accdcfc6bf17a6b3fc4dfc26b816ba16ec410de Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 15 Apr 2025 22:22:09 +0200 Subject: [PATCH 021/177] Fix cwd not applying on launch (#18801) --- src/cascadia/TerminalApp/Remoting.cpp | 2 +- src/cascadia/TerminalApp/Remoting.h | 1 - src/cascadia/TerminalApp/TerminalPage.cpp | 7 +++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/Remoting.cpp b/src/cascadia/TerminalApp/Remoting.cpp index 8383fb98fd..0c8693ed46 100644 --- a/src/cascadia/TerminalApp/Remoting.cpp +++ b/src/cascadia/TerminalApp/Remoting.cpp @@ -17,7 +17,7 @@ namespace winrt::TerminalApp::implementation { CommandlineArgs::CommandlineArgs(winrt::array_view args, winrt::hstring currentDirectory, uint32_t showWindowCommand, winrt::hstring envString) : _args{ args.begin(), args.end() }, - _cwd{ std::move(currentDirectory) }, + CurrentDirectory{ std::move(currentDirectory) }, ShowWindowCommand{ showWindowCommand }, CurrentEnvironment{ std::move(envString) } { diff --git a/src/cascadia/TerminalApp/Remoting.h b/src/cascadia/TerminalApp/Remoting.h index a86619c4dc..2ad297f31e 100644 --- a/src/cascadia/TerminalApp/Remoting.h +++ b/src/cascadia/TerminalApp/Remoting.h @@ -36,7 +36,6 @@ namespace winrt::TerminalApp::implementation ::TerminalApp::AppCommandlineArgs _parsed; int32_t _parseResult = 0; winrt::com_array _args; - winrt::hstring _cwd; }; struct RequestReceiveContentArgs : RequestReceiveContentArgsT diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index fb061e548c..581b0ddcf3 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -563,8 +563,11 @@ namespace winrt::TerminalApp::implementation _WindowProperties.VirtualEnvVars(originalVirtualEnv); } }); - _WindowProperties.VirtualWorkingDirectory(cwd); - _WindowProperties.VirtualEnvVars(env); + if (!cwd.empty()) + { + _WindowProperties.VirtualWorkingDirectory(cwd); + _WindowProperties.VirtualEnvVars(env); + } for (size_t i = 0; i < actions.size(); ++i) { From 712ce5fa2f8120929ab8156dcfa9c7833ad573bc Mon Sep 17 00:00:00 2001 From: Windows Console Service Bot <14666831+consvc@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:13:59 -0500 Subject: [PATCH 022/177] Localization Updates - main - 04/16/2025 21:02:38 (#18807) --- .../TerminalConnection/Resources/de-DE/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/es-ES/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/fr-FR/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/it-IT/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/ja-JP/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/ko-KR/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/pt-BR/Resources.resw | 8 +++++++- .../Resources/qps-ploc/Resources.resw | 10 ++++++++-- .../Resources/qps-ploca/Resources.resw | 10 ++++++++-- .../Resources/qps-plocm/Resources.resw | 10 ++++++++-- .../TerminalConnection/Resources/ru-RU/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/zh-CN/Resources.resw | 8 +++++++- .../TerminalConnection/Resources/zh-TW/Resources.resw | 8 +++++++- .../Resources/ko-KR/Resources.resw | 4 ++++ .../Resources/qps-ploc/Resources.resw | 2 +- .../Resources/qps-ploca/Resources.resw | 2 +- .../Resources/qps-plocm/Resources.resw | 2 +- 17 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/cascadia/TerminalConnection/Resources/de-DE/Resources.resw b/src/cascadia/TerminalConnection/Resources/de-DE/Resources.resw index 8c2f336a59..3f3a417d28 100644 --- a/src/cascadia/TerminalConnection/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/de-DE/Resources.resw @@ -209,7 +209,7 @@ Sie können dieses Terminal jetzt mit STRG+D schließen oder zum Neustart die EINGABETASTE drücken. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [Fehler {0} beim Start von `{1}'] @@ -220,4 +220,10 @@ Auf das Startverzeichnis „{0}“ konnte nicht zugegriffen werden The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + Für den angeforderten Vorgang sind erhöhte Rechte erforderlich. + + + Die angegebene Datei wurde nicht gefunden. + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/es-ES/Resources.resw b/src/cascadia/TerminalConnection/Resources/es-ES/Resources.resw index 72810e2a7a..b6518ee7cd 100644 --- a/src/cascadia/TerminalConnection/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/es-ES/Resources.resw @@ -209,7 +209,7 @@ Ahora puede cerrar este terminal con Ctrl+D o presionar Entrar para reiniciar. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [error {0} al iniciar `{1}'] @@ -220,4 +220,10 @@ No se pudo obtener acceso al directorio inicial «{0}» The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + La operación solicitada requiere elevación. + + + El sistema no puede encontrar el archivo especificado. + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalConnection/Resources/fr-FR/Resources.resw index e289e48032..d679cb34be 100644 --- a/src/cascadia/TerminalConnection/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/fr-FR/Resources.resw @@ -209,7 +209,7 @@ Vous pouvez maintenant fermer ce terminal avec Ctrl+D, ou appuyez sur Entrée pour redémarrer. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [erreur {0} lors du lancement de `{1}'] @@ -220,4 +220,10 @@ Le démarrage du répertoire n’est pas accessible «{0}» The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + L’opération demandée nécessite une élévation. + + + Le fichier spécifié est introuvable. + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/it-IT/Resources.resw b/src/cascadia/TerminalConnection/Resources/it-IT/Resources.resw index 56ae1ac16c..901e01ead5 100644 --- a/src/cascadia/TerminalConnection/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/it-IT/Resources.resw @@ -209,7 +209,7 @@ È ora possibile chiudere il terminale con CTRL+D oppure premere INVIO per riavviarlo. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [errore {0} durante l'avvio di `{1}'] @@ -220,4 +220,10 @@ Non è possibile accedere alla directory di avvio "{0}" The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + Per l'operazione richiesta è necessaria l'elevazione dei privilegi. + + + Impossibile trovare il file specificato. + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalConnection/Resources/ja-JP/Resources.resw index 2f16581c88..0791abdea3 100644 --- a/src/cascadia/TerminalConnection/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/ja-JP/Resources.resw @@ -209,7 +209,7 @@ このターミナルを Ctrl+D で閉じるか、Enter キーを押して再起動できます。 - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). ['{1}' の起動時にエラー {0} が発生しました] @@ -220,4 +220,10 @@ 先頭のディレクトリ "{0}" にアクセスできませんでした The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + 要求された操作には、権限の昇格が必要です。 + + + 指定されたファイルが見つかりません。 + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalConnection/Resources/ko-KR/Resources.resw index a161a1aebc..28518fb6ff 100644 --- a/src/cascadia/TerminalConnection/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/ko-KR/Resources.resw @@ -209,7 +209,7 @@ 이제 Ctrl+D 이 터미널을 닫거나 Enter 키를 눌러 다시 시작할 수 있습니다. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [’{1}' 시작 시 {0} 오류 발생] @@ -220,4 +220,10 @@ 시작 디렉터리 "{0}"에 액세스할 수 없습니다. The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + 요청한 작업을 수행하려면 권한 상승이 필요합니다. + + + 시스템에서 지정된 파일을 찾을 수 없습니다. + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalConnection/Resources/pt-BR/Resources.resw index 5ebee962e9..1fc00e6777 100644 --- a/src/cascadia/TerminalConnection/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/pt-BR/Resources.resw @@ -209,7 +209,7 @@ Agora você pode fechar este terminal com Ctrl+D ou pressione Enter para reiniciar. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [erro {0} ao iniciar "{1}"] @@ -220,4 +220,10 @@ Não foi possível acessar o diretório inicial "{0}" The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + A operação solicitada exige elevação. + + + O sistema não pode encontrar o arquivo especificado. + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalConnection/Resources/qps-ploc/Resources.resw index 3bac0793ae..efbf49decb 100644 --- a/src/cascadia/TerminalConnection/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/qps-ploc/Resources.resw @@ -209,7 +209,7 @@ Ỳóŭ ćǻή иòω сĺøѕè ťнįş тёѓмîήªŀ ωīťђ Çťѓℓ+Ď, όг ргéšѕ Σñтèř το ґèšтªят. !!! !!! !!! !!! !!! !!! !!! - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [℮ѓѓŏŕ {0} ωĥєй łåύñćђίηğ `{1}'] !!! !!! !!! @@ -220,4 +220,10 @@ Ċőŭľđ йōť ª¢čеѕş şτāŗťΐиğ ðιѓεςтоŗγ "{0}" !!! !!! !!! !!! The first argument {0} is a path to a directory on the filesystem, as provided by the user. - + + Ţнė ŗēqμĕѕŧєđ ôφ℮ґάтĩöй ŕєqΰїŗęś ėĺęνáτîøŋ. !!! !!! !!! !!! + + + Ŧнё şγśţêм ςâлηόť ƒїлď ŧнэ ƒΐĺë śрéćιƒієð. !!! !!! !!! !!! + + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalConnection/Resources/qps-ploca/Resources.resw index 3bac0793ae..efbf49decb 100644 --- a/src/cascadia/TerminalConnection/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/qps-ploca/Resources.resw @@ -209,7 +209,7 @@ Ỳóŭ ćǻή иòω сĺøѕè ťнįş тёѓмîήªŀ ωīťђ Çťѓℓ+Ď, όг ргéšѕ Σñтèř το ґèšтªят. !!! !!! !!! !!! !!! !!! !!! - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [℮ѓѓŏŕ {0} ωĥєй łåύñćђίηğ `{1}'] !!! !!! !!! @@ -220,4 +220,10 @@ Ċőŭľđ йōť ª¢čеѕş şτāŗťΐиğ ðιѓεςтоŗγ "{0}" !!! !!! !!! !!! The first argument {0} is a path to a directory on the filesystem, as provided by the user. - + + Ţнė ŗēqμĕѕŧєđ ôφ℮ґάтĩöй ŕєqΰїŗęś ėĺęνáτîøŋ. !!! !!! !!! !!! + + + Ŧнё şγśţêм ςâлηόť ƒїлď ŧнэ ƒΐĺë śрéćιƒієð. !!! !!! !!! !!! + + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalConnection/Resources/qps-plocm/Resources.resw index 3bac0793ae..efbf49decb 100644 --- a/src/cascadia/TerminalConnection/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/qps-plocm/Resources.resw @@ -209,7 +209,7 @@ Ỳóŭ ćǻή иòω сĺøѕè ťнįş тёѓмîήªŀ ωīťђ Çťѓℓ+Ď, όг ргéšѕ Σñтèř το ґèšтªят. !!! !!! !!! !!! !!! !!! !!! - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [℮ѓѓŏŕ {0} ωĥєй łåύñćђίηğ `{1}'] !!! !!! !!! @@ -220,4 +220,10 @@ Ċőŭľđ йōť ª¢čеѕş şτāŗťΐиğ ðιѓεςтоŗγ "{0}" !!! !!! !!! !!! The first argument {0} is a path to a directory on the filesystem, as provided by the user. - + + Ţнė ŗēqμĕѕŧєđ ôφ℮ґάтĩöй ŕєqΰїŗęś ėĺęνáτîøŋ. !!! !!! !!! !!! + + + Ŧнё şγśţêм ςâлηόť ƒїлď ŧнэ ƒΐĺë śрéćιƒієð. !!! !!! !!! !!! + + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalConnection/Resources/ru-RU/Resources.resw index 0863bfa562..ce5e6e54de 100644 --- a/src/cascadia/TerminalConnection/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/ru-RU/Resources.resw @@ -209,7 +209,7 @@ Теперь вы можете закрыть этот терминал с помощью клавиш CTRL+D. Или нажмите клавишу ВВОД для перезапуска. - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [ошибка {0} при запуске "{1}"] @@ -220,4 +220,10 @@ Не удалось получить доступ к запуску каталога "{0}" The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + Запрошенная операция требует получения дополнительных прав. + + + Не удается найти указанный файл. + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalConnection/Resources/zh-CN/Resources.resw index 84771df255..44d8d926e6 100644 --- a/src/cascadia/TerminalConnection/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/zh-CN/Resources.resw @@ -209,7 +209,7 @@ 现在可以使用Ctrl+D关闭此终端,或按 Enter 重新启动。 - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [出现错误 {0} (启动“{1}”时)] @@ -220,4 +220,10 @@ 无法访问启动目录“{0}” The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + 请求的操作需要提升。 + + + 系统找不到指定的文件。 + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalConnection/Resources/zh-TW/Resources.resw index ef14f26052..bb93a4f393 100644 --- a/src/cascadia/TerminalConnection/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/zh-TW/Resources.resw @@ -209,7 +209,7 @@ 您現在可以使用 Ctrl+D 關閉此終端機,或按 Enter 重新啟動。 - "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). + "Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter). [啟動 `{1}' 時發生錯誤 {0}] @@ -220,4 +220,10 @@ 無法存取開始目錄 "{0}" The first argument {0} is a path to a directory on the filesystem, as provided by the user. + + 要求的操作需要提高權限。 + + + 系統找不到指定的檔案。 + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index 0e5577a351..6c0cdccc25 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -2312,6 +2312,10 @@ MSYS2(C:\ -> /c) {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. + + MinGW(C:\ -> C:/) + {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. + 프로필이 더 이상 검색되지 않음 diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index b7933d2b72..e0ca3c51b9 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -2317,7 +2317,7 @@ {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. - MinGW (C:\ -> C:/) + MinGW (C:\ -> C:/) !!! !! {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index b7933d2b72..e0ca3c51b9 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -2317,7 +2317,7 @@ {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. - MinGW (C:\ -> C:/) + MinGW (C:\ -> C:/) !!! !! {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index b7933d2b72..e0ca3c51b9 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -2317,7 +2317,7 @@ {Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting. - MinGW (C:\ -> C:/) + MinGW (C:\ -> C:/) !!! !! {Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting. From 68d9e0d0389101d65f91e5f3bd3fda34df7564f3 Mon Sep 17 00:00:00 2001 From: Heiko <61519853+htcfreek@users.noreply.github.com> Date: Sat, 19 Apr 2025 01:01:12 +0200 Subject: [PATCH 023/177] [Enterprise, GPO] Add "Default Terminal app" policy to definition template (#18363) This PR adds a new policy definition to the ADMX templates for settings the default Terminal application in Windows. > [!Note] > This PR does not change any code of Windows, Console Host or Windows Terminal. It only adds the definition for a new policy to the templates. I got the registry values form the documentation and by testing the values. The policy is only available as user policy because the registry values have to be in HKCU. The Policy is implemented as preference (not inside the Policy key) and therefore keeps it's value on removing (not configured) it. You can see this in `gpedit.msc` on the policy symbol and the hint in the description. Closes #18302 Refs #18303 --- policies/WindowsTerminal.admx | 57 +++++++++++++++++++++++++++++ policies/en-US/WindowsTerminal.adml | 12 ++++++ 2 files changed, 69 insertions(+) diff --git a/policies/WindowsTerminal.admx b/policies/WindowsTerminal.admx index 48c36aec7e..47c2f981bb 100644 --- a/policies/WindowsTerminal.admx +++ b/policies/WindowsTerminal.admx @@ -9,6 +9,7 @@ + @@ -24,5 +25,61 @@ + + + + + + + + {00000000-0000-0000-0000-000000000000} + + + + + {00000000-0000-0000-0000-000000000000} + + + + + + + {B23D10C0-E52E-411E-9D5B-C09FDF709C7D} + + + + + {B23D10C0-E52E-411E-9D5B-C09FDF709C7D} + + + + + + + {E12CFF52-A866-4C77-9A90-F570A7AA2C6B} + + + + + {2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69} + + + + + + + {86633F1F-6454-40EC-89CE-DA4EBA977EE2} + + + + + {06EC847C-C0A5-46B8-92CB-7C92F6E35CD5} + + + + + + + diff --git a/policies/en-US/WindowsTerminal.adml b/policies/en-US/WindowsTerminal.adml index 5516292e12..f2bcf71d3c 100644 --- a/policies/en-US/WindowsTerminal.adml +++ b/policies/en-US/WindowsTerminal.adml @@ -7,6 +7,7 @@ Windows Terminal At least Windows Terminal 1.21 + At least Windows 11 22H2 or Windows 10 22H2 (Build 19045.3031, KB5026435) with Windows Terminal 1.17 Disabled Profile Sources Profiles will not be generated from any sources listed here. Source names can be arbitrary strings. Potential candidates can be found as the "source" property on profile definitions in Windows Terminal's settings.json file. @@ -18,11 +19,22 @@ Common sources are: For instance, setting this policy to Windows.Terminal.Wsl will disable the builtin WSL integration of Windows Terminal. Note: Existing profiles will disappear from Windows Terminal after adding their source to this policy. + Default terminal application + Select the default terminal application used in Windows. + +If you select Windows Terminal Preview and it is not installed the system will fallback to the legacy Windows Console Host. (Please note that the settings interfaces showing "Let windows decide" in this case as configuration.) + Automatic selection (Windows Terminal, if available) + Windows Console Host (legacy) + Windows Terminal + Windows Terminal Preview (if available) List of disabled sources (one per line) + + Select from the following options: + From 773a4b919853369166811becb87b9e78ad918211 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 23 Apr 2025 19:15:51 +0200 Subject: [PATCH 024/177] Implement DECSET 2026 - Synchronized Output (#18826) --- src/renderer/base/renderer.cpp | 72 +++++++++++++++++++++++++- src/renderer/base/renderer.hpp | 4 ++ src/renderer/base/thread.cpp | 4 +- src/terminal/adapter/DispatchTypes.hpp | 1 + src/terminal/adapter/adaptDispatch.cpp | 13 +++++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 90f6d5936f..5824bcb2f2 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -4,8 +4,6 @@ #include "precomp.h" #include "renderer.hpp" -#pragma hdrstop - using namespace Microsoft::Console::Render; using namespace Microsoft::Console::Types; @@ -90,6 +88,11 @@ IRenderData* Renderer::GetRenderData() const noexcept _pData->UnlockConsole(); }); + if (_isSynchronizingOutput) + { + _synchronizeWithOutput(); + } + // Last chance check if anything scrolled without an explicit invalidate notification since the last frame. _CheckViewportAndScroll(); @@ -183,6 +186,71 @@ void Renderer::NotifyPaintFrame() noexcept _thread.NotifyPaint(); } +// NOTE: You must be holding the console lock when calling this function. +void Renderer::SynchronizedOutputBegin() noexcept +{ + // Kick the render thread into calling `_synchronizeWithOutput()`. + _isSynchronizingOutput = true; +} + +// NOTE: You must be holding the console lock when calling this function. +void Renderer::SynchronizedOutputEnd() noexcept +{ + // Unblock `_synchronizeWithOutput()` from the `WaitOnAddress` call. + _isSynchronizingOutput = false; + WakeByAddressSingle(&_isSynchronizingOutput); + + // It's crucial to give the render thread at least a chance to gain the lock. + // Otherwise, a VT application could continuously spam DECSET 2026 (Synchronized Output) and + // essentially drop our renderer to 10 FPS, because `_isSynchronizingOutput` is always true. + // + // Obviously calling LockConsole/UnlockConsole here is an awful, ugly hack, + // since there's no guarantee that this is the same lock as the one the VT parser uses. + // But the alternative is Denial-Of-Service of the render thread. + // + // Note that this causes raw throughput of DECSET 2026 to be comparatively low, but that's fine. + // Apps that use DECSET 2026 don't produce that sequence continuously, but rather at a fixed rate. + _pData->UnlockConsole(); + _pData->LockConsole(); +} + +void Renderer::_synchronizeWithOutput() noexcept +{ + constexpr DWORD timeout = 100; + + UINT64 start = 0; + DWORD elapsed = 0; + bool wrong = false; + + QueryUnbiasedInterruptTime(&start); + + // Wait for `_isSynchronizingOutput` to be set to false or for a timeout to occur. + while (true) + { + // We can't call a blocking function while holding the console lock, so release it temporarily. + _pData->UnlockConsole(); + const auto ok = WaitOnAddress(&_isSynchronizingOutput, &wrong, sizeof(_isSynchronizingOutput), timeout - elapsed); + _pData->LockConsole(); + + if (!ok || !_isSynchronizingOutput) + { + break; + } + + UINT64 now; + QueryUnbiasedInterruptTime(&now); + elapsed = static_cast((now - start) / 10000); + if (elapsed >= timeout) + { + break; + } + } + + // If a timeout occurred, `_isSynchronizingOutput` may still be true. + // Set it to false now to skip calling `_synchronizeWithOutput()` on the next frame. + _isSynchronizingOutput = false; +} + // Routine Description: // - Called when the system has requested we redraw a portion of the console. // Arguments: diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index d5f6be56d1..c8c3045760 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -35,6 +35,8 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PaintFrame(); void NotifyPaintFrame() noexcept; + void SynchronizedOutputBegin() noexcept; + void SynchronizedOutputEnd() noexcept; void TriggerSystemRedraw(const til::rect* const prcDirtyClient); void TriggerRedraw(const Microsoft::Console::Types::Viewport& region); void TriggerRedraw(const til::point* const pcoord); @@ -91,6 +93,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _PaintFrame() noexcept; [[nodiscard]] HRESULT _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept; + void _synchronizeWithOutput() noexcept; bool _CheckViewportAndScroll(); [[nodiscard]] HRESULT _PaintBackground(_In_ IRenderEngine* const pEngine); void _PaintBufferOutput(_In_ IRenderEngine* const pEngine); @@ -124,6 +127,7 @@ namespace Microsoft::Console::Render std::function _pfnBackgroundColorChanged; std::function _pfnFrameColorChanged; std::function _pfnRendererEnteredErrorState; + bool _isSynchronizingOutput = false; bool _forceUpdateViewport = false; til::point_span _lastSelectionPaintSpan{}; diff --git a/src/renderer/base/thread.cpp b/src/renderer/base/thread.cpp index 467bef7358..39b7ec6078 100644 --- a/src/renderer/base/thread.cpp +++ b/src/renderer/base/thread.cpp @@ -34,7 +34,7 @@ DWORD WINAPI RenderThread::_ThreadProc() // Between waiting on _hEvent and calling PaintFrame() there should be a minimal delay, // so that a key press progresses to a drawing operation as quickly as possible. - // As such, we wait for the renderer to complete _before_ waiting on _hEvent. + // As such, we wait for the renderer to complete _before_ waiting on `_redraw`. renderer->WaitUntilCanRender(); _redraw.wait(); @@ -78,6 +78,8 @@ void RenderThread::EnablePainting() noexcept } } +// This function is meant to only be called by `Renderer`. You should use `TriggerTeardown()` instead, +// even if you plan to call `EnablePainting()` later, because that ensures proper synchronization. void RenderThread::DisablePainting() noexcept { _enable.ResetEvent(); diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index f1adb1457c..b99dd997dd 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -544,6 +544,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes ALTERNATE_SCROLL = DECPrivateMode(1007), ASB_AlternateScreenBuffer = DECPrivateMode(1049), XTERM_BracketedPasteMode = DECPrivateMode(2004), + SO_SynchronizedOutput = DECPrivateMode(2026), GCM_GraphemeClusterMode = DECPrivateMode(2027), W32IM_Win32InputMode = DECPrivateMode(9001), }; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 9158b962ab..c8f4c5eb85 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1914,6 +1914,19 @@ void AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con case DispatchTypes::ModeParams::XTERM_BracketedPasteMode: _api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, enable); break; + case DispatchTypes::ModeParams::SO_SynchronizedOutput: + if (_renderer) + { + if (enable) + { + _renderer->SynchronizedOutputBegin(); + } + else + { + _renderer->SynchronizedOutputEnd(); + } + } + break; case DispatchTypes::ModeParams::GCM_GraphemeClusterMode: break; case DispatchTypes::ModeParams::W32IM_Win32InputMode: From a8a47b93671361e529ff9f967d0e7c1b028eebb9 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 23 Apr 2025 12:34:47 -0700 Subject: [PATCH 025/177] [SUI] Improve accessibility to open json (#18828) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "open JSON" button in the settings UI wasn't working when invoked via accessibility tools (specifically Narrator in scan mode and Voice Access). For some reason, in those scenarios, neither the `Tapped` or `KeyDown` event were hit! This PR adds the logic to open the json file via the `ItemInvoked` event instead. The `Tapped` and `KeyDown` handlers were removed to prevent a redundant `OpenJson` event being raised. Additionally, `SelectsOnInvoked` was set to `False` on the "open JSON" nav item. This prevents the selection pill from moving to the nav item, which feels more correct. ## Validation Steps Performed The following scenarios are confirmed to open the JSON ✅ Mouse click ✅ Keyboard (Spacebar and Enter) ✅ Voice Access ✅ Narrator in scan mode For all of these (except Voice Access), I've confirmed that holding the Alt button while invoking the JSON button opens defaults.json. Closes #18770 Closes #12003 --- .../TerminalSettingsEditor/MainPage.cpp | 33 +++++++------------ .../TerminalSettingsEditor/MainPage.h | 2 -- .../TerminalSettingsEditor/MainPage.xaml | 4 +-- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 5daf396b72..054c8480cc 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -38,6 +38,7 @@ using namespace winrt::Windows::System; using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::Foundation::Collections; +static const std::wstring_view openJsonTag{ L"OpenJson_Nav" }; static const std::wstring_view launchTag{ L"Launch_Nav" }; static const std::wstring_view interactionTag{ L"Interaction_Nav" }; static const std::wstring_view renderingTag{ L"Rendering_Nav" }; @@ -324,6 +325,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (const auto navString = clickedItemContainer.Tag().try_as()) { + if (*navString == openJsonTag) + { + const auto window = CoreWindow::GetForCurrentThread(); + const auto rAltState = window.GetKeyState(VirtualKey::RightMenu); + const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu); + const auto altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) || + WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); + const auto target = altPressed ? SettingsTarget::DefaultsFile : SettingsTarget::SettingsFile; + OpenJson.raise(nullptr, target); + return; + } _Navigate(*navString, BreadcrumbSubPage::None); } else if (const auto profile = clickedItemContainer.Tag().try_as()) @@ -575,27 +587,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - void MainPage::OpenJsonTapped(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& /*args*/) - { - const auto window = CoreWindow::GetForCurrentThread(); - const auto rAltState = window.GetKeyState(VirtualKey::RightMenu); - const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu); - const auto altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) || - WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); - - const auto target = altPressed ? SettingsTarget::DefaultsFile : SettingsTarget::SettingsFile; - OpenJson.raise(nullptr, target); - } - - void MainPage::OpenJsonKeyDown(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& args) - { - if (args.Key() == VirtualKey::Enter || args.Key() == VirtualKey::Space) - { - const auto target = args.KeyStatus().IsMenuKeyDown ? SettingsTarget::DefaultsFile : SettingsTarget::SettingsFile; - OpenJson.raise(nullptr, target); - } - } - void MainPage::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) { _settingsClone.LogSettingChanges(false); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 81724effb1..582f8813b1 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -30,8 +30,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void UpdateSettings(const Model::CascadiaSettings& settings); - void OpenJsonKeyDown(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& args); - void OpenJsonTapped(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& args); void SettingsNav_Loaded(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); void SettingsNav_ItemInvoked(const Microsoft::UI::Xaml::Controls::NavigationView& sender, const Microsoft::UI::Xaml::Controls::NavigationViewItemInvokedEventArgs& args); void SaveButton_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 3a0773062a..c6d36e8f20 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -171,8 +171,8 @@ + SelectsOnInvoked="False" + Tag="OpenJson_Nav"> From 29924217615d58940d8309bec49476fa9dd2fb6b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 23 Apr 2025 22:27:21 +0200 Subject: [PATCH 026/177] Fix a major stdin wakeup race condition (#18816) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The conhost v2 rewrite from a decade ago introduced a race condition: Previously, we would acquire and hold the global console lock while servicing a console API call. If the call cannot be completed a wait task is enqueued, while the lock is held. The v2 rewrite then split the project up into a "server" and "host" component (which remain to this day). The "host" would hold the console lock, while the "server" was responsible for enqueueing wait tasks _outside of the console lock_. Without any form of synchronization, any operations on the waiter list would then of course introduce a race condition. In conhost this primarily meant keyboard/mouse input, because that runs on the separate Win32 window thread. For Windows Terminal it primarily meant the VT input thread. I do not know why this issue is so extremely noticeable specifically when we respond to DSC CPR requests, but I'm also not surprised: I suspect that the overall performance issues that conhost had for a long time, meant that most things it did were slower than allocating the wait task. Now that both conhost and Windows Terminal became orders of magnitudes faster over the last few years, it probably just so happens that the DSC CPR request takes almost exactly as many cycles to complete as allocating the wait task does, hence perfectly reproducing the race condition. There's also a slight chance that this is actually a regression from my ConPTY rewrite #17510, but I fail to see what that would be. Regardless of that, I'm 100% certain though, that this is a bug that has existed in v0.1. Closes #18117 Closes #18800 ## Validation Steps Performed * See repro in #18800. In other words: * Continuously emit DSC CPR sequences * ...read the response from stdin * ...and print the response to stdout * Doesn't deadlock randomly anymore ✅ * Feature & Unit tests ✅ --- src/host/ApiRoutines.h | 8 +- src/host/_stream.cpp | 182 ++++++++----------------- src/host/_stream.h | 5 +- src/host/directio.cpp | 8 +- src/host/server.h | 6 +- src/host/stream.cpp | 77 ++++++----- src/host/ut_host/ApiRoutinesTests.cpp | 62 ++------- src/host/ut_host/ScreenBufferTests.cpp | 16 +-- src/host/ut_host/TextBufferTests.cpp | 16 +-- src/host/ut_host/VtIoTests.cpp | 7 +- src/host/writeData.cpp | 42 +++--- src/host/writeData.hpp | 6 +- src/server/ApiDispatchers.cpp | 42 ++---- src/server/IApiRoutines.h | 10 +- src/server/WaitBlock.cpp | 9 ++ src/server/WaitQueue.h | 1 - 16 files changed, 172 insertions(+), 325 deletions(-) diff --git a/src/host/ApiRoutines.h b/src/host/ApiRoutines.h index f6fc4ad4d8..9d6affe33b 100644 --- a/src/host/ApiRoutines.h +++ b/src/host/ApiRoutines.h @@ -56,12 +56,12 @@ public: const bool IsUnicode, const bool IsPeek, const bool IsWaitAllowed, - std::unique_ptr& waiter) noexcept override; + CONSOLE_API_MSG* pWaitReplyMessage) noexcept override; [[nodiscard]] HRESULT ReadConsoleImpl(IConsoleInputObject& context, std::span buffer, size_t& written, - std::unique_ptr& waiter, + CONSOLE_API_MSG* pWaitReplyMessage, const std::wstring_view initialData, const std::wstring_view exeName, INPUT_READ_HANDLE_DATA& readHandleState, @@ -73,12 +73,12 @@ public: [[nodiscard]] HRESULT WriteConsoleAImpl(IConsoleOutputObject& context, const std::string_view buffer, size_t& read, - std::unique_ptr& waiter) noexcept override; + CONSOLE_API_MSG* pWaitReplyMessage) noexcept override; [[nodiscard]] HRESULT WriteConsoleWImpl(IConsoleOutputObject& context, const std::wstring_view buffer, size_t& read, - std::unique_ptr& waiter) noexcept override; + CONSOLE_API_MSG* pWaitReplyMessage) noexcept override; #pragma region ThreadCreationInfo [[nodiscard]] HRESULT GetConsoleLangIdImpl(LANGID& langId) noexcept override; diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 3090151540..1bb023a861 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -415,30 +415,9 @@ void WriteClearScreen(SCREEN_INFORMATION& screenInfo) // - pwchBuffer - wide character text to be inserted into buffer // - pcbBuffer - byte count of pwchBuffer on the way in, number of bytes consumed on the way out. // - screenInfo - Screen Information class to write the text into at the current cursor position -// - ppWaiter - If writing to the console is blocked for whatever reason, this will be filled with a pointer to context -// that can be used by the server to resume the call at a later time. -// Return Value: -// - STATUS_SUCCESS if OK. -// - CONSOLE_STATUS_WAIT if we couldn't finish now and need to be called back later (see ppWaiter). -// - Or a suitable NTSTATUS format error code for memory/string/math failures. -[[nodiscard]] NTSTATUS DoWriteConsole(_In_reads_bytes_(*pcbBuffer) PCWCHAR pwchBuffer, - _Inout_ size_t* const pcbBuffer, - SCREEN_INFORMATION& screenInfo, - std::unique_ptr& waiter) +[[nodiscard]] HRESULT DoWriteConsole(SCREEN_INFORMATION& screenInfo, std::wstring_view str) try { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))) - { - waiter = std::make_unique(screenInfo, - pwchBuffer, - *pcbBuffer, - gci.OutputCP); - return CONSOLE_STATUS_WAIT; - } - - const std::wstring_view str{ pwchBuffer, *pcbBuffer / sizeof(WCHAR) }; - if (WI_IsAnyFlagClear(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) { WriteCharsLegacy(screenInfo, str, nullptr); @@ -447,55 +426,9 @@ try { WriteCharsVT(screenInfo, str); } - - return STATUS_SUCCESS; -} -NT_CATCH_RETURN() - -// Routine Description: -// - This method performs the actual work of attempting to write to the console, converting data types as necessary -// to adapt from the server types to the legacy internal host types. -// - It operates on Unicode data only. It's assumed the text is translated by this point. -// Arguments: -// - OutContext - the console output object to write the new text into -// - pwsTextBuffer - wide character text buffer provided by client application to insert -// - cchTextBufferLength - text buffer counted in characters -// - pcchTextBufferRead - character count of the number of characters we were able to insert before returning -// - ppWaiter - If we are blocked from writing now and need to wait, this is filled with contextual data for the server to restore the call later -// Return Value: -// - S_OK if successful. -// - S_OK if we need to wait (check if ppWaiter is not nullptr). -// - Or a suitable HRESULT code for math/string/memory failures. -[[nodiscard]] HRESULT WriteConsoleWImplHelper(IConsoleOutputObject& context, - const std::wstring_view buffer, - size_t& read, - std::unique_ptr& waiter) noexcept -{ - try - { - // Set out variables in case we exit early. - read = 0; - waiter.reset(); - - // Convert characters to bytes to give to DoWriteConsole. - size_t cbTextBufferLength; - RETURN_IF_FAILED(SizeTMult(buffer.size(), sizeof(wchar_t), &cbTextBufferLength)); - - auto Status = DoWriteConsole(const_cast(buffer.data()), &cbTextBufferLength, context, waiter); - - // Convert back from bytes to characters for the resulting string length written. - read = cbTextBufferLength / sizeof(wchar_t); - - if (Status == CONSOLE_STATUS_WAIT) - { - FAIL_FAST_IF_NULL(waiter.get()); - Status = STATUS_SUCCESS; - } - - RETURN_NTSTATUS(Status); - } - CATCH_RETURN(); + return S_OK; } +CATCH_RETURN() // Routine Description: // - Writes non-Unicode formatted data into the given console output object. @@ -514,13 +447,12 @@ NT_CATCH_RETURN() [[nodiscard]] HRESULT ApiRoutines::WriteConsoleAImpl(IConsoleOutputObject& context, const std::string_view buffer, size_t& read, - std::unique_ptr& waiter) noexcept + CONSOLE_API_MSG* pWaitReplyMessage) noexcept { try { // Ensure output variables are initialized. read = 0; - waiter.reset(); if (buffer.empty()) { @@ -620,67 +552,63 @@ NT_CATCH_RETURN() wstr.resize((dbcsLength + mbPtrLength) / sizeof(wchar_t)); } - // Hold the specific version of the waiter locally so we can tinker with it if we have to store additional context. - std::unique_ptr writeDataWaiter{}; - - // Make the W version of the call - size_t wcBufferWritten{}; - const auto hr{ WriteConsoleWImplHelper(screenInfo, wstr, wcBufferWritten, writeDataWaiter) }; - - // If there is no waiter, process the byte count now. - if (nullptr == writeDataWaiter.get()) + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))) { - // Calculate how many bytes of the original A buffer were consumed in the W version of the call to satisfy mbBufferRead. - // For UTF-8 conversions, we've already returned this information above. - if (CP_UTF8 != codepage) - { - size_t mbBufferRead{}; + const auto waiter = new WriteData(screenInfo, std::move(wstr), gci.OutputCP); - // Start by counting the number of A bytes we used in printing our W string to the screen. - try - { - mbBufferRead = GetALengthFromW(codepage, { wstr.data(), wcBufferWritten }); - } - CATCH_LOG(); - - // If we captured a byte off the string this time around up above, it means we didn't feed - // it into the WriteConsoleW above, and therefore its consumption isn't accounted for - // in the count we just made. Add +1 to compensate. - if (leadByteCaptured) - { - mbBufferRead++; - } - - // If we consumed an internally-stored lead byte this time around up above, it means that we - // fed a byte into WriteConsoleW that wasn't a part of this particular call's request. - // We need to -1 to compensate and tell the caller the right number of bytes consumed this request. - if (leadByteConsumed) - { - mbBufferRead--; - } - - read = mbBufferRead; - } - } - else - { // If there is a waiter, then we need to stow some additional information in the wait structure so // we can synthesize the correct byte count later when the wait routine is triggered. if (CP_UTF8 != codepage) { // For non-UTF8 codepages, save the lead byte captured/consumed data so we can +1 or -1 the final decoded count // in the WaitData::Notify method later. - writeDataWaiter->SetLeadByteAdjustmentStatus(leadByteCaptured, leadByteConsumed); + waiter->SetLeadByteAdjustmentStatus(leadByteCaptured, leadByteConsumed); } else { // For UTF8 codepages, just remember the consumption count from the UTF-8 parser. - writeDataWaiter->SetUtf8ConsumedCharacters(read); + waiter->SetUtf8ConsumedCharacters(read); } + + std::ignore = ConsoleWaitQueue::s_CreateWait(pWaitReplyMessage, waiter); + return CONSOLE_STATUS_WAIT; } - // Give back the waiter now that we're done with tinkering with it. - waiter.reset(writeDataWaiter.release()); + // Make the W version of the call + const auto hr = DoWriteConsole(screenInfo, wstr); + + // Calculate how many bytes of the original A buffer were consumed in the W version of the call to satisfy mbBufferRead. + // For UTF-8 conversions, we've already returned this information above. + if (CP_UTF8 != codepage) + { + size_t mbBufferRead{}; + + // Start by counting the number of A bytes we used in printing our W string to the screen. + try + { + mbBufferRead = GetALengthFromW(codepage, wstr); + } + CATCH_LOG(); + + // If we captured a byte off the string this time around up above, it means we didn't feed + // it into the WriteConsoleW above, and therefore its consumption isn't accounted for + // in the count we just made. Add +1 to compensate. + if (leadByteCaptured) + { + mbBufferRead++; + } + + // If we consumed an internally-stored lead byte this time around up above, it means that we + // fed a byte into WriteConsoleW that wasn't a part of this particular call's request. + // We need to -1 to compensate and tell the caller the right number of bytes consumed this request. + if (leadByteConsumed) + { + mbBufferRead--; + } + + read = mbBufferRead; + } return hr; } @@ -703,20 +631,24 @@ NT_CATCH_RETURN() [[nodiscard]] HRESULT ApiRoutines::WriteConsoleWImpl(IConsoleOutputObject& context, const std::wstring_view buffer, size_t& read, - std::unique_ptr& waiter) noexcept + CONSOLE_API_MSG* pWaitReplyMessage) noexcept { try { LockConsole(); auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - std::unique_ptr writeDataWaiter; - RETURN_IF_FAILED(WriteConsoleWImplHelper(context.GetActiveBuffer(), buffer, read, writeDataWaiter)); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))) + { + std::ignore = ConsoleWaitQueue::s_CreateWait(pWaitReplyMessage, new WriteData(context, std::wstring{ buffer }, gci.OutputCP)); + return CONSOLE_STATUS_WAIT; + } - // Transfer specific waiter pointer into the generic interface wrapper. - waiter.reset(writeDataWaiter.release()); - - return S_OK; + read = 0; + auto Status = DoWriteConsole(context, buffer); + read = buffer.size(); + return Status; } CATCH_RETURN(); } diff --git a/src/host/_stream.h b/src/host/_stream.h index 4651deb800..5953367d7a 100644 --- a/src/host/_stream.h +++ b/src/host/_stream.h @@ -25,7 +25,4 @@ void WriteClearScreen(SCREEN_INFORMATION& screenInfo); // NOTE: console lock must be held when calling this routine // String has been translated to unicode at this point. -[[nodiscard]] NTSTATUS DoWriteConsole(_In_reads_bytes_(pcbBuffer) const wchar_t* pwchBuffer, - _Inout_ size_t* const pcbBuffer, - SCREEN_INFORMATION& screenInfo, - std::unique_ptr& waiter); +[[nodiscard]] HRESULT DoWriteConsole(SCREEN_INFORMATION& screenInfo, std::wstring_view str); diff --git a/src/host/directio.cpp b/src/host/directio.cpp index c7e3c292da..cd28ef07b1 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -58,12 +58,10 @@ using Microsoft::Console::Interactivity::ServiceLocator; const bool IsUnicode, const bool IsPeek, const bool IsWaitAllowed, - std::unique_ptr& waiter) noexcept + CONSOLE_API_MSG* pWaitReplyMessage) noexcept { try { - waiter.reset(); - if (eventReadCount == 0) { return STATUS_SUCCESS; @@ -83,9 +81,7 @@ using Microsoft::Console::Interactivity::ServiceLocator; { // If we're told to wait until later, move all of our context // to the read data object and send it back up to the server. - waiter = std::make_unique(&inputBuffer, - &readHandleState, - eventReadCount); + std::ignore = ConsoleWaitQueue::s_CreateWait(pWaitReplyMessage, new DirectReadData(&inputBuffer, &readHandleState, eventReadCount)); } return Status; } diff --git a/src/host/server.h b/src/host/server.h index 0a0f905777..d62484b3eb 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -166,9 +166,9 @@ private: MidiAudio _midiAudio; }; -#define CONSOLE_STATUS_WAIT 0xC0030001 -#define CONSOLE_STATUS_READ_COMPLETE 0xC0030002 -#define CONSOLE_STATUS_WAIT_NO_BLOCK 0xC0030003 +#define CONSOLE_STATUS_WAIT ((HRESULT)0xC0030001) +#define CONSOLE_STATUS_READ_COMPLETE ((HRESULT)0xC0030002) +#define CONSOLE_STATUS_WAIT_NO_BLOCK ((HRESULT)0xC0030003) #include "../server/ObjectHandle.h" diff --git a/src/host/stream.cpp b/src/host/stream.cpp index 11a7f6a411..d97d629b0d 100644 --- a/src/host/stream.cpp +++ b/src/host/stream.cpp @@ -340,7 +340,7 @@ NT_CATCH_RETURN() INPUT_READ_HANDLE_DATA& readHandleState, const std::wstring_view exeName, const bool unicode, - std::unique_ptr& waiter) noexcept + CONSOLE_API_MSG* pWaitReplyMessage) noexcept { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); RETURN_HR_IF(E_FAIL, !gci.HasActiveOutputBuffer()); @@ -364,7 +364,8 @@ NT_CATCH_RETURN() if (!cookedReadData->Read(unicode, bytesRead, controlKeyState)) { // memory will be cleaned up by wait queue - waiter.reset(cookedReadData.release()); + std::ignore = ConsoleWaitQueue::s_CreateWait(pWaitReplyMessage, cookedReadData.release()); + return CONSOLE_STATUS_WAIT; } else { @@ -468,25 +469,23 @@ NT_CATCH_RETURN() // populated. // - STATUS_SUCCESS on success // - Other NSTATUS codes as necessary -[[nodiscard]] NTSTATUS DoReadConsole(InputBuffer& inputBuffer, - const HANDLE processData, - std::span buffer, - size_t& bytesRead, - ULONG& controlKeyState, - const std::wstring_view initialData, - const DWORD ctrlWakeupMask, - INPUT_READ_HANDLE_DATA& readHandleState, - const std::wstring_view exeName, - const bool unicode, - std::unique_ptr& waiter) noexcept +[[nodiscard]] HRESULT DoReadConsole(InputBuffer& inputBuffer, + const HANDLE processData, + std::span buffer, + size_t& bytesRead, + ULONG& controlKeyState, + const std::wstring_view initialData, + const DWORD ctrlWakeupMask, + INPUT_READ_HANDLE_DATA& readHandleState, + const std::wstring_view exeName, + const bool unicode, + CONSOLE_API_MSG* pWaitReplyMessage) noexcept { try { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - waiter.reset(); - bytesRead = 0; if (buffer.size() < 1) @@ -504,17 +503,17 @@ NT_CATCH_RETURN() } else if (WI_IsFlagSet(inputBuffer.InputMode, ENABLE_LINE_INPUT)) { - return NTSTATUS_FROM_HRESULT(_ReadLineInput(inputBuffer, - processData, - buffer, - bytesRead, - controlKeyState, - initialData, - ctrlWakeupMask, - readHandleState, - exeName, - unicode, - waiter)); + return _ReadLineInput(inputBuffer, + processData, + buffer, + bytesRead, + controlKeyState, + initialData, + ctrlWakeupMask, + readHandleState, + exeName, + unicode, + pWaitReplyMessage); } else { @@ -525,7 +524,7 @@ NT_CATCH_RETURN() unicode); if (status == CONSOLE_STATUS_WAIT) { - waiter = std::make_unique(&inputBuffer, &readHandleState, gsl::narrow(buffer.size()), reinterpret_cast(buffer.data())); + std::ignore = ConsoleWaitQueue::s_CreateWait(pWaitReplyMessage, new RAW_READ_DATA(&inputBuffer, &readHandleState, gsl::narrow(buffer.size()), reinterpret_cast(buffer.data()))); } return status; } @@ -536,7 +535,7 @@ NT_CATCH_RETURN() [[nodiscard]] HRESULT ApiRoutines::ReadConsoleImpl(IConsoleInputObject& context, std::span buffer, size_t& written, - std::unique_ptr& waiter, + CONSOLE_API_MSG* pWaitReplyMessage, const std::wstring_view initialData, const std::wstring_view exeName, INPUT_READ_HANDLE_DATA& readHandleState, @@ -545,17 +544,17 @@ NT_CATCH_RETURN() const DWORD controlWakeupMask, DWORD& controlKeyState) noexcept { - return HRESULT_FROM_NT(DoReadConsole(context, - clientHandle, - buffer, - written, - controlKeyState, - initialData, - controlWakeupMask, - readHandleState, - exeName, - IsUnicode, - waiter)); + return DoReadConsole(context, + clientHandle, + buffer, + written, + controlKeyState, + initialData, + controlWakeupMask, + readHandleState, + exeName, + IsUnicode, + pWaitReplyMessage); } void UnblockWriteConsole(const DWORD dwReason) diff --git a/src/host/ut_host/ApiRoutinesTests.cpp b/src/host/ut_host/ApiRoutinesTests.cpp index 37e6ec62fd..bb2f95878b 100644 --- a/src/host/ut_host/ApiRoutinesTests.cpp +++ b/src/host/ut_host/ApiRoutinesTests.cpp @@ -372,46 +372,24 @@ class ApiRoutinesTests for (size_t i = 0; i < cchTestText; i += cchIncrement) { Log::Comment(WEX::Common::String().Format(L"Iteration %d of loop with increment %d", i, cchIncrement)); - if (fInduceWait) - { - Log::Comment(L"Blocking global output state to induce waits."); - s_AdjustOutputWait(true); - } + s_AdjustOutputWait(fInduceWait); size_t cchRead = 0; - std::unique_ptr waiter; // The increment is either the specified length or the remaining text in the string (if that is smaller). const auto cchWriteLength = std::min(cchIncrement, cchTestText - i); // Run the test method - const auto hr = _pApiRoutines->WriteConsoleAImpl(si, { pszTestText + i, cchWriteLength }, cchRead, waiter); + const auto hr = _pApiRoutines->WriteConsoleAImpl(si, { pszTestText + i, cchWriteLength }, cchRead, nullptr); - VERIFY_ARE_EQUAL(S_OK, hr, L"Successful result code from writing."); if (!fInduceWait) { - VERIFY_IS_NULL(waiter.get(), L"We should have no waiter for this case."); + VERIFY_ARE_EQUAL(S_OK, hr); VERIFY_ARE_EQUAL(cchWriteLength, cchRead, L"We should have the same character count back as 'written' that we gave in."); } else { - VERIFY_IS_NOT_NULL(waiter.get(), L"We should have a waiter for this case."); - // The cchRead is irrelevant at this point as it's not going to be returned until we're off the wait. - - Log::Comment(L"Unblocking global output state so the wait can be serviced."); - s_AdjustOutputWait(false); - Log::Comment(L"Dispatching the wait."); - auto Status = STATUS_SUCCESS; - size_t dwNumBytes = 0; - DWORD dwControlKeyState = 0; // unused but matches the pattern for read. - void* pOutputData = nullptr; // unused for writes but used for read. - const BOOL bNotifyResult = waiter->Notify(WaitTerminationReason::NoReason, FALSE, &Status, &dwNumBytes, &dwControlKeyState, &pOutputData); - - VERIFY_IS_TRUE(!!bNotifyResult, L"Wait completion on notify should be successful."); - VERIFY_ARE_EQUAL(STATUS_SUCCESS, Status, L"We should have a successful return code to pass to the caller."); - - const auto dwBytesExpected = cchWriteLength; - VERIFY_ARE_EQUAL(dwBytesExpected, dwNumBytes, L"We should have the byte length of the string we put in as the returned value."); + VERIFY_ARE_EQUAL(CONSOLE_STATUS_WAIT, hr); } } } @@ -431,43 +409,21 @@ class ApiRoutinesTests gci.LockConsole(); auto Unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - const std::wstring testText(L"Test text"); + const std::wstring_view testText(L"Test text"); - if (fInduceWait) - { - Log::Comment(L"Blocking global output state to induce waits."); - s_AdjustOutputWait(true); - } + s_AdjustOutputWait(fInduceWait); size_t cchRead = 0; - std::unique_ptr waiter; - const auto hr = _pApiRoutines->WriteConsoleWImpl(si, testText, cchRead, waiter); + const auto hr = _pApiRoutines->WriteConsoleWImpl(si, testText, cchRead, nullptr); - VERIFY_ARE_EQUAL(S_OK, hr, L"Successful result code from writing."); if (!fInduceWait) { - VERIFY_IS_NULL(waiter.get(), L"We should have no waiter for this case."); + VERIFY_ARE_EQUAL(S_OK, hr); VERIFY_ARE_EQUAL(testText.size(), cchRead, L"We should have the same character count back as 'written' that we gave in."); } else { - VERIFY_IS_NOT_NULL(waiter.get(), L"We should have a waiter for this case."); - // The cchRead is irrelevant at this point as it's not going to be returned until we're off the wait. - - Log::Comment(L"Unblocking global output state so the wait can be serviced."); - s_AdjustOutputWait(false); - Log::Comment(L"Dispatching the wait."); - auto Status = STATUS_SUCCESS; - size_t dwNumBytes = 0; - DWORD dwControlKeyState = 0; // unused but matches the pattern for read. - void* pOutputData = nullptr; // unused for writes but used for read. - const BOOL bNotifyResult = waiter->Notify(WaitTerminationReason::NoReason, TRUE, &Status, &dwNumBytes, &dwControlKeyState, &pOutputData); - - VERIFY_IS_TRUE(!!bNotifyResult, L"Wait completion on notify should be successful."); - VERIFY_ARE_EQUAL(STATUS_SUCCESS, Status, L"We should have a successful return code to pass to the caller."); - - const auto dwBytesExpected = testText.size() * sizeof(wchar_t); - VERIFY_ARE_EQUAL(dwBytesExpected, dwNumBytes, L"We should have the byte length of the string we put in as the returned value."); + VERIFY_ARE_EQUAL(CONSOLE_STATUS_WAIT, hr); } } diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 31ec3a747f..77b0f303cf 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -2549,11 +2549,7 @@ void ScreenBufferTests::TestAltBufferVtDispatching() // We're going to write some data to either the main buffer or the alt // buffer, as if we were using the API. - std::unique_ptr waiter; - std::wstring seq = L"\x1b[5;6H"; - auto seqCb = 2 * seq.size(); - VERIFY_SUCCEEDED(DoWriteConsole(&seq[0], &seqCb, mainBuffer, waiter)); - + VERIFY_SUCCEEDED(DoWriteConsole(mainBuffer, L"\x1b[5;6H")); VERIFY_ARE_EQUAL(til::point(0, 0), mainCursor.GetPosition()); // recall: vt coordinates are (row, column), 1-indexed VERIFY_ARE_EQUAL(til::point(5, 4), altCursor.GetPosition()); @@ -2565,17 +2561,11 @@ void ScreenBufferTests::TestAltBufferVtDispatching() VERIFY_ARE_EQUAL(expectedDefaults, mainBuffer.GetAttributes()); VERIFY_ARE_EQUAL(expectedDefaults, alternate.GetAttributes()); - seq = L"\x1b[48;2;255;0;255m"; - seqCb = 2 * seq.size(); - VERIFY_SUCCEEDED(DoWriteConsole(&seq[0], &seqCb, mainBuffer, waiter)); - + VERIFY_SUCCEEDED(DoWriteConsole(mainBuffer, L"\x1b[48;2;255;0;255m")); VERIFY_ARE_EQUAL(expectedDefaults, mainBuffer.GetAttributes()); VERIFY_ARE_EQUAL(expectedRgb, alternate.GetAttributes()); - seq = L"X"; - seqCb = 2 * seq.size(); - VERIFY_SUCCEEDED(DoWriteConsole(&seq[0], &seqCb, mainBuffer, waiter)); - + VERIFY_SUCCEEDED(DoWriteConsole(mainBuffer, L"X")); VERIFY_ARE_EQUAL(til::point(0, 0), mainCursor.GetPosition()); VERIFY_ARE_EQUAL(til::point(6, 4), altCursor.GetPosition()); diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 4252609e30..1ee380d70e 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -1390,22 +1390,18 @@ void TextBufferTests::TestBackspaceStringsAPI() // backspacing it with "\b \b". // Regardless of how we write those sequences of characters, the end result // should be the same. - std::unique_ptr waiter; Log::Comment(NoThrowString().Format( L"Using WriteCharsLegacy, write \\b \\b as a single string.")); - size_t aCb = 2; - size_t seqCb = 6; - VERIFY_SUCCEEDED(DoWriteConsole(L"a", &aCb, si, waiter)); - VERIFY_SUCCEEDED(DoWriteConsole(L"\b \b", &seqCb, si, waiter)); + VERIFY_SUCCEEDED(DoWriteConsole(si, L"a")); + VERIFY_SUCCEEDED(DoWriteConsole(si, L"\b \b")); VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0); VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0); - seqCb = 2; - VERIFY_SUCCEEDED(DoWriteConsole(L"a", &seqCb, si, waiter)); - VERIFY_SUCCEEDED(DoWriteConsole(L"\b", &seqCb, si, waiter)); - VERIFY_SUCCEEDED(DoWriteConsole(L" ", &seqCb, si, waiter)); - VERIFY_SUCCEEDED(DoWriteConsole(L"\b", &seqCb, si, waiter)); + VERIFY_SUCCEEDED(DoWriteConsole(si, L"a")); + VERIFY_SUCCEEDED(DoWriteConsole(si, L"\b")); + VERIFY_SUCCEEDED(DoWriteConsole(si, L" ")); + VERIFY_SUCCEEDED(DoWriteConsole(si, L"\b")); VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0); VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0); } diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index e557ed517d..6de23ae0fa 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -249,23 +249,22 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests resetContents(); size_t written; - std::unique_ptr waiter; std::string_view expected; std::string_view actual; - THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"", written, waiter)); + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"", written, nullptr)); expected = ""; actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); // Force-wrap because we write up to the last column. - THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"aaaaaaaa", written, waiter)); + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"aaaaaaaa", written, nullptr)); expected = "aaaaaaaa\r\n"; actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); // Force-wrap because we write up to the last column, but this time with a tab. - THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"a\t\r\nb", written, waiter)); + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"a\t\r\nb", written, nullptr)); expected = "a\t\r\n\r\nb"; actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); diff --git a/src/host/writeData.cpp b/src/host/writeData.cpp index dcf49c54c0..6250155461 100644 --- a/src/host/writeData.cpp +++ b/src/host/writeData.cpp @@ -6,9 +6,10 @@ #include "_stream.h" #include "../types/inc/convert.hpp" - #include "../interactivity/inc/ServiceLocator.hpp" +using Microsoft::Console::Interactivity::ServiceLocator; + // Routine Description: // - Creates a new write data object for used in servicing write console requests // Arguments: @@ -22,31 +23,22 @@ // Return Value: // - THROW: Throws if space cannot be allocated to copy the given string WriteData::WriteData(SCREEN_INFORMATION& siContext, - _In_reads_bytes_(cbContext) PCWCHAR pwchContext, - const size_t cbContext, + std::wstring pwchContext, const UINT uiOutputCodepage) : IWaitRoutine(ReplyDataType::Write), _siContext(siContext), - _pwchContext(THROW_IF_NULL_ALLOC(reinterpret_cast(new byte[cbContext]))), - _cbContext(cbContext), + _pwchContext(std::move(pwchContext)), _uiOutputCodepage(uiOutputCodepage), _fLeadByteCaptured(false), _fLeadByteConsumed(false), _cchUtf8Consumed(0) { - memmove(_pwchContext, pwchContext, _cbContext); } // Routine Description: // - Destroys the write data object // - Frees the string copy we made on creation -WriteData::~WriteData() -{ - if (nullptr != _pwchContext) - { - delete[] _pwchContext; - } -} +WriteData::~WriteData() = default; // Routine Description: // - Stores some additional information about lead byte adjustments from the conversion @@ -102,7 +94,7 @@ bool WriteData::Notify(const WaitTerminationReason TerminationReason, _Out_ DWORD* const pControlKeyState, _Out_ void* const /*pOutputData*/) { - *pNumBytes = _cbContext; + *pNumBytes = 0; *pControlKeyState = 0; if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::ThreadDying)) @@ -111,6 +103,12 @@ bool WriteData::Notify(const WaitTerminationReason TerminationReason, return true; } + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))) + { + return false; + } + // if we get to here, this routine was called by the input // thread, which grabs the current console lock. @@ -119,20 +117,16 @@ bool WriteData::Notify(const WaitTerminationReason TerminationReason, FAIL_FAST_IF(!(Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked())); - std::unique_ptr waiter; - auto cbContext = _cbContext; - auto Status = DoWriteConsole(_pwchContext, - &cbContext, - _siContext, - waiter); + auto Status = DoWriteConsole(_siContext, _pwchContext); if (Status == CONSOLE_STATUS_WAIT) { // an extra waiter will be created by DoWriteConsole, but we're already a waiter so discard it. - waiter.reset(); return false; } + auto cbContext = _pwchContext.size(); + // There's extra work to do to correct the byte counts if the original call was an A-version call. // We always process and hold text in the waiter as W-version text, but the A call is expecting // a byte value in its own codepage of how much we have written in that codepage. @@ -140,10 +134,6 @@ bool WriteData::Notify(const WaitTerminationReason TerminationReason, { if (CP_UTF8 != _uiOutputCodepage) { - // At this level with WriteConsole, everything is byte counts, so change back to char counts for - // GetALengthFromW to work correctly. - const auto cchContext = cbContext / sizeof(wchar_t); - // For non-UTF-8 codepages, we need to back convert the amount consumed and then // correlate that with any lead bytes we may have kept for later or reintroduced // from previous calls. @@ -152,7 +142,7 @@ bool WriteData::Notify(const WaitTerminationReason TerminationReason, // Start by counting the number of A bytes we used in printing our W string to the screen. try { - cchTextBufferRead = GetALengthFromW(_uiOutputCodepage, { _pwchContext, cchContext }); + cchTextBufferRead = GetALengthFromW(_uiOutputCodepage, _pwchContext); } CATCH_LOG(); diff --git a/src/host/writeData.hpp b/src/host/writeData.hpp index 4609b98d37..30e2a7953a 100644 --- a/src/host/writeData.hpp +++ b/src/host/writeData.hpp @@ -25,8 +25,7 @@ class WriteData : public IWaitRoutine { public: WriteData(SCREEN_INFORMATION& siContext, - _In_reads_bytes_(cbContext) PCWCHAR pwchContext, - const size_t cbContext, + std::wstring pwchContext, const UINT uiOutputCodepage); ~WriteData(); @@ -45,8 +44,7 @@ public: private: SCREEN_INFORMATION& _siContext; - wchar_t* const _pwchContext; - const size_t _cbContext; + std::wstring _pwchContext; UINT const _uiOutputCodepage; bool _fLeadByteCaptured; bool _fLeadByteConsumed; diff --git a/src/server/ApiDispatchers.cpp b/src/server/ApiDispatchers.cpp index 6c99344e80..be9a2fae85 100644 --- a/src/server/ApiDispatchers.cpp +++ b/src/server/ApiDispatchers.cpp @@ -173,7 +173,6 @@ constexpr T saturate(auto val) const auto pInputReadHandleData = pHandleData->GetClientInput(); - std::unique_ptr waiter; InputEventQueue outEvents; auto hr = m->_pApiRoutines->GetConsoleInputImpl( *pInputBuffer, @@ -183,7 +182,7 @@ constexpr T saturate(auto val) a->Unicode, fIsPeek, fIsWaitAllowed, - waiter); + m); // We must return the number of records in the message payload (to alert the client) // as well as in the message headers (below in SetReplyInformation) to alert the driver. @@ -192,14 +191,10 @@ constexpr T saturate(auto val) size_t cbWritten; LOG_IF_FAILED(SizeTMult(outEvents.size(), sizeof(INPUT_RECORD), &cbWritten)); - if (waiter) + if (hr == CONSOLE_STATUS_WAIT) { - hr = ConsoleWaitQueue::s_CreateWait(m, waiter.release()); - if (SUCCEEDED(hr)) - { - *pbReplyPending = TRUE; - hr = CONSOLE_STATUS_WAIT; - } + hr = S_OK; + *pbReplyPending = TRUE; } else { @@ -290,14 +285,13 @@ constexpr T saturate(auto val) // across multiple calls when we are simulating a command prompt input line for the client application. const auto pInputReadHandleData = HandleData->GetClientInput(); - std::unique_ptr waiter; size_t cbWritten; const std::span outputBuffer(reinterpret_cast(pvBuffer), cbBufferSize); auto hr = m->_pApiRoutines->ReadConsoleImpl(*pInputBuffer, outputBuffer, cbWritten, // We must set the reply length in bytes. - waiter, + m, initialData, exeView, *pInputReadHandleData, @@ -308,15 +302,10 @@ constexpr T saturate(auto val) LOG_IF_FAILED(SizeTToULong(cbWritten, &a->NumBytes)); - if (nullptr != waiter.get()) + if (hr == CONSOLE_STATUS_WAIT) { - // If we received a waiter, we need to queue the wait and not reply. - hr = ConsoleWaitQueue::s_CreateWait(m, waiter.release()); - - if (SUCCEEDED(hr)) - { - *pbReplyPending = TRUE; - } + hr = S_OK; + *pbReplyPending = TRUE; } else { @@ -355,7 +344,6 @@ constexpr T saturate(auto val) ULONG cbBufferSize; RETURN_IF_FAILED(m->GetInputBuffer(&pvBuffer, &cbBufferSize)); - std::unique_ptr waiter; size_t cbRead; // We have to hold onto the HR from the call and return it. @@ -373,7 +361,7 @@ constexpr T saturate(auto val) TraceLoggingUInt32(a->NumBytes, "NumBytes"), TraceLoggingCountedWideString(buffer.data(), static_cast(buffer.size()), "Buffer")); - hr = m->_pApiRoutines->WriteConsoleWImpl(*pScreenInfo, buffer, cchInputRead, waiter); + hr = m->_pApiRoutines->WriteConsoleWImpl(*pScreenInfo, buffer, cchInputRead, m); // We must set the reply length in bytes. Convert back from characters. LOG_IF_FAILED(SizeTMult(cchInputRead, sizeof(wchar_t), &cbRead)); @@ -388,7 +376,7 @@ constexpr T saturate(auto val) TraceLoggingUInt32(a->NumBytes, "NumBytes"), TraceLoggingCountedString(buffer.data(), static_cast(buffer.size()), "Buffer")); - hr = m->_pApiRoutines->WriteConsoleAImpl(*pScreenInfo, buffer, cchInputRead, waiter); + hr = m->_pApiRoutines->WriteConsoleAImpl(*pScreenInfo, buffer, cchInputRead, m); // Reply length is already in bytes (chars), don't need to convert. cbRead = cchInputRead; @@ -397,14 +385,10 @@ constexpr T saturate(auto val) // We must return the byte length of the read data in the message. LOG_IF_FAILED(SizeTToULong(cbRead, &a->NumBytes)); - if (nullptr != waiter.get()) + if (hr == CONSOLE_STATUS_WAIT) { - // If we received a waiter, we need to queue the wait and not reply. - hr = ConsoleWaitQueue::s_CreateWait(m, waiter.release()); - if (SUCCEEDED(hr)) - { - *pbReplyPending = TRUE; - } + hr = S_OK; + *pbReplyPending = TRUE; } else { diff --git a/src/server/IApiRoutines.h b/src/server/IApiRoutines.h index 568b150968..eb53f347a3 100644 --- a/src/server/IApiRoutines.h +++ b/src/server/IApiRoutines.h @@ -27,6 +27,8 @@ typedef InputBuffer IConsoleInputObject; class INPUT_READ_HANDLE_DATA; +typedef struct _CONSOLE_API_MSG CONSOLE_API_MSG; + #include "IWaitRoutine.h" #include "../types/inc/IInputEvent.hpp" #include "../types/inc/viewport.hpp" @@ -64,12 +66,12 @@ public: const bool IsUnicode, const bool IsPeek, const bool IsWaitAllowed, - std::unique_ptr& waiter) noexcept = 0; + CONSOLE_API_MSG* pWaitReplyMessage) noexcept = 0; [[nodiscard]] virtual HRESULT ReadConsoleImpl(IConsoleInputObject& context, std::span buffer, size_t& written, - std::unique_ptr& waiter, + CONSOLE_API_MSG* pWaitReplyMessage, const std::wstring_view initialData, const std::wstring_view exeName, INPUT_READ_HANDLE_DATA& readHandleState, @@ -81,12 +83,12 @@ public: [[nodiscard]] virtual HRESULT WriteConsoleAImpl(IConsoleOutputObject& context, const std::string_view buffer, size_t& read, - std::unique_ptr& waiter) noexcept = 0; + CONSOLE_API_MSG* pWaitReplyMessage) noexcept = 0; [[nodiscard]] virtual HRESULT WriteConsoleWImpl(IConsoleOutputObject& context, const std::wstring_view buffer, size_t& read, - std::unique_ptr& waiter) noexcept = 0; + CONSOLE_API_MSG* pWaitReplyMessage) noexcept = 0; #pragma region Thread Creation Info [[nodiscard]] virtual HRESULT GetConsoleLangIdImpl(LANGID& langId) noexcept = 0; diff --git a/src/server/WaitBlock.cpp b/src/server/WaitBlock.cpp index b26dffcb58..a871a52fc6 100644 --- a/src/server/WaitBlock.cpp +++ b/src/server/WaitBlock.cpp @@ -86,6 +86,15 @@ ConsoleWaitBlock::~ConsoleWaitBlock() [[nodiscard]] HRESULT ConsoleWaitBlock::s_CreateWait(_Inout_ CONSOLE_API_MSG* const pWaitReplyMessage, _In_ IWaitRoutine* const pWaiter) { + if (!pWaitReplyMessage || !pWaiter) + { + if (pWaiter) + { + delete pWaiter; + } + return E_INVALIDARG; + } + const auto ProcessData = pWaitReplyMessage->GetProcessHandle(); FAIL_FAST_IF_NULL(ProcessData); diff --git a/src/server/WaitQueue.h b/src/server/WaitQueue.h index 929f364446..be8a871ade 100644 --- a/src/server/WaitQueue.h +++ b/src/server/WaitQueue.h @@ -40,7 +40,6 @@ public: [[nodiscard]] static HRESULT s_CreateWait(_Inout_ CONSOLE_API_MSG* const pWaitReplyMessage, _In_ IWaitRoutine* const pWaiter); -private: bool _NotifyBlock(_In_ ConsoleWaitBlock* pWaitBlock, const WaitTerminationReason TerminationReason); From 093f5d168c91dbe461041b47288c53ab4dcbdaf4 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 23 Apr 2025 13:31:23 -0700 Subject: [PATCH 027/177] Add descriptions and new labels to the bot rules (#18342) Wanted to learn how the bot works, so I went ahead cleaned up the bot rules a bit. List of changes: - added a description for each rule (and move it to the top of the rule) - added all the "Area-" labels and sorted --- .github/policies/resourceManagement.yml | 387 ++++++++++++++---------- 1 file changed, 231 insertions(+), 156 deletions(-) diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml index 695df7162d..7bdd1b5d03 100644 --- a/.github/policies/resourceManagement.yml +++ b/.github/policies/resourceManagement.yml @@ -8,7 +8,7 @@ where: configuration: resourceManagementConfiguration: scheduledSearches: - - description: + - description: '"Needs-Author-Feedback" and "No-Recent-Activity" issues are closed after 3 days of inactivity' frequencies: - hourly: hour: 3 @@ -23,7 +23,7 @@ configuration: days: 3 actions: - closeIssue - - description: + - description: '"Needs-Author-Feedback" issues are labelled "No-Recent-Activity" after 4 days of inactivity' frequencies: - hourly: hour: 3 @@ -41,7 +41,7 @@ configuration: label: No-Recent-Activity - addReply: reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**. - - description: + - description: '"Resolution-Duplicate" issues are closed after 1 day of inactivity' frequencies: - hourly: hour: 3 @@ -56,7 +56,7 @@ configuration: - addReply: reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes. - closeIssue - - description: + - description: '"Needs-Author-Feedback" and "No-Recent-Activity" PRs are closed after 7 days of inactivity' frequencies: - hourly: hour: 3 @@ -71,7 +71,7 @@ configuration: days: 7 actions: - closeIssue - - description: + - description: Add "No-Recent-Activity" label to PRs with "Needs-Author-Feedback" label after 7 days of inactivity frequencies: - hourly: hour: 3 @@ -90,7 +90,8 @@ configuration: - addReply: reply: This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. eventResponderTasks: - - if: + - description: Add "Needs-Triage" to new issues + if: - payloadType: Issues - or: - and: @@ -102,8 +103,8 @@ configuration: then: - addLabel: label: Needs-Triage - description: - - if: + - description: Replace "Needs-Author-Feedback" with "Needs-Attention" when author comments + if: - payloadType: Issue_Comment - isAction: action: Created @@ -116,8 +117,8 @@ configuration: label: Needs-Attention - removeLabel: label: Needs-Author-Feedback - description: - - if: + - description: Remove "No-Recent-Activity" when closing an issue + if: - payloadType: Issues - not: isAction: @@ -127,16 +128,16 @@ configuration: then: - removeLabel: label: No-Recent-Activity - description: - - if: + - description: Remove "No-Recent-Activity" when someone comments on an issue + if: - payloadType: Issue_Comment - hasLabel: label: No-Recent-Activity then: - removeLabel: label: No-Recent-Activity - description: - - if: + - description: Add "Needs-Author-Feedback" when changes are requested on a PR + if: - payloadType: Pull_Request_Review - isAction: action: Submitted @@ -145,8 +146,8 @@ configuration: then: - addLabel: label: Needs-Author-Feedback - description: - - if: + - description: Remove "Needs-Author-Feedback" when author performs activity on their PR + if: - payloadType: Pull_Request - isActivitySender: issueAuthor: True @@ -158,8 +159,8 @@ configuration: then: - removeLabel: label: Needs-Author-Feedback - description: - - if: + - description: Remove "Needs-Author-Feedback" when author comments on their issue + if: - payloadType: Issue_Comment - isActivitySender: issueAuthor: True @@ -168,8 +169,8 @@ configuration: then: - removeLabel: label: Needs-Author-Feedback - description: - - if: + - description: Remove "Needs-Author-Feedback" when the author reviews the PR + if: - payloadType: Pull_Request_Review - isActivitySender: issueAuthor: True @@ -178,8 +179,8 @@ configuration: then: - removeLabel: label: Needs-Author-Feedback - description: - - if: + - description: Remove "No-Recent-Activity"" when activity occurs on the PR (aside from closing it) + if: - payloadType: Pull_Request - not: isAction: @@ -189,39 +190,39 @@ configuration: then: - removeLabel: label: No-Recent-Activity - description: - - if: + - description: Remove "No-Recent-Activity" when someone comments on the PR + if: - payloadType: Issue_Comment - hasLabel: label: No-Recent-Activity then: - removeLabel: label: No-Recent-Activity - description: - - if: + - description: Remove "No-Recent-Activity" when someone reviews the PR + if: - payloadType: Pull_Request_Review - hasLabel: label: No-Recent-Activity then: - removeLabel: label: No-Recent-Activity - description: - - if: + - description: Enable auto-merge on PRs with the "AutoMerge" label + if: - payloadType: Pull_Request - hasLabel: label: AutoMerge then: - enableAutoMerge: mergeMethod: Squash - description: - - if: + - description: Disable auto-merge on PRs when the "AutoMerge" label is removed + if: - payloadType: Pull_Request - labelRemoved: label: AutoMerge then: - disableAutoMerge - description: - - if: + - description: Add "Needs-Tag-Fix" label to issues without an Area, Issue, or Product label + if: - payloadType: Issues - or: - and: @@ -238,15 +239,45 @@ configuration: - not: hasLabel: label: Area-Accessibility + - not: + hasLabel: + label: Area-AtlasEngine + - not: + hasLabel: + label: Area-AzureShell - not: hasLabel: label: Area-Build + - not: + hasLabel: + label: Area-Chat + - not: + hasLabel: + label: Area-CmdPal + - not: + hasLabel: + label: Area-CodeHealth + - not: + hasLabel: + label: Area-Commandline + - not: + hasLabel: + label: Area-CookedRead + - not: + hasLabel: + label: Area-DefApp - not: hasLabel: label: Area-Extensibility - not: hasLabel: label: Area-Fonts + - not: + hasLabel: + label: Area-GroupPolicy + - not: + hasLabel: + label: Area-i18n - not: hasLabel: label: Area-Input @@ -256,21 +287,45 @@ configuration: - not: hasLabel: label: Area-Interop + - not: + hasLabel: + label: Area-Localization - not: hasLabel: label: Area-Output - not: hasLabel: label: Area-Performance + - not: + hasLabel: + label: Area-Portable + - not: + hasLabel: + label: Area-Quality + - not: + hasLabel: + label: Area-Remoting - not: hasLabel: label: Area-Rendering + - not: + hasLabel: + label: Area-Schema - not: hasLabel: label: Area-Server - not: hasLabel: label: Area-Settings + - not: + hasLabel: + label: Area-SettingsUI + - not: + hasLabel: + label: Area-ShellExtension + - not: + hasLabel: + label: Area-Suggestions - not: hasLabel: label: Area-TerminalConnection @@ -279,49 +334,19 @@ configuration: label: Area-TerminalControl - not: hasLabel: - label: Area-User Interface + label: Area-Theming + - not: + hasLabel: + label: Area-UserInterface - not: hasLabel: label: Area-VT - - not: - hasLabel: - label: Area-CodeHealth - - not: - hasLabel: - label: Area-Quality - - not: - hasLabel: - label: Area-AzureShell - - not: - hasLabel: - label: Area-Schema - - not: - hasLabel: - label: Area-Commandline - - not: - hasLabel: - label: Area-ShellExtension - - not: - hasLabel: - label: Area-WPFControl - - not: - hasLabel: - label: Area-Settings UI - - not: - hasLabel: - label: Area-DefApp - - not: - hasLabel: - label: Area-Remoting - not: hasLabel: label: Area-Windowing - not: hasLabel: - label: Area-Theming - - not: - hasLabel: - label: Area-Localization + label: Area-WPFControl - and: - not: hasLabel: @@ -408,8 +433,8 @@ configuration: then: - addLabel: label: Needs-Tag-Fix - description: - - if: + - description: Remove "Needs-Tag-Fix" label when an issue is tagged with an Area, Issue, and Product label + if: - payloadType: Issues - and: - isLabeled @@ -417,66 +442,117 @@ configuration: label: Needs-Tag-Fix - and: - or: - - hasLabel: - label: Area-Accessibility - - hasLabel: - label: Area-Build - - hasLabel: - label: Area-Extensibility - - hasLabel: - label: Area-Fonts - - hasLabel: - label: Area-Input - - hasLabel: - label: Area-Interaction - - hasLabel: - label: Area-Interop - - hasLabel: - label: Area-Output - - hasLabel: - label: Area-Performance - - hasLabel: - label: Area-Rendering - - hasLabel: - label: Area-Server - - hasLabel: - label: Area-Settings - - hasLabel: - label: Area-TerminalConnection - - hasLabel: - label: Area-TerminalControl - - hasLabel: - label: Area-User Interface - - hasLabel: - label: Area-VT - - hasLabel: - label: Area-CodeHealth - - hasLabel: - label: Area-Quality - - hasLabel: - label: Area-Schema - - hasLabel: - label: Area-AzureShell - - hasLabel: - label: Area-Commandline - - hasLabel: - label: Area-ShellExtension - - hasLabel: - label: Area-WPFControl - - hasLabel: - label: Area-Settings UI - - hasLabel: - label: Area-DefApp - - hasLabel: - label: Area-Localization - - hasLabel: - label: Area-Windowing - - hasLabel: - label: Area-Theming - - hasLabel: - label: Area-AtlasEngine - - hasLabel: - label: Area-CmdPal + - not: + hasLabel: + label: Area-Accessibility + - not: + hasLabel: + label: Area-AtlasEngine + - not: + hasLabel: + label: Area-AzureShell + - not: + hasLabel: + label: Area-Build + - not: + hasLabel: + label: Area-Chat + - not: + hasLabel: + label: Area-CmdPal + - not: + hasLabel: + label: Area-CodeHealth + - not: + hasLabel: + label: Area-Commandline + - not: + hasLabel: + label: Area-CookedRead + - not: + hasLabel: + label: Area-DefApp + - not: + hasLabel: + label: Area-Extensibility + - not: + hasLabel: + label: Area-Fonts + - not: + hasLabel: + label: Area-GroupPolicy + - not: + hasLabel: + label: Area-i18n + - not: + hasLabel: + label: Area-Input + - not: + hasLabel: + label: Area-Interaction + - not: + hasLabel: + label: Area-Interop + - not: + hasLabel: + label: Area-Localization + - not: + hasLabel: + label: Area-Output + - not: + hasLabel: + label: Area-Performance + - not: + hasLabel: + label: Area-Portable + - not: + hasLabel: + label: Area-Quality + - not: + hasLabel: + label: Area-Remoting + - not: + hasLabel: + label: Area-Rendering + - not: + hasLabel: + label: Area-Schema + - not: + hasLabel: + label: Area-Server + - not: + hasLabel: + label: Area-Settings + - not: + hasLabel: + label: Area-SettingsUI + - not: + hasLabel: + label: Area-ShellExtension + - not: + hasLabel: + label: Area-Suggestions + - not: + hasLabel: + label: Area-TerminalConnection + - not: + hasLabel: + label: Area-TerminalControl + - not: + hasLabel: + label: Area-Theming + - not: + hasLabel: + label: Area-UserInterface + - not: + hasLabel: + label: Area-VT + - not: + hasLabel: + label: Area-Windowing + - not: + hasLabel: + label: Area-WPFControl - or: - hasLabel: label: Issue-Bug @@ -533,14 +609,14 @@ configuration: then: - removeLabel: label: Needs-Tag-Fix - description: - - if: + - description: Add "In-PR" label to issues that are referenced in a PR + if: - payloadType: Pull_Request then: - inPrLabel: label: In-PR - description: - - if: + - description: Remove "Needs-Tag-Fix" label when an issue also has the "Resolution-Duplicate" label + if: - payloadType: Issues - hasLabel: label: Needs-Tag-Fix @@ -549,8 +625,8 @@ configuration: then: - removeLabel: label: Needs-Tag-Fix - description: - - if: + - description: Close issues that are opened and have the template title + if: - payloadType: Issues - or: - titleContains: @@ -576,8 +652,8 @@ configuration: label: Needs-Author-Feedback - addReply: reply: Hi! Thanks for attempting to open an issue. Unfortunately, your title wasn't changed from the original template which makes it very hard for us to track and triage. You are welcome to fix up the title and try again with a new issue. - description: - - if: + - description: Close issues that are opened and have no body + if: - payloadType: Issues - or: - isAction: @@ -595,8 +671,8 @@ configuration: label: Needs-Author-Feedback - addReply: reply: "Hi! Thanks for attempting to open an issue. Unfortunately, you didn't write anything in the body which makes it impossible to understand your concern. You are welcome to fix up the issue and try again by opening another issue with the body filled out. " - description: - - if: + - description: Request a review from the team when a PR is labeled "Needs-Second" + if: - payloadType: Pull_Request - isLabeled - hasLabel: @@ -613,8 +689,8 @@ configuration: reviewer: dhowett - requestReview: reviewer: lhecker - description: - - if: + - description: Remove "Needs-Second" label when a PR is reviewed + if: - payloadType: Pull_Request_Review - not: isOpen - hasLabel: @@ -622,8 +698,8 @@ configuration: then: - removeLabel: label: Needs-Second - description: - - if: + - description: Remove "Help-Wanted" label from issues that are in a PR + if: - payloadType: Issues - hasLabel: label: In-PR @@ -633,8 +709,8 @@ configuration: then: - removeLabel: label: Help-Wanted - description: - - if: + - description: Comments with "/dup", "/dupe", or "/duplicate" will close the issue as a duplicate and remove "Needs-" labels + if: - payloadType: Issue_Comment - commentContains: pattern: '\/dup(licate|e)?(\s+of)?\s+\#[\d]+' @@ -662,8 +738,8 @@ configuration: label: Needs-Repro - removeLabel: label: Needs-Second - description: - - if: + - description: Comments with "/feedback" will direct people to Feedback Hub + if: - payloadType: Issue_Comment - commentContains: pattern: '\/feedback' @@ -680,13 +756,13 @@ configuration: Hi there!

Can you please send us feedback with the [Feedback Hub](https://support.microsoft.com/en-us/windows/send-feedback-to-microsoft-with-the-feedback-hub-app-f59187f8-8739-22d6-ba93-f66612949332) with this issue? Make sure to click the "Start recording" button, then reproduce the issue before submitting the feedback. Once it's submitted, paste the link here so we can more easily find your crash information on the back end?

Thanks!

![image](https://user-images.githubusercontent.com/18356694/140811502-a068f78b-89d2-4587-925a-73e19652b830.png)

![image](https://user-images.githubusercontent.com/18356694/140811557-cdc22a0f-fa6a-4f6a-953e-73b51f5548a3.png)

![image](https://user-images.githubusercontent.com/18221333/62478649-6de55400-b760-11e9-806e-5aab7e085a9f.png) - addLabel: label: Needs-Author-Feedback - description: - - if: + - description: Comments clean the email reply + if: - payloadType: Issue_Comment then: - cleanEmailReply - description: - - if: + - description: Sync labels when a PR event occurs + if: - payloadType: Pull_Request then: - labelSync: @@ -701,8 +777,8 @@ configuration: pattern: Severity- - labelSync: pattern: Impact- - description: - - if: + - description: Comments with "/dup", "/dupe", or "/duplicate" targeting another repo will close the issue as a duplicate and remove "Needs-" labels + if: - payloadType: Issue_Comment - commentContains: pattern: '\/dup(licate|e)?(\s+of)?\s+https' @@ -730,8 +806,8 @@ configuration: label: Needs-Repro - removeLabel: label: Needs-Second - description: - - if: + - description: Comments with "/?" will replace the "Needs-Attention" label with "Needs-Author-Feedback" + if: - payloadType: Issue_Comment - commentContains: pattern: /? @@ -746,6 +822,5 @@ configuration: label: Needs-Attention - addLabel: label: Needs-Author-Feedback - description: onFailure: onSuccess: From a2d80682c96eb85d6537c3f9371ba64bcb3814c4 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 24 Apr 2025 19:22:30 +0200 Subject: [PATCH 028/177] Use RenderSettings for DECSET 2026 - Synchronized Output (#18833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `RenderSettings` already stores `DECSCNM` (reversed screen), so it only makes sense to also store DECSET 2026 there. ## Validation Steps Performed * Same as in #18826 ✅ --- src/cascadia/TerminalControl/ControlCore.cpp | 2 +- src/renderer/base/RenderSettings.cpp | 6 +-- src/renderer/base/renderer.cpp | 52 +++++++++++--------- src/renderer/base/renderer.hpp | 7 ++- src/renderer/inc/RenderSettings.hpp | 3 +- src/terminal/adapter/adaptDispatch.cpp | 14 +++--- 6 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 4bd41d58d0..a96adb7a1d 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -143,7 +143,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // the UIA Engine to the renderer. This prevents us from signaling changes to the cursor or buffer. { // Now create the renderer and initialize the render thread. - const auto& renderSettings = _terminal->GetRenderSettings(); + auto& renderSettings = _terminal->GetRenderSettings(); _renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get()); _renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); }); diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp index 371b591b32..c587c60128 100644 --- a/src/renderer/base/RenderSettings.cpp +++ b/src/renderer/base/RenderSettings.cpp @@ -46,9 +46,9 @@ void RenderSettings::RestoreDefaultSettings() noexcept { _colorTable = _defaultColorTable; _colorAliasIndices = _defaultColorAliasIndices; - // For now, DECSCNM is the only render mode we need to reset. The others are - // all user preferences that can't be changed programmatically. - _renderMode.reset(Mode::ScreenReversed); + // DECSCNM and Synchronized Output are the only render mode we need to reset. + // The others are all user preferences that can't be changed programmatically. + _renderMode.reset(Mode::ScreenReversed, Mode::SynchronizedOutput); } // Routine Description: diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 5824bcb2f2..c923ef8e3f 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -25,7 +25,7 @@ static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 }; // - pData - The interface to console data structures required for rendering // Return Value: // - An instance of a Renderer. -Renderer::Renderer(const RenderSettings& renderSettings, IRenderData* pData) : +Renderer::Renderer(RenderSettings& renderSettings, IRenderData* pData) : _renderSettings(renderSettings), _pData(pData) { @@ -187,31 +187,36 @@ void Renderer::NotifyPaintFrame() noexcept } // NOTE: You must be holding the console lock when calling this function. -void Renderer::SynchronizedOutputBegin() noexcept +void Renderer::SynchronizedOutputChanged() noexcept { - // Kick the render thread into calling `_synchronizeWithOutput()`. - _isSynchronizingOutput = true; -} + const auto so = _renderSettings.GetRenderMode(RenderSettings::Mode::SynchronizedOutput); + if (_isSynchronizingOutput == so) + { + return; + } -// NOTE: You must be holding the console lock when calling this function. -void Renderer::SynchronizedOutputEnd() noexcept -{ - // Unblock `_synchronizeWithOutput()` from the `WaitOnAddress` call. - _isSynchronizingOutput = false; - WakeByAddressSingle(&_isSynchronizingOutput); + // If `_isSynchronizingOutput` is true, it'll kick the + // render thread into calling `_synchronizeWithOutput()`... + _isSynchronizingOutput = so; - // It's crucial to give the render thread at least a chance to gain the lock. - // Otherwise, a VT application could continuously spam DECSET 2026 (Synchronized Output) and - // essentially drop our renderer to 10 FPS, because `_isSynchronizingOutput` is always true. - // - // Obviously calling LockConsole/UnlockConsole here is an awful, ugly hack, - // since there's no guarantee that this is the same lock as the one the VT parser uses. - // But the alternative is Denial-Of-Service of the render thread. - // - // Note that this causes raw throughput of DECSET 2026 to be comparatively low, but that's fine. - // Apps that use DECSET 2026 don't produce that sequence continuously, but rather at a fixed rate. - _pData->UnlockConsole(); - _pData->LockConsole(); + if (!_isSynchronizingOutput) + { + // ...otherwise, unblock `_synchronizeWithOutput()` from the `WaitOnAddress` call. + WakeByAddressSingle(&_isSynchronizingOutput); + + // It's crucial to give the render thread at least a chance to gain the lock. + // Otherwise, a VT application could continuously spam DECSET 2026 (Synchronized Output) and + // essentially drop our renderer to 10 FPS, because `_isSynchronizingOutput` is always true. + // + // Obviously calling LockConsole/UnlockConsole here is an awful, ugly hack, + // since there's no guarantee that this is the same lock as the one the VT parser uses. + // But the alternative is Denial-Of-Service of the render thread. + // + // Note that this causes raw throughput of DECSET 2026 to be comparatively low, but that's fine. + // Apps that use DECSET 2026 don't produce that sequence continuously, but rather at a fixed rate. + _pData->UnlockConsole(); + _pData->LockConsole(); + } } void Renderer::_synchronizeWithOutput() noexcept @@ -249,6 +254,7 @@ void Renderer::_synchronizeWithOutput() noexcept // If a timeout occurred, `_isSynchronizingOutput` may still be true. // Set it to false now to skip calling `_synchronizeWithOutput()` on the next frame. _isSynchronizingOutput = false; + _renderSettings.SetRenderMode(RenderSettings::Mode::SynchronizedOutput, false); } // Routine Description: diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index c8c3045760..31acc72860 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -28,15 +28,14 @@ namespace Microsoft::Console::Render class Renderer { public: - Renderer(const RenderSettings& renderSettings, IRenderData* pData); + Renderer(RenderSettings& renderSettings, IRenderData* pData); IRenderData* GetRenderData() const noexcept; [[nodiscard]] HRESULT PaintFrame(); void NotifyPaintFrame() noexcept; - void SynchronizedOutputBegin() noexcept; - void SynchronizedOutputEnd() noexcept; + void SynchronizedOutputChanged() noexcept; void TriggerSystemRedraw(const til::rect* const prcDirtyClient); void TriggerRedraw(const Microsoft::Console::Types::Viewport& region); void TriggerRedraw(const til::point* const pcoord); @@ -113,7 +112,7 @@ namespace Microsoft::Console::Render void _prepareNewComposition(); [[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine); - const RenderSettings& _renderSettings; + RenderSettings& _renderSettings; std::array _engines{}; IRenderData* _pData = nullptr; // Non-ownership pointer static constexpr size_t _firstSoftFontChar = 0xEF20; diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp index 84443889bd..8b0d64379f 100644 --- a/src/renderer/inc/RenderSettings.hpp +++ b/src/renderer/inc/RenderSettings.hpp @@ -25,7 +25,8 @@ namespace Microsoft::Console::Render AlwaysDistinguishableColors, IntenseIsBold, IntenseIsBright, - ScreenReversed + ScreenReversed, + SynchronizedOutput, }; RenderSettings() noexcept; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index c8f4c5eb85..12fe6854e0 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1915,16 +1915,10 @@ void AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con _api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, enable); break; case DispatchTypes::ModeParams::SO_SynchronizedOutput: + _renderSettings.SetRenderMode(RenderSettings::Mode::SynchronizedOutput, enable); if (_renderer) { - if (enable) - { - _renderer->SynchronizedOutputBegin(); - } - else - { - _renderer->SynchronizedOutputEnd(); - } + _renderer->SynchronizedOutputChanged(); } break; case DispatchTypes::ModeParams::GCM_GraphemeClusterMode: @@ -2065,6 +2059,9 @@ void AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) case DispatchTypes::ModeParams::XTERM_BracketedPasteMode: state = mapTemp(_api.GetSystemMode(ITerminalApi::Mode::BracketedPaste)); break; + case DispatchTypes::ModeParams::SO_SynchronizedOutput: + state = mapTemp(_renderSettings.GetRenderMode(RenderSettings::Mode::SynchronizedOutput)); + break; case DispatchTypes::ModeParams::GCM_GraphemeClusterMode: state = mapPerm(CodepointWidthDetector::Singleton().GetMode() == TextMeasurementMode::Graphemes); break; @@ -3050,6 +3047,7 @@ void AdaptDispatch::HardReset() if (_renderer) { _renderer->TriggerRedrawAll(true, true); + _renderer->SynchronizedOutputChanged(); } // Cursor to 1,1 - the Soft Reset guarantees this is absolute From 8e94983170db5251182f6d2e09390c786f7e7969 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 24 Apr 2025 13:25:21 -0500 Subject: [PATCH 029/177] ServicingPipeline: make a bunch of quality of life improvements (#18830) We used to cherry-pick every commit that had even one card in "To Cherry Pick", even if it was also referenced by a card in "Rejected" or even "To Consider". Now we will warn and skip those commits. I took this opportunity to add a bit of an object model for servicing cards as well as prettify the output. That allowed us to add a list of cards that were ignored due to having no commits, and display little icons for each type of card. --- .../ReleaseEngineering/ServicingPipeline.ps1 | 161 ++++++++++++++++-- 1 file changed, 144 insertions(+), 17 deletions(-) diff --git a/tools/ReleaseEngineering/ServicingPipeline.ps1 b/tools/ReleaseEngineering/ServicingPipeline.ps1 index c562ae0195..957168dbe7 100644 --- a/tools/ReleaseEngineering/ServicingPipeline.ps1 +++ b/tools/ReleaseEngineering/ServicingPipeline.ps1 @@ -126,6 +126,10 @@ Function Get-GraphQlProjectWithNodes($Organization, $Number) { pageInfo { hasNextPage endCursor } nodes { id + type + title: fieldValueByName(name: "Title") { + ... on ProjectV2ItemFieldTextValue { text } + } status: fieldValueByName(name: "Status") { ... on ProjectV2ItemFieldSingleSelectValue { name } } @@ -161,6 +165,96 @@ Function Get-GraphQlProjectWithNodes($Organization, $Number) { $Project } +Enum ServicingStatus { + Unknown + ToConsider + ToCherryPick + CherryPicked + Validated + Shipped + Rejected +} + +Enum ServicingItemType { + Unknown + DraftIssue + Issue + PullRequest + Redacted +} + +Class ServicingCard { + [String]$Id + [Int]$Number + [String]$Title + [String]$Commit + [ServicingStatus]$Status + [ServicingItemType]$Type + static [ServicingItemType]TypeFromString($name) { + $v = Switch -Exact ($name) { + "DRAFT_ISSUE" { [ServicingItemType]::DraftIssue } + "ISSUE" { [ServicingItemType]::Issue } + "PULL_REQUEST" { [ServicingItemType]::PullRequest } + "REDACTED" { [ServicingItemType]::Redacted } + Default { [ServicingItemType]::Unknown } + } + Return $v + } + + static [ServicingStatus]StatusFromString($name) { + $v = Switch -Exact ($name) { + "To Consider" { [ServicingStatus]::ToConsider } + "To Cherry Pick" { [ServicingStatus]::ToCherryPick } + "Cherry Picked" { [ServicingStatus]::CherryPicked } + "Validated" { [ServicingStatus]::Validated } + "Shipped" { [ServicingStatus]::Shipped } + "Rejected" { [ServicingStatus]::Rejected } + Default { [ServicingStatus]::Unknown } + } + Return $v + } + + ServicingCard([object]$node) { + $this.Id = $node.id + $this.Title = $node.title.text + If (-Not [String]::IsNullOrEmpty($node.content.mergeCommit.oid)) { + $this.Commit = $node.content.mergeCommit.oid + } ElseIf (-Not [String]::IsNullOrEmpty($node.content.closedByPullRequestsReferences.nodes.mergeCommit.oid)) { + $this.Commit = $node.content.closedByPullRequestsReferences.nodes.mergeCommit.oid + } + If (-Not [String]::IsNullOrEmpty($node.content.number)) { + $this.Number = [Int]$node.content.number + } + $this.Status = [ServicingCard]::StatusFromString($node.status.name) + $this.Type = [ServicingCard]::TypeFromString($node.type) + } + + [string]Format() { + $color = Switch -Exact ($this.Status) { + ([ServicingStatus]::ToConsider) { "`e[38:5:166m" } + ([ServicingStatus]::ToCherryPick) { "`e[38:5:28m" } + ([ServicingStatus]::CherryPicked) { "`e[38:5:20m" } + ([ServicingStatus]::Validated) { "`e[38:5:126m" } + ([ServicingStatus]::Shipped) { "`e[38:5:92m" } + ([ServicingStatus]::Rejected) { "`e[38:5:160m" } + Default { "`e[m" } + } + $symbol = Switch -Exact ($this.Type) { + ([ServicingItemType]::DraftIssue) { "`u{25cb}" } # white circle + ([ServicingItemType]::Issue) { "`u{2b24}" } # black circle + ([ServicingItemType]::PullRequest) { "`u{25c0}" } # black left triangle + ([ServicingItemType]::Redacted) { "`u{25ec}" } # triangle with dot + Default { "`u{2b24}" } + } + $localTitle = $this.Title + If ($this.Number -Gt 0) { + $localTitle = "[`e]8;;https://github.com/microsoft/terminal/issues/{1}`e\Link`e]8;;`e\] {0} (#{1})" -f ($this.Title, $this.Number) + } + Return "{0}{1}`e[m ({2}) {3}" -f ($color, $symbol, $this.Status, $localTitle) + } +} + + If ([String]::IsNullOrEmpty($Version)) { $BranchVersionRegex = [Regex]"^release-(\d+(\.\d+)+)$" $Branch = & git rev-parse --abbrev-ref HEAD @@ -209,20 +303,50 @@ $StatusFieldId = $StatusField.id $StatusRejectOptionId = $StatusField.options | Where-Object name -eq $script:RejectStatusName | Select-Object -Expand id $StatusDoneOptionId = $StatusField.options | Where-Object name -eq $script:DoneStatusName | Select-Object -Expand id -$ToPickList = $Project.organization.projectV2.items.nodes | Where-Object { $_.status.name -eq $TodoStatusName } +# Create ServicingCards out of each node, but filter out the ones with no commits. +$cards = $Project.organization.projectV2.items.nodes | ForEach-Object { [ServicingCard]::new($_) } -$commits = New-Object System.Collections.ArrayList -$cards = [System.Collections.Generic.Dictionary[String, String[]]]::new() -$ToPickList | ForEach-Object { - If (-Not [String]::IsNullOrEmpty($_.content.mergeCommit.oid)) { - $co = $_.content.mergeCommit.oid - } ElseIf (-Not [String]::IsNullOrEmpty($_.content.closedByPullRequestsReferences.nodes.mergeCommit.oid)) { - $co = $_.content.closedByPullRequestsReferences.nodes.mergeCommit.oid - } Else { - Return +$incompleteCards = $cards | Where-Object { [String]::IsNullOrEmpty($_.Commit) -And $_.Status -Eq ([ServicingStatus]::ToCherryPick) } +If ($incompleteCards.Length -Gt 0) { + Write-Host "Cards to cherry pick are not associated with commits:" + $incompleteCards | ForEach-Object { + Write-Host " - $($_.Format())" } - $null = $commits.Add($co) - $cards[$co] += $_.id + Write-Host "" +} + +$considerCards = $cards | Where-Object Status -Eq ([ServicingStatus]::ToConsider) +If ($considerCards.Length -Gt 0) { + Write-Host "`e[7m CONSIDERATION QUEUE `e[27m" + $considerCards | ForEach-Object { + Write-Host " - $($_.Format())" + } + Write-Host "" +} + +$commitGroups = $cards | Where-Object { -Not [String]::IsNullOrEmpty($_.Commit) } | Group-Object Commit +$commits = New-Object System.Collections.ArrayList +$finalCardsForCommit = [System.Collections.Generic.Dictionary[String, ServicingCard[]]]::new() + +$commitGroups | ForEach-Object { + $statuses = $_.Group | Select-Object -Unique Status + If ($statuses.Length -Gt 1) { + Write-Host "Commit $($_.Name) is present in more than one column:" + $_.Group | ForEach-Object { + Write-Host " - $($_.Format())" + } + Write-Host "`e[1mIt will be ignored.`e[m`n" + } Else { + If ($statuses[0].Status -eq ([ServicingStatus]::ToCherryPick)) { + $null = $commits.Add($_.Name) + $finalCardsForCommit[$_.Name] = $_.Group + } + } +} + +If ($commits.Count -Eq 0) { + Write-Host "Nothing to do." + Exit } $sortedAllCommits = & git rev-list --no-walk=sorted $commits @@ -235,6 +359,9 @@ If ($GpgSign) { $sortedAllCommits | ForEach-Object { Write-Host "`e[96m`e[1;7mPICK`e[22;27m`e[m $(& git show -q --pretty=oneline $_)" + ForEach($card In $finalCardsForCommit[$_]) { + Write-Host $card.Format() + } $null = & git cherry-pick -x $_ 2>&1 $Err = "" While ($True) { @@ -249,8 +376,8 @@ $sortedAllCommits | ForEach-Object { } If ($Global:Reject) { - ForEach($card In $cards[$_]) { - Set-GraphQlProjectEntryStatus -Project $Project.organization.projectV2.id -Item $card -Field $StatusFieldId -Value $StatusRejectOptionId + ForEach($card In $finalCardsForCommit[$_]) { + Set-GraphQlProjectEntryStatus -Project $Project.organization.projectV2.id -Item $card.Id -Field $StatusFieldId -Value $StatusRejectOptionId } # Fall through to Skip } @@ -262,10 +389,10 @@ $sortedAllCommits | ForEach-Object { $Err = & git cherry-pick --continue --no-edit } Else { - & git commit @PickArgs --amend --no-edit --trailer "Service-Card-Id:$($cards[$_])" --trailer "Service-Version:$Version" | Out-Null + & git commit @PickArgs --amend --no-edit --trailer "Service-Card-Id:$($finalCardsForCommit[$_].Id)" --trailer "Service-Version:$Version" | Out-Null Write-Host "`e[92;1;7m OK `e[m" - ForEach($card In $cards[$_]) { - Set-GraphQlProjectEntryStatus -Project $Project.organization.projectV2.id -Item $card -Field $StatusFieldId -Value $StatusDoneOptionId + ForEach($card In $finalCardsForCommit[$_]) { + Set-GraphQlProjectEntryStatus -Project $Project.organization.projectV2.id -Item $card.Id -Field $StatusFieldId -Value $StatusDoneOptionId } Break } From d2089ec1bd8debedb2d0a731377ba1437c6bd720 Mon Sep 17 00:00:00 2001 From: HO-COOH Date: Sat, 26 Apr 2025 06:55:24 +0800 Subject: [PATCH 030/177] Remove virtual functions in BaseWindow with CRTP (#18840) Minor refactors. Obviously you do not need `virtual` functions when you have CRTP which `BaseWindow` already uses. --- src/cascadia/WindowsTerminal/BaseWindow.h | 22 +++++-------------- src/cascadia/WindowsTerminal/IslandWindow.h | 12 +++++----- .../WindowsTerminal/NonClientIslandWindow.h | 4 ++-- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/cascadia/WindowsTerminal/BaseWindow.h b/src/cascadia/WindowsTerminal/BaseWindow.h index 2b1f57c512..f780946282 100644 --- a/src/cascadia/WindowsTerminal/BaseWindow.h +++ b/src/cascadia/WindowsTerminal/BaseWindow.h @@ -9,7 +9,6 @@ class BaseWindow public: static constexpr UINT CM_UPDATE_TITLE = WM_USER + 0; - virtual ~BaseWindow() = 0; static T* GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); @@ -37,7 +36,7 @@ public: return DefWindowProc(window, message, wparam, lparam); } - [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept + [[nodiscard]] LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { @@ -58,19 +57,19 @@ public: if (_minimized) { _minimized = false; - OnRestore(); + static_cast(this)->OnRestore(); } // We always need to fire the resize event, even when we're transitioning from minimized. // We might be transitioning directly from minimized to maximized, and we'll need // to trigger any size-related content changes. - OnResize(width, height); + static_cast(this)->OnResize(width, height); break; case SIZE_MINIMIZED: if (!_minimized) { _minimized = true; - OnMinimize(); + static_cast(this)->OnMinimize(); } break; default: @@ -109,10 +108,6 @@ public: return 0; } - virtual void OnResize(const UINT width, const UINT height) = 0; - virtual void OnMinimize() = 0; - virtual void OnRestore() = 0; - RECT GetWindowRect() const noexcept { RECT rc = { 0 }; @@ -204,13 +199,13 @@ protected: void _setupUserData() { - SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast(this)); + SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast(static_cast(this))); } // Method Description: // - This method is called when the window receives the WM_NCCREATE message. // Return Value: // - The value returned from the window proc. - [[nodiscard]] virtual LRESULT OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept + [[nodiscard]] LRESULT OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept { _setupUserData(); @@ -220,8 +215,3 @@ protected: return DefWindowProc(_window.get(), WM_NCCREATE, wParam, lParam); }; }; - -template -inline BaseWindow::~BaseWindow() -{ -} diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 97bdd0841d..a720b321d7 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -19,7 +19,7 @@ public: static void ShowCursorMaybe(const UINT message) noexcept; IslandWindow() noexcept; - virtual ~IslandWindow() override; + ~IslandWindow(); virtual void MakeWindow() noexcept; virtual void Close(); @@ -27,13 +27,13 @@ public: virtual void OnSize(const UINT width, const UINT height); HWND GetInteropHandle() const; - [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; + [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; - [[nodiscard]] LRESULT OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override; + [[nodiscard]] LRESULT OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept; - void OnResize(const UINT width, const UINT height) override; - void OnMinimize() override; - void OnRestore() override; + void OnResize(const UINT width, const UINT height); + void OnMinimize(); + void OnRestore(); virtual void OnAppInitialized(); virtual void SetContent(winrt::Windows::UI::Xaml::UIElement content); virtual void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme); diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index bdf64f9840..6fc0761a3c 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -27,13 +27,13 @@ public: static constexpr const int topBorderVisibleHeight = 1; NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept; - ~NonClientIslandWindow() override; + ~NonClientIslandWindow(); virtual void Close() override; void MakeWindow() noexcept override; virtual void OnSize(const UINT width, const UINT height) override; - [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; + [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; virtual til::rect GetNonClientFrame(UINT dpi) const noexcept override; virtual til::size GetTotalNonClientExclusiveSize(UINT dpi) const noexcept override; From 21f31793263d411c17ae676855f431b357211b64 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 25 Apr 2025 18:00:49 -0500 Subject: [PATCH 031/177] release-engineering: package (during build) and upload GPO templates (#18841) I've been doing this manually. It is time for me to do it not-manually. --- .../job-merge-msix-into-bundle.yml | 4 ++++ .../Draft-TerminalReleases.ps1 | 21 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml index 1668dcfb29..261750db8f 100644 --- a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml +++ b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml @@ -147,6 +147,10 @@ jobs: ValidateSignature: true Verbosity: 'Verbose' + - pwsh: |- + tar -c -v --format=zip -f "$(JobOutputDirectory)/GroupPolicyTemplates_$(XES_APPXMANIFESTVERSION).zip" -C "$(Build.SourcesDirectory)/policies" * + displayName: Package GPO Templates + - ${{ parameters.afterBuildSteps }} - ${{ if eq(parameters.publishArtifacts, true) }}: diff --git a/tools/ReleaseEngineering/Draft-TerminalReleases.ps1 b/tools/ReleaseEngineering/Draft-TerminalReleases.ps1 index 5d3775892e..3da91ff51a 100644 --- a/tools/ReleaseEngineering/Draft-TerminalReleases.ps1 +++ b/tools/ReleaseEngineering/Draft-TerminalReleases.ps1 @@ -30,6 +30,7 @@ Enum AssetType { Unknown ApplicationBundle PreinstallKit + GroupPolicy Zip } @@ -83,6 +84,9 @@ Class Asset { $local:bundlePath = Join-Path $local:directory $local:bundleName $this.Type = [AssetType]::PreinstallKit $this.Architecture = "all" + } ElseIf (".zip" -eq $local:ext -and $local:filename -like 'GroupPolicy*') { + $this.Type = [AssetType]::GroupPolicy + $this.Architecture = "all" } ElseIf (".zip" -eq $local:ext) { $this.Type = [AssetType]::Zip } ElseIf (".msixbundle" -eq $local:ext) { @@ -90,7 +94,7 @@ Class Asset { $this.Architecture = "all" } - If ($this.Type -Ne [AssetType]::Zip) { + If ($this.Type -In ([AssetType]::ApplicationBundle, [AssetType]::PreinstallKit)) { Write-Verbose "Cracking bundle $($local:bundlePath)" $local:firstMsixName = & $script:tar -t -f $local:bundlePath | Select-String 'Cascadia.*\.msix' | @@ -105,8 +109,10 @@ Class Asset { $local:Manifest = [xml](Get-Content (Join-Path $local:directory AppxManifest.xml)) $this.ParseManifest($local:Manifest) } Else { - & $script:tar -x -f $this.Path -C $local:directory --strip-components=1 '*/wt.exe' - $this.ExpandedVersion = (Get-Item (Join-Path $local:directory wt.exe)).VersionInfo.ProductVersion + If ($this.Type -Ne [AssetType]::GroupPolicy) { + & $script:tar -x -f $this.Path -C $local:directory --strip-components=1 '*/wt.exe' + $this.ExpandedVersion = (Get-Item (Join-Path $local:directory wt.exe)).VersionInfo.ProductVersion + } # Zip files just encode everything in their filename. Not great, but workable. $this.ParseFilename($local:filename) @@ -133,7 +139,9 @@ Class Asset { $parts = [IO.Path]::GetFileNameWithoutExtension($filename).Split("_") $this.Name = $parts[0] $this.Version = $parts[1] - $this.Architecture = $parts[2] + If ($parts.Length -Ge 3) { + $this.Architecture = $parts[2] + } } [string]IdealFilename() { @@ -149,6 +157,9 @@ Class Asset { Zip { "{0}_{1}_{2}.zip" -f ($this.Name, $this.Version, $this.Architecture) } + GroupPolicy { + "{0}_{1}.zip" -f ($this.Name, $this.Version) + } Default { Throw "Unknown type $($_.Type)" } @@ -174,7 +185,7 @@ class Release { Release([Asset[]]$a) { $this.Assets = $a - $this.Branding = $a[0].Branding + $this.Branding = $a | Where-Object Branding -Ne ([Branding]::Unknown) | Select -Unique -First 1 -Expand Branding $this.Name = Switch($this.Branding) { Release { "Windows Terminal" } Preview { "Windows Terminal Preview" } From a233b18d74b3da5f709349dd4d2954e7c334dddb Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 29 Apr 2025 15:23:23 -0500 Subject: [PATCH 032/177] Port OSS consumption changes from OS PR !12826284 (#18853) sources and sources.dep goo mostly. --- src/audio/midi/lib/sources.dep | 2 +- src/buffer/out/lib/sources.dep | 2 +- src/buffer/out/ut_textbuffer/sources.dep | 2 +- src/host/exe/sources.dep | 2 +- src/host/ft_host/sources.dep | 2 +- src/host/lib/sources.dep | 2 +- src/host/ut_host/sources.dep | 2 +- src/host/ut_lib/sources.dep | 2 +- src/interactivity/base/lib/sources.dep | 2 +- src/interactivity/onecore/lib/sources.dep | 2 +- src/interactivity/win32/lib/sources.dep | 2 +- src/interactivity/win32/sources.inc | 1 + src/interactivity/win32/ut_interactivity_win32/sources.dep | 2 +- src/project.inc | 7 +++---- src/project.unittest.inc | 2 +- src/propsheet/sources | 1 + src/propsheet/sources.dep | 2 +- src/propslib/sources.dep | 2 +- src/renderer/base/lib/sources.dep | 2 +- src/renderer/gdi/lib/sources.dep | 2 +- src/renderer/wddmcon/lib/sources.dep | 2 +- src/server/lib/sources.dep | 2 +- src/terminal/adapter/lib/sources.dep | 2 +- src/terminal/adapter/ut_adapter/sources.dep | 2 +- src/terminal/input/lib/sources.dep | 2 +- src/terminal/parser/ft_fuzzer/sources | 4 ++-- src/terminal/parser/ft_fuzzer/sources.dep | 2 +- src/terminal/parser/ft_fuzzwrapper/sources.dep | 2 +- src/terminal/parser/lib/sources.dep | 2 +- src/terminal/parser/ut_parser/sources.dep | 2 +- src/til/ut_til/sources.dep | 2 +- src/tools/vtpipeterm/sources | 1 + src/tsf/sources.dep | 2 +- src/types/lib/sources.dep | 2 +- src/types/ut_types/sources.dep | 2 +- 35 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/audio/midi/lib/sources.dep b/src/audio/midi/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/audio/midi/lib/sources.dep +++ b/src/audio/midi/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/buffer/out/lib/sources.dep b/src/buffer/out/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/buffer/out/lib/sources.dep +++ b/src/buffer/out/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/buffer/out/ut_textbuffer/sources.dep b/src/buffer/out/ut_textbuffer/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/buffer/out/ut_textbuffer/sources.dep +++ b/src/buffer/out/ut_textbuffer/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/host/exe/sources.dep b/src/host/exe/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/host/exe/sources.dep +++ b/src/host/exe/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/host/ft_host/sources.dep b/src/host/ft_host/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/host/ft_host/sources.dep +++ b/src/host/ft_host/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/host/lib/sources.dep b/src/host/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/host/lib/sources.dep +++ b/src/host/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/host/ut_host/sources.dep b/src/host/ut_host/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/host/ut_host/sources.dep +++ b/src/host/ut_host/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/host/ut_lib/sources.dep b/src/host/ut_lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/host/ut_lib/sources.dep +++ b/src/host/ut_lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/interactivity/base/lib/sources.dep b/src/interactivity/base/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/interactivity/base/lib/sources.dep +++ b/src/interactivity/base/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/interactivity/onecore/lib/sources.dep b/src/interactivity/onecore/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/interactivity/onecore/lib/sources.dep +++ b/src/interactivity/onecore/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/interactivity/win32/lib/sources.dep b/src/interactivity/win32/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/interactivity/win32/lib/sources.dep +++ b/src/interactivity/win32/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/interactivity/win32/sources.inc b/src/interactivity/win32/sources.inc index f816c285db..cd35ffc408 100644 --- a/src/interactivity/win32/sources.inc +++ b/src/interactivity/win32/sources.inc @@ -60,4 +60,5 @@ INCLUDES = \ ..; \ TARGETLIBS = \ + $(TARGETLIBS) \ $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib \ diff --git a/src/interactivity/win32/ut_interactivity_win32/sources.dep b/src/interactivity/win32/ut_interactivity_win32/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/interactivity/win32/ut_interactivity_win32/sources.dep +++ b/src/interactivity/win32/ut_interactivity_win32/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/project.inc b/src/project.inc index 1d86343d9c..152aca2717 100644 --- a/src/project.inc +++ b/src/project.inc @@ -4,16 +4,16 @@ # ------------------------------------- # Pull our dependencies from vcpkg. This includes gsl - if you run into errors -# from a missing gsl, make sure you go build onecore/window/vcpkg first. +# from a missing gsl or fmt, make sure you go build .../console/vcpkg first. -!include $(PROJECT_ROOT)\vcpkg\consume.inc +!include $(PROJECT_ROOT)\core\console\vcpkg\consume.inc # ------------------------------------- # Preprocessor Settings # ------------------------------------- UNICODE = 1 -C_DEFINES = $(C_DEFINES) -DUNICODE -D_UNICODE -DFMT_HEADER_ONLY -D__INSIDE_WINDOWS -DBUILD_ONECORE_INTERACTIVITY +C_DEFINES = $(C_DEFINES) -DUNICODE -D_UNICODE -D__INSIDE_WINDOWS -DBUILD_ONECORE_INTERACTIVITY # ------------------------------------- # CRT Configuration @@ -50,7 +50,6 @@ INCLUDES= \ $(CONSOLE_SRC_PATH)\inc; \ $(CONSOLE_SRC_PATH)\..\..\inc; \ $(CONSOLE_SRC_PATH)\..\oss\chromium; \ - $(CONSOLE_SRC_PATH)\..\oss\fmt\include; \ $(CONSOLE_SRC_PATH)\..\oss\interval_tree; \ $(CONSOLE_SRC_PATH)\..\oss\pcg\include; \ $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \ diff --git a/src/project.unittest.inc b/src/project.unittest.inc index 8d625b78e8..13b0caa9f4 100644 --- a/src/project.unittest.inc +++ b/src/project.unittest.inc @@ -9,7 +9,7 @@ # Preprocessor Settings # ------------------------------------- -C_DEFINES = $(C_DEFINES) -DINLINE_TEST_METHOD_MARKUP -DUNIT_TESTING -DFMT_HEADER_ONLY +C_DEFINES = $(C_DEFINES) -DINLINE_TEST_METHOD_MARKUP -DUNIT_TESTING # ------------------------------------- # Program Information diff --git a/src/propsheet/sources b/src/propsheet/sources index 033d3a5396..84bf81282e 100644 --- a/src/propsheet/sources +++ b/src/propsheet/sources @@ -78,6 +78,7 @@ INCLUDES = \ ..\host; \ TARGETLIBS = \ + $(TARGETLIBS) \ $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib \ $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecoreuap.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\onecore_internal.lib \ diff --git a/src/propsheet/sources.dep b/src/propsheet/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/propsheet/sources.dep +++ b/src/propsheet/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/propslib/sources.dep b/src/propslib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/propslib/sources.dep +++ b/src/propslib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/renderer/base/lib/sources.dep b/src/renderer/base/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/renderer/base/lib/sources.dep +++ b/src/renderer/base/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/renderer/gdi/lib/sources.dep b/src/renderer/gdi/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/renderer/gdi/lib/sources.dep +++ b/src/renderer/gdi/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/renderer/wddmcon/lib/sources.dep b/src/renderer/wddmcon/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/renderer/wddmcon/lib/sources.dep +++ b/src/renderer/wddmcon/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/server/lib/sources.dep b/src/server/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/server/lib/sources.dep +++ b/src/server/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/terminal/adapter/lib/sources.dep b/src/terminal/adapter/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/terminal/adapter/lib/sources.dep +++ b/src/terminal/adapter/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/terminal/adapter/ut_adapter/sources.dep b/src/terminal/adapter/ut_adapter/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/terminal/adapter/ut_adapter/sources.dep +++ b/src/terminal/adapter/ut_adapter/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/terminal/input/lib/sources.dep b/src/terminal/input/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/terminal/input/lib/sources.dep +++ b/src/terminal/input/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/terminal/parser/ft_fuzzer/sources b/src/terminal/parser/ft_fuzzer/sources index a8c508a25d..c0dfad9134 100644 --- a/src/terminal/parser/ft_fuzzer/sources +++ b/src/terminal/parser/ft_fuzzer/sources @@ -3,7 +3,7 @@ # - Console Virtual Terminal Parser Fuzzer # ------------------------------------- -!include $(PROJECT_ROOT)\vcpkg\consume.inc +!include ..\..\..\..\..\vcpkg\consume.inc # This program will generate fuzz input for the parsing engine # and is to be used in conjunction with the fuzz wrapper tool. @@ -47,6 +47,7 @@ C_DEFINES = $(C_DEFINES) -DUNICODE -D_UNICODE USE_STD_CPP20 = 1 MSC_WARNING_LEVEL = /W4 /WX +USER_C_FLAGS = $(USER_C_FLAGS) /Zc:preprocessor /fp:contract /utf-8 # ------------------------------------- # Build System Settings @@ -68,7 +69,6 @@ SOURCES = \ INCLUDES = \ ..\..\..\inc; \ $(CONSOLE_SRC_PATH)\..\oss\chromium; \ - $(CONSOLE_SRC_PATH)\..\oss\fmt\include; \ $(CONSOLE_SRC_PATH)\..\oss\interval_tree; \ $(INCLUDES) \ diff --git a/src/terminal/parser/ft_fuzzer/sources.dep b/src/terminal/parser/ft_fuzzer/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/terminal/parser/ft_fuzzer/sources.dep +++ b/src/terminal/parser/ft_fuzzer/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/terminal/parser/ft_fuzzwrapper/sources.dep b/src/terminal/parser/ft_fuzzwrapper/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/terminal/parser/ft_fuzzwrapper/sources.dep +++ b/src/terminal/parser/ft_fuzzwrapper/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/terminal/parser/lib/sources.dep b/src/terminal/parser/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/terminal/parser/lib/sources.dep +++ b/src/terminal/parser/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/terminal/parser/ut_parser/sources.dep b/src/terminal/parser/ut_parser/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/terminal/parser/ut_parser/sources.dep +++ b/src/terminal/parser/ut_parser/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/til/ut_til/sources.dep b/src/til/ut_til/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/til/ut_til/sources.dep +++ b/src/til/ut_til/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/tools/vtpipeterm/sources b/src/tools/vtpipeterm/sources index f19c0fe1ca..ecbc0bfcfd 100644 --- a/src/tools/vtpipeterm/sources +++ b/src/tools/vtpipeterm/sources @@ -18,6 +18,7 @@ USE_NATIVE_EH = 1 C_DEFINES=-DUNICODE -D__INSIDE_WINDOWS TARGETLIBS=\ + $(TARGETLIBS) \ $(MINWIN_EXTERNAL_SDK_LIB_PATH_L)\ntdll.lib \ $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib diff --git a/src/tsf/sources.dep b/src/tsf/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/tsf/sources.dep +++ b/src/tsf/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/types/lib/sources.dep b/src/types/lib/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/types/lib/sources.dep +++ b/src/types/lib/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ diff --git a/src/types/ut_types/sources.dep b/src/types/ut_types/sources.dep index bc61c95c6b..fce1139651 100644 --- a/src/types/ut_types/sources.dep +++ b/src/types/ut_types/sources.dep @@ -1,3 +1,3 @@ BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ + onecore\windows\core\console\vcpkg|PASS1 \ From 0568173abab4150d167dcced3994c99356dfc5cd Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 29 Apr 2025 15:37:56 -0500 Subject: [PATCH 033/177] wpf: strong-name sign Microsoft.Terminal.Wpf (#18836) This requires us to delay-sign the assembly with a public key (the snk file), and then later submit it for strong naming. This is separate from code signing, and has to take place before it. The snk file does not contain any private key material. This cannot merge until we are approved to use this new signing "key code". --- .github/actions/spelling/expect/expect.txt | 1 + build/config/272MSSharedLibSN2048.snk | Bin 0 -> 288 bytes build/config/esrp.build.batch.wpfdotnet.json | 14 ++++++++++++++ .../WpfTerminalControl/WpfTerminalControl.csproj | 6 ++++++ 4 files changed, 21 insertions(+) create mode 100644 build/config/272MSSharedLibSN2048.snk diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 4356e36eae..f80ab40840 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1679,6 +1679,7 @@ SMARTQUOTE SMTO snapcx snapcy +snk SOLIDBOX Solutiondir somefile diff --git a/build/config/272MSSharedLibSN2048.snk b/build/config/272MSSharedLibSN2048.snk new file mode 100644 index 0000000000000000000000000000000000000000..bd766f84a23ac0323c0589df14827e0644f6a6ae GIT binary patch literal 288 zcmV+*0pI=rBme*mfB*m#0RR970ssI2Bme+XQ$aBR2mk;90097ns?fghXpI}0N)347 z{VDt;tTgRCI>Y;$Jq$=VYp67;hyXPP3W!Lu*sb-BXAaT{6uvfrsFBIYz#i`^vM6#? zd^x@VuMTW-NL_sW8d2YgN7HQUshE)lwTixZ=A-8t0qtwthHw&yJ_{O6HLp+enc>H;SZF)np*8VQkMZhY=?#0CI&6piJ=rt}<6RP#`KS4*d|kC{4?MzJ m(H@s~`ArjxJnB=qs52ZcZOe@=sZJQb5o7-mDk9t2Ekc^_F@YHX literal 0 HcmV?d00001 diff --git a/build/config/esrp.build.batch.wpfdotnet.json b/build/config/esrp.build.batch.wpfdotnet.json index 0699353f64..316aa70116 100644 --- a/build/config/esrp.build.batch.wpfdotnet.json +++ b/build/config/esrp.build.batch.wpfdotnet.json @@ -6,6 +6,20 @@ ], "SigningInfo": { "Operations": [ + { + "KeyCode": "CP-233904-SN", + "OperationSetCode": "StrongNameSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": [] + }, + { + "KeyCode": "CP-233904-SN", + "OperationSetCode": "StrongNameVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": [] + }, { "KeyCode": "CP-230012", "OperationSetCode": "SigntoolSign", diff --git a/src/cascadia/WpfTerminalControl/WpfTerminalControl.csproj b/src/cascadia/WpfTerminalControl/WpfTerminalControl.csproj index 6581f1478b..8a299b5c05 100644 --- a/src/cascadia/WpfTerminalControl/WpfTerminalControl.csproj +++ b/src/cascadia/WpfTerminalControl/WpfTerminalControl.csproj @@ -15,6 +15,12 @@ 0.1 + + true + true + ..\..\..\build\config\272MSSharedLibSN2048.snk + + true From 08e76da3a1687a1303733406b684a9a6ab523acb Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 30 Apr 2025 00:15:38 +0100 Subject: [PATCH 034/177] Fix two image erasure bugs (#18855) This PR fixes two cases where image content wasn't correctly erased when overwritten. 1. When legacy console APIs fill an area of the buffer using a starting coordinate and a length, the affected area could potentially wrap over multiple rows, but we were only erasing the overwritten image content on the first affected row. 2. When copying an area of the buffer with text content over another area that contained image content, the image in the target area would sometimes not be erased, because we ignored the `_eraseCells` return value which indicated that the image slice needed to be removed. ## References and Relevant Issues The original code was from the Sixel implementation in PR #17421. ## Validation Steps Performed I've manually verified that these two cases are now working as expected. ## PR Checklist - [x] Closes #18568 --- src/buffer/out/ImageSlice.cpp | 31 +++++++++++++++++++++---------- src/buffer/out/ImageSlice.hpp | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/buffer/out/ImageSlice.cpp b/src/buffer/out/ImageSlice.cpp index 65809e58a0..3500eaf0a5 100644 --- a/src/buffer/out/ImageSlice.cpp +++ b/src/buffer/out/ImageSlice.cpp @@ -186,18 +186,20 @@ bool ImageSlice::_copyCells(const ImageSlice& srcSlice, const til::CoordType src } // The used destination before and after the written area must be erased. - if (dstUsedBegin < dstWriteBegin) + // If this results in the entire range being erased, we return true to let + // the caller know that the slice should be deleted. + if (dstUsedBegin < dstWriteBegin && _eraseCells(dstUsedBegin, dstWriteBegin)) { - _eraseCells(dstUsedBegin, dstWriteBegin); + return true; } - if (dstUsedEnd > dstWriteEnd) + if (dstUsedEnd > dstWriteEnd && _eraseCells(dstWriteEnd, dstUsedEnd)) { - _eraseCells(dstWriteEnd, dstUsedEnd); + return true; } - // If the beginning column is now not less than the end, that means the - // content has been entirely erased, so we return true to let the caller - // know that the slice should be deleted. + // At this point, if the beginning column is not less than the end, that + // means this was an empty slice into which nothing was copied, so we can + // again return true to let the caller know it should be deleted. return _columnBegin >= _columnEnd; } @@ -210,10 +212,19 @@ void ImageSlice::EraseBlock(TextBuffer& buffer, const til::rect rect) } } -void ImageSlice::EraseCells(TextBuffer& buffer, const til::point at, const size_t distance) +void ImageSlice::EraseCells(TextBuffer& buffer, const til::point at, const til::CoordType distance) { - auto& row = buffer.GetMutableRowByOffset(at.y); - EraseCells(row, at.x, gsl::narrow_cast(at.x + distance)); + auto x = at.x; + auto y = at.y; + auto distanceRemaining = distance; + while (distanceRemaining > 0) + { + auto& row = buffer.GetMutableRowByOffset(y); + EraseCells(row, x, x + distanceRemaining); + distanceRemaining -= (static_cast(row.size()) - x); + x = 0; + y++; + } } void ImageSlice::EraseCells(ROW& row, const til::CoordType columnBegin, const til::CoordType columnEnd) diff --git a/src/buffer/out/ImageSlice.hpp b/src/buffer/out/ImageSlice.hpp index 87244abe78..14c0b314ae 100644 --- a/src/buffer/out/ImageSlice.hpp +++ b/src/buffer/out/ImageSlice.hpp @@ -41,7 +41,7 @@ public: static void CopyRow(const ROW& srcRow, ROW& dstRow); static void CopyCells(const ROW& srcRow, const til::CoordType srcColumn, ROW& dstRow, const til::CoordType dstColumnBegin, const til::CoordType dstColumnEnd); static void EraseBlock(TextBuffer& buffer, const til::rect rect); - static void EraseCells(TextBuffer& buffer, const til::point at, const size_t distance); + static void EraseCells(TextBuffer& buffer, const til::point at, const til::CoordType distance); static void EraseCells(ROW& row, const til::CoordType columnBegin, const til::CoordType columnEnd); private: From 34b8ed3575ef4620ac21cb27749016698488ab81 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Thu, 1 May 2025 19:28:44 +0100 Subject: [PATCH 035/177] Account for custom cell sizes in launch size calculation (#18862) When calculating the initial terminal window size, we weren't taking into account the line height and cell width settings, so the resulting number of rows and columns didn't match the requested launch size. Verification: Manually verified that the window is now correctly sized when using a custom line height and cell width. Closes #18582 --- src/cascadia/TerminalControl/TermControl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 60833fdd92..7118c7a868 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2825,6 +2825,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto fontSize = settings.FontSize(); const auto fontWeight = settings.FontWeight(); const auto fontFace = settings.FontFace(); + const auto cellWidth = CSSLengthPercentage::FromString(settings.CellWidth().c_str()); + const auto cellHeight = CSSLengthPercentage::FromString(settings.CellHeight().c_str()); const auto scrollState = settings.ScrollState(); const auto padding = settings.Padding(); @@ -2836,6 +2838,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // not, but DX doesn't use that info at all. // The Codepage is additionally not actually used by the DX engine at all. FontInfoDesired desiredFont{ fontFace, 0, fontWeight.Weight, fontSize, CP_UTF8 }; + desiredFont.SetCellSize(cellWidth, cellHeight); FontInfo actualFont{ fontFace, 0, fontWeight.Weight, desiredFont.GetEngineSize(), CP_UTF8, false }; // Create a DX engine and initialize it with our font and DPI. We'll From 58092f142f0264a3bc5766bf53129aead4ae3c8b Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 2 May 2025 14:27:28 -0500 Subject: [PATCH 036/177] Bring back changes required to build conhost-1.24 in Windows (#18856) --- .github/actions/spelling/allow/apis.txt | 1 + src/host/sources.inc | 8 +++++--- src/host/sources.test.inc | 3 --- src/host/ut_host/sources | 3 --- src/interactivity/win32/ut_interactivity_win32/sources | 8 +++++--- src/project.unittest.inc | 1 + src/terminal/adapter/ut_adapter/sources | 8 +++++--- src/terminal/parser/ft_fuzzer/sources | 2 ++ src/terminal/parser/ft_fuzzwrapper/sources | 1 + src/terminal/parser/ut_parser/sources | 8 +++++--- src/tools/vtpipeterm/main.cpp | 8 ++++++++ src/tools/vtpipeterm/sources | 6 +++++- src/types/ut_types/sources | 1 + 13 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index f7671d04f4..9be081bc2b 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -222,6 +222,7 @@ Stubless Subheader Subpage syscall +syscolors SYSTEMBACKDROP TABROW TASKBARCREATED diff --git a/src/host/sources.inc b/src/host/sources.inc index e8c540fd80..c080c15ce7 100644 --- a/src/host/sources.inc +++ b/src/host/sources.inc @@ -132,7 +132,7 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\dxgi.lib \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3d11.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ - $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1-1-0.lib \ + $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ @@ -160,12 +160,14 @@ TARGETLIBS = \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-sysparams-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-window-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-winstamin-l1.lib \ + $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-syscolors-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-shell-shell32-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uxtheme-themes-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-dataobject-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-namespace-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uiacore-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-usp10-l1.lib \ + $(ONECORE_EXTERNAL_SDK_LIB_PATH)\ntdll.lib \ $(WINCORE_OBJ_PATH)\console\open\src\host\lib\$(O)\conhostv2.lib \ $(WINCORE_OBJ_PATH)\console\conint\$(O)\conint.lib \ $(WINCORE_OBJ_PATH)\console\open\src\buffer\out\lib\$(O)\conbufferout.lib \ @@ -177,7 +179,6 @@ TARGETLIBS = \ $(WINCORE_OBJ_PATH)\console\open\src\audio\midi\lib\$(O)\ConAudioMidi.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\base\lib\$(O)\ConRenderBase.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\gdi\lib\$(O)\ConRenderGdi.lib \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\vt\lib\$(O)\ConRenderVt.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\wddmcon\lib\$(O)\ConRenderWddmCon.lib \ $(WINCORE_OBJ_PATH)\console\open\src\server\lib\$(O)\ConServer.lib \ $(WINCORE_OBJ_PATH)\console\open\src\interactivity\base\lib\$(O)\ConInteractivityBaseLib.lib \ @@ -196,7 +197,7 @@ DELAYLOAD = \ api-ms-win-core-com-l1.dll; \ api-ms-win-core-registry-l2.dll; \ api-ms-win-mm-playsound-l1.dll; \ - ext-ms-win-imm-l1-1-0.lib; \ + ext-ms-win-imm-l1.dll; \ api-ms-win-shcore-obsolete-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ @@ -229,6 +230,7 @@ DELAYLOAD = \ ext-ms-win-rtcore-ntuser-sysparams-l1.dll; \ ext-ms-win-rtcore-ntuser-window-ext-l1.dll; \ ext-ms-win-rtcore-ntuser-winstamin-l1.dll; \ + ext-ms-win-rtcore-ntuser-syscolors-l1.dll; \ ext-ms-win-shell-shell32-l1.dll; \ ext-ms-win-uiacore-l1.dll; \ ext-ms-win-uxtheme-themes-l1.dll; \ diff --git a/src/host/sources.test.inc b/src/host/sources.test.inc index 7d49665c33..6fd7ff7412 100644 --- a/src/host/sources.test.inc +++ b/src/host/sources.test.inc @@ -24,10 +24,7 @@ INCLUDES = \ ..\..\inc\test; \ $(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \ -# prepend the ConRenderVt.Unittest.lib, so that it's linked before the non-ut version. - TARGETLIBS = \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\vt\ut_lib\$(O)\ConRenderVt.Unittest.lib \ $(TARGETLIBS) \ $(ONECORESDKTOOLS_INTERNAL_LIB_PATH_L)\WexTest\Cue\Wex.Common.lib \ $(ONECORESDKTOOLS_INTERNAL_LIB_PATH_L)\WexTest\Cue\Wex.Logger.lib \ diff --git a/src/host/ut_host/sources b/src/host/ut_host/sources index b690477830..d8da9ec993 100644 --- a/src/host/ut_host/sources +++ b/src/host/ut_host/sources @@ -41,10 +41,7 @@ INCLUDES = \ ..\..\inc\test; \ $(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \ -# prepend the ConRenderVt.Unittest.lib, so that it's linked before the non-ut version. - TARGETLIBS = \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\vt\ut_lib\$(O)\ConRenderVt.Unittest.lib \ $(WINCORE_OBJ_PATH)\console\open\src\host\ut_lib\$(O)\ConhostV2.Unittest.lib \ $(TARGETLIBS) \ $(ONECORESDKTOOLS_INTERNAL_LIB_PATH_L)\WexTest\Cue\Wex.Common.lib \ diff --git a/src/interactivity/win32/ut_interactivity_win32/sources b/src/interactivity/win32/ut_interactivity_win32/sources index 1822bf5179..55eaa847fc 100644 --- a/src/interactivity/win32/ut_interactivity_win32/sources +++ b/src/interactivity/win32/ut_interactivity_win32/sources @@ -50,7 +50,7 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\dxgi.lib \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\propsys.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ - $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1-1-0.lib \ + $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ @@ -76,12 +76,14 @@ TARGETLIBS = \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-sysparams-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-window-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-winstamin-l1.lib \ + $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-syscolors-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-shell-shell32-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uxtheme-themes-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-dataobject-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-namespace-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uiacore-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-usp10-l1.lib \ + $(ONECORE_EXTERNAL_SDK_LIB_PATH)\ntdll.lib \ $(WINCORE_OBJ_PATH)\console\conint\$(O)\conint.lib \ $(WINCORE_OBJ_PATH)\console\open\src\buffer\out\lib\$(O)\conbufferout.lib \ $(WINCORE_OBJ_PATH)\console\open\src\host\lib\$(O)\conhostv2.lib \ @@ -93,7 +95,6 @@ TARGETLIBS = \ $(WINCORE_OBJ_PATH)\console\open\src\audio\midi\lib\$(O)\ConAudioMidi.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\base\lib\$(O)\ConRenderBase.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\gdi\lib\$(O)\ConRenderGdi.lib \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\vt\lib\$(O)\ConRenderVt.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\wddmcon\lib\$(O)\ConRenderWddmCon.lib \ $(WINCORE_OBJ_PATH)\console\open\src\server\lib\$(O)\ConServer.lib \ $(WINCORE_OBJ_PATH)\console\open\src\interactivity\base\lib\$(O)\ConInteractivityBaseLib.lib \ @@ -112,7 +113,7 @@ DELAYLOAD = \ OLEAUT32.dll; \ icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ - ext-ms-win-imm-l1-1-0.lib; \ + ext-ms-win-imm-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ @@ -143,6 +144,7 @@ DELAYLOAD = \ ext-ms-win-rtcore-ntuser-sysparams-l1.dll; \ ext-ms-win-rtcore-ntuser-window-ext-l1.dll; \ ext-ms-win-rtcore-ntuser-winstamin-l1.dll; \ + ext-ms-win-rtcore-ntuser-syscolors-l1.dll; \ ext-ms-win-shell-shell32-l1.dll; \ ext-ms-win-uiacore-l1.dll; \ ext-ms-win-uxtheme-themes-l1.dll; \ diff --git a/src/project.unittest.inc b/src/project.unittest.inc index 13b0caa9f4..a4f5daf4bb 100644 --- a/src/project.unittest.inc +++ b/src/project.unittest.inc @@ -32,6 +32,7 @@ INCLUDES = \ TARGETLIBS = \ $(TARGETLIBS) \ $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib \ + $(ONECORE_EXTERNAL_SDK_LIB_PATH)\ntdll.lib \ $(ONECORESDKTOOLS_INTERNAL_LIB_PATH_L)\WexTest\Cue\Wex.Common.lib \ $(ONECORESDKTOOLS_INTERNAL_LIB_PATH_L)\WexTest\Cue\Wex.Logger.lib \ $(ONECORESDKTOOLS_INTERNAL_LIB_PATH_L)\WexTest\Cue\Te.Common.lib \ diff --git a/src/terminal/adapter/ut_adapter/sources b/src/terminal/adapter/ut_adapter/sources index 6f4eb322a2..2fe3c59699 100644 --- a/src/terminal/adapter/ut_adapter/sources +++ b/src/terminal/adapter/ut_adapter/sources @@ -48,7 +48,7 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3d11.lib \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ - $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1-1-0.lib \ + $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ @@ -73,12 +73,14 @@ TARGETLIBS = \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-sysparams-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-window-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-winstamin-l1.lib \ + $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-syscolors-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-shell-shell32-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uxtheme-themes-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-dataobject-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-namespace-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uiacore-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-usp10-l1.lib \ + $(ONECORE_EXTERNAL_SDK_LIB_PATH)\ntdll.lib \ $(WINCORE_OBJ_PATH)\console\conint\$(O)\conint.lib \ $(WINCORE_OBJ_PATH)\console\open\src\buffer\out\lib\$(O)\conbufferout.lib \ $(WINCORE_OBJ_PATH)\console\open\src\host\lib\$(O)\conhostv2.lib \ @@ -91,7 +93,6 @@ TARGETLIBS = \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\base\lib\$(O)\ConRenderBase.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\gdi\lib\$(O)\ConRenderGdi.lib \ $(WINCORE_OBJ_PATH)\console\open\src\renderer\wddmcon\lib\$(O)\ConRenderWddmCon.lib \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\vt\lib\$(O)\ConRenderVt.lib \ $(WINCORE_OBJ_PATH)\console\open\src\server\lib\$(O)\ConServer.lib \ $(WINCORE_OBJ_PATH)\console\open\src\interactivity\base\lib\$(O)\ConInteractivityBaseLib.lib \ $(WINCORE_OBJ_PATH)\console\open\src\interactivity\win32\lib\$(O)\ConInteractivityWin32Lib.lib \ @@ -107,7 +108,7 @@ DELAYLOAD = \ OLEAUT32.dll; \ icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ - ext-ms-win-imm-l1-1-0.lib; \ + ext-ms-win-imm-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ @@ -137,6 +138,7 @@ DELAYLOAD = \ ext-ms-win-rtcore-ntuser-sysparams-l1.dll; \ ext-ms-win-rtcore-ntuser-window-ext-l1.dll; \ ext-ms-win-rtcore-ntuser-winstamin-l1.dll; \ + ext-ms-win-rtcore-ntuser-syscolors-l1.dll; \ ext-ms-win-shell-shell32-l1.dll; \ ext-ms-win-uiacore-l1.dll; \ ext-ms-win-uxtheme-themes-l1.dll; \ diff --git a/src/terminal/parser/ft_fuzzer/sources b/src/terminal/parser/ft_fuzzer/sources index c0dfad9134..c221ac9c0c 100644 --- a/src/terminal/parser/ft_fuzzer/sources +++ b/src/terminal/parser/ft_fuzzer/sources @@ -27,6 +27,8 @@ TEST_CODE = 1 USE_UNICRT = 1 USE_MSVCRT = 1 +NO_WCHAR_T = 1 # use native wchar_t +USE_CXX17_STD_BYTE = 1 # Windows disables std::byte by default... USE_STL = 1 STL_VER = STL_VER_CURRENT diff --git a/src/terminal/parser/ft_fuzzwrapper/sources b/src/terminal/parser/ft_fuzzwrapper/sources index 1753a400e3..73df7091e4 100644 --- a/src/terminal/parser/ft_fuzzwrapper/sources +++ b/src/terminal/parser/ft_fuzzwrapper/sources @@ -60,6 +60,7 @@ INCLUDES = \ TARGETLIBS = \ $(TARGETLIBS) \ + $(ONECORE_EXTERNAL_SDK_LIB_PATH)\ntdll.lib \ $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib \ $(OBJ_PATH)\..\lib\$(O)\ConTermParser.lib \ $(OBJ_PATH)\..\..\..\types\lib\$(O)\ConTypes.lib \ diff --git a/src/terminal/parser/ut_parser/sources b/src/terminal/parser/ut_parser/sources index fcc2178953..fd4e09dc3b 100644 --- a/src/terminal/parser/ut_parser/sources +++ b/src/terminal/parser/ut_parser/sources @@ -38,7 +38,7 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3d11.lib \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ - $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1-1-0.lib \ + $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-imm-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ @@ -63,12 +63,14 @@ TARGETLIBS = \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-sysparams-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-window-ext-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-winstamin-l1.lib \ + $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-rtcore-ntuser-syscolors-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-shell-shell32-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uxtheme-themes-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-dataobject-l1.lib \ $(ONECORESHELL_INTERNAL_LIB_VPATH_L)\api-ms-win-shell-namespace-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-uiacore-l1.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-usp10-l1.lib \ + $(ONECORE_EXTERNAL_SDK_LIB_PATH)\ntdll.lib \ $(WINCORE_OBJ_PATH)\console\conint\$(O)\conint.lib \ $(CONSOLE_OBJ_PATH)\buffer\out\lib\$(O)\conbufferout.lib \ $(CONSOLE_OBJ_PATH)\host\lib\$(O)\conhostv2.lib \ @@ -80,7 +82,6 @@ TARGETLIBS = \ $(CONSOLE_OBJ_PATH)\renderer\base\lib\$(O)\ConRenderBase.lib \ $(CONSOLE_OBJ_PATH)\renderer\gdi\lib\$(O)\ConRenderGdi.lib \ $(CONSOLE_OBJ_PATH)\renderer\wddmcon\lib\$(O)\ConRenderWddmCon.lib \ - $(CONSOLE_OBJ_PATH)\renderer\vt\lib\$(O)\ConRenderVt.lib \ $(CONSOLE_OBJ_PATH)\audio\midi\lib\$(O)\ConAudioMidi.lib \ $(CONSOLE_OBJ_PATH)\server\lib\$(O)\ConServer.lib \ $(CONSOLE_OBJ_PATH)\interactivity\base\lib\$(O)\ConInteractivityBaseLib.lib \ @@ -98,7 +99,7 @@ DELAYLOAD = \ OLEAUT32.dll; \ icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ - ext-ms-win-imm-l1-1-0.lib; \ + ext-ms-win-imm-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ @@ -129,6 +130,7 @@ DELAYLOAD = \ ext-ms-win-rtcore-ntuser-sysparams-l1.dll; \ ext-ms-win-rtcore-ntuser-window-ext-l1.dll; \ ext-ms-win-rtcore-ntuser-winstamin-l1.dll; \ + ext-ms-win-rtcore-ntuser-syscolors-l1.dll; \ ext-ms-win-shell-shell32-l1.dll; \ ext-ms-win-uiacore-l1.dll; \ ext-ms-win-usp10-l1.dll; \ diff --git a/src/tools/vtpipeterm/main.cpp b/src/tools/vtpipeterm/main.cpp index fa79b0b9cf..3117f7aaaa 100644 --- a/src/tools/vtpipeterm/main.cpp +++ b/src/tools/vtpipeterm/main.cpp @@ -1,12 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #define NOMINMAX #include +#ifndef __INSIDE_WINDOWS #define CONPTY_IMPEXP #include +#else // Building inside Windows, just use the kernel32 ones. +#define ConptyCreatePseudoConsole CreatePseudoConsole +#define ConptyReleasePseudoConsole ReleasePseudoConsole +#define ConptyResizePseudoConsole ResizePseudoConsole +#endif #include diff --git a/src/tools/vtpipeterm/sources b/src/tools/vtpipeterm/sources index ecbc0bfcfd..5fec40ebba 100644 --- a/src/tools/vtpipeterm/sources +++ b/src/tools/vtpipeterm/sources @@ -1,3 +1,6 @@ +!include $(PROJECT_ROOT)\core\console\vcpkg\consume.inc + +USE_STD_CPP20 = 1 MSC_WARNING_LEVEL=/W4 /WX @@ -20,7 +23,8 @@ C_DEFINES=-DUNICODE -D__INSIDE_WINDOWS TARGETLIBS=\ $(TARGETLIBS) \ $(MINWIN_EXTERNAL_SDK_LIB_PATH_L)\ntdll.lib \ - $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib + $(ONECORE_EXTERNAL_SDK_LIB_VPATH_L)\onecore.lib \ + $(WINCORE_OBJ_PATH)\console\open\src\types\lib\$(O)\ConTypes.lib \ SOURCES=main.cpp \ diff --git a/src/types/ut_types/sources b/src/types/ut_types/sources index f5741649fe..e2fe087a64 100644 --- a/src/types/ut_types/sources +++ b/src/types/ut_types/sources @@ -25,6 +25,7 @@ INCLUDES = \ TARGETLIBS = \ $(WINCORE_OBJ_PATH)\console\open\src\types\lib\$(O)\ConTypes.lib \ + $(ONECORE_EXTERNAL_SDK_LIB_PATH)\ntdll.lib \ $(TARGETLIBS) \ # ------------------------------------- From 865f5e52397025cfb21f40871bfaf04d85a44986 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Fri, 2 May 2025 23:36:45 +0100 Subject: [PATCH 037/177] Fix SGR mouse movement reports (#18864) According to the documentation, the final character of an SGR mouse report is meant to be `M` for a button press and `m` for a button release. However it isn't clear what the final character should be for motion events, and we were using an `m` if there weren't any buttons held down at the time, while all other terminals used an `M`, regardless of the button state. This PR updates our implementation to match what everyone else is doing, since our interpretation of the spec was causing problems for some apps. ## Validation Steps Performed I've manually tested the new behavior in Vttest, and confirmed that our mouse reports now match Xterm more closely, and I've tested with v0.42.0 of Zellij, which was previous glitching badly in Windows Terminal, but now works correctly. I've also updated our unit tests for the SGR mouse mode to reflect the correct report format. Closes #18712 --- .../adapter/ut_adapter/MouseInputTest.cpp | 23 ++++++--------- src/terminal/input/mouseInput.cpp | 29 ++++++++----------- src/terminal/input/terminalInput.hpp | 2 +- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/terminal/adapter/ut_adapter/MouseInputTest.cpp b/src/terminal/adapter/ut_adapter/MouseInputTest.cpp index f3df50a0a6..fe39b86630 100644 --- a/src/terminal/adapter/ut_adapter/MouseInputTest.cpp +++ b/src/terminal/adapter/ut_adapter/MouseInputTest.cpp @@ -99,7 +99,7 @@ public: TerminalInput::StringType str; str.append(std::wstring_view{ buffer }); - str[str.size() - 1] = IsButtonDown(uiButton) ? L'M' : L'm'; + str[str.size() - 1] = IsButtonUp(uiButton) ? L'm' : L'M'; return str; } @@ -182,23 +182,18 @@ public: return result; } - bool IsButtonDown(unsigned int uiButton) + bool IsButtonUp(unsigned int uiButton) { - auto fIsDown = false; switch (uiButton) { - case WM_LBUTTONDBLCLK: - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_RBUTTONDBLCLK: - case WM_MBUTTONDOWN: - case WM_MBUTTONDBLCLK: - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: - fIsDown = true; - break; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: + return true; + default: + return false; } - return fIsDown; } /* From winuser.h - Needed to manually specify the test properties diff --git a/src/terminal/input/mouseInput.cpp b/src/terminal/input/mouseInput.cpp index 596df0360e..c36ce160b4 100644 --- a/src/terminal/input/mouseInput.cpp +++ b/src/terminal/input/mouseInput.cpp @@ -63,24 +63,19 @@ static constexpr bool _isWheelMsg(const unsigned int buttonCode) noexcept } // Routine Description: -// - Determines if the input windows message code describes a button press -// (either down or doubleclick) +// - Determines if the input windows message code describes a button release // Parameters: // - button - the message to decode. // Return value: -// - true if button is a button down event -static constexpr bool _isButtonDown(const unsigned int button) noexcept +// - true if button is a button up event +static constexpr bool _isButtonUp(const unsigned int button) noexcept { switch (button) { - case WM_LBUTTONDBLCLK: - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_RBUTTONDBLCLK: - case WM_MBUTTONDOWN: - case WM_MBUTTONDBLCLK: - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: return true; default: return false; @@ -372,7 +367,7 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position, // then we want to handle hovers with WM_MOUSEMOVE. // However, if we're dragging (WM_MOUSEMOVE with a button pressed), // then use that pressed button instead. - return _GenerateSGRSequence(position, physicalButtonPressed ? realButton : button, _isButtonDown(realButton), isHover, modifierKeyState, delta); + return _GenerateSGRSequence(position, physicalButtonPressed ? realButton : button, _isButtonUp(button), isHover, modifierKeyState, delta); } else { @@ -463,18 +458,18 @@ TerminalInput::OutputType TerminalInput::_GenerateUtf8Sequence(const til::point // Parameters: // - position - The windows coordinates (top,left = 0,0) of the mouse event // - button - the message to decode. WM_MOUSEMOVE is used for mouse hovers with no buttons pressed. -// - isDown - true if a mouse button was pressed. +// - isRelease - true if a mouse button was released. // - isHover - true if the sequence is generated in response to a mouse hover // - modifierKeyState - the modifier keys pressed with this button // - delta - the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL) // - ppwchSequence - On success, where to put the pointer to the generated sequence // - pcchLength - On success, where to put the length of the generated sequence -TerminalInput::OutputType TerminalInput::_GenerateSGRSequence(const til::point position, const unsigned int button, const bool isDown, const bool isHover, const short modifierKeyState, const short delta) +TerminalInput::OutputType TerminalInput::_GenerateSGRSequence(const til::point position, const unsigned int button, const bool isRelease, const bool isHover, const short modifierKeyState, const short delta) { // Format for SGR events is: - // "\x1b[<%d;%d;%d;%c", xButton, x+1, y+1, fButtonDown? 'M' : 'm' + // "\x1b[<%d;%d;%d;%c", xButton, x+1, y+1, isRelease? 'm' : 'M' const auto xbutton = _windowsButtonToSGREncoding(button, isHover, modifierKeyState, delta); - return fmt::format(FMT_COMPILE(L"{}<{};{};{}{}"), _csi, xbutton, position.x + 1, position.y + 1, isDown ? L'M' : L'm'); + return fmt::format(FMT_COMPILE(L"{}<{};{};{}{}"), _csi, xbutton, position.x + 1, position.y + 1, isRelease ? L'm' : L'M'); } // Routine Description: diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp index 4048826956..d9570cc9cc 100644 --- a/src/terminal/input/terminalInput.hpp +++ b/src/terminal/input/terminalInput.hpp @@ -109,7 +109,7 @@ namespace Microsoft::Console::VirtualTerminal #pragma region MouseInput [[nodiscard]] OutputType _GenerateDefaultSequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); [[nodiscard]] OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); - [[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isDown, bool isHover, short modifierKeyState, short delta); + [[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isRelease, bool isHover, short modifierKeyState, short delta); [[nodiscard]] OutputType _makeAlternateScrollOutput(short delta) const; From 06f736bebe84eda0c34b935a875eebe031a899b7 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 2 May 2025 18:30:52 -0500 Subject: [PATCH 038/177] wpf: use the new TSF implementation (#18861) This fixes two issues in the WPF terminal control: - The emoji picker and other IME candidate windows didn't show up in the right place - Submitting an emoji via the emoji picker would result in two win32 input mode events with a VK of 65535 and the surrogate pair halves. I am not sure I did the right thing with the thread TSF handle... --- src/cascadia/TerminalControl/HwndTerminal.cpp | 84 +++++++++++++++++++ src/cascadia/TerminalControl/HwndTerminal.hpp | 22 +++++ 2 files changed, 106 insertions(+) diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index d2aa1e1796..3cb02ee547 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -19,6 +19,79 @@ using namespace ::Microsoft::Terminal::Core; static LPCWSTR term_window_class = L"HwndTerminalClass"; +STDMETHODIMP HwndTerminal::TsfDataProvider::QueryInterface(REFIID, void**) noexcept +{ + return E_NOTIMPL; +} + +ULONG STDMETHODCALLTYPE HwndTerminal::TsfDataProvider::AddRef() noexcept +{ + return 1; +} + +ULONG STDMETHODCALLTYPE HwndTerminal::TsfDataProvider::Release() noexcept +{ + return 1; +} + +HWND HwndTerminal::TsfDataProvider::GetHwnd() +{ + return _terminal->GetHwnd(); +} + +RECT HwndTerminal::TsfDataProvider::GetViewport() +{ + const auto hwnd = GetHwnd(); + + RECT rc; + GetClientRect(hwnd, &rc); + + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect + // > The left and top members are zero. The right and bottom members contain the width and height of the window. + // --> We can turn the client rect into a screen-relative rect by adding the left/top position. + ClientToScreen(hwnd, reinterpret_cast(&rc)); + rc.right += rc.left; + rc.bottom += rc.top; + + return rc; +} + +RECT HwndTerminal::TsfDataProvider::GetCursorPosition() +{ + // Convert from columns/rows to pixels. + til::point cursorPos; + til::size fontSize; + { + const auto lock = _terminal->_terminal->LockForReading(); + cursorPos = _terminal->_terminal->GetCursorPosition(); // measured in terminal cells + fontSize = _terminal->_actualFont.GetSize(); // measured in pixels, not DIP + } + POINT ptSuggestion = { + .x = cursorPos.x * fontSize.width, + .y = cursorPos.y * fontSize.height, + }; + + ClientToScreen(GetHwnd(), &ptSuggestion); + + // Final measurement should be in pixels + return { + .left = ptSuggestion.x, + .top = ptSuggestion.y, + .right = ptSuggestion.x + fontSize.width, + .bottom = ptSuggestion.y + fontSize.height, + }; +} + +void HwndTerminal::TsfDataProvider::HandleOutput(std::wstring_view text) +{ + _terminal->_WriteTextToConnection(text); +} + +Microsoft::Console::Render::Renderer* HwndTerminal::TsfDataProvider::GetRenderer() +{ + return _terminal->_renderer.get(); +} + // This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx // "If the high-order bit is 1, the key is down; otherwise, it is up." static constexpr short KeyPressed{ gsl::narrow_cast(0x8000) }; @@ -242,6 +315,7 @@ try { // As a rule, detach resources from the Terminal before shutting them down. // This ensures that teardown is reentrant. + _tsfHandle = {}; // Shut down the renderer (and therefore the thread) before we implode _renderer.reset(); @@ -941,6 +1015,16 @@ void __stdcall TerminalSetFocus(void* terminal) { LOG_IF_FAILED(uiaEngine->Enable()); } + publicTerminal->_FocusTSF(); +} + +void HwndTerminal::_FocusTSF() noexcept +{ + if (!_tsfHandle) + { + _tsfHandle = Microsoft::Console::TSF::Handle::Create(); + _tsfHandle.AssociateFocus(&_tsfDataProvider); + } } void __stdcall TerminalKillFocus(void* terminal) diff --git a/src/cascadia/TerminalControl/HwndTerminal.hpp b/src/cascadia/TerminalControl/HwndTerminal.hpp index 8ecd32df02..350727f12a 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.hpp +++ b/src/cascadia/TerminalControl/HwndTerminal.hpp @@ -6,6 +6,7 @@ #include "../../buffer/out/textBuffer.hpp" #include "../../renderer/inc/FontInfoDesired.hpp" #include "../../types/IControlAccessibilityInfo.h" +#include "../../tsf/Handle.h" namespace Microsoft::Console::Render::Atlas { @@ -85,6 +86,21 @@ public: static LRESULT CALLBACK HwndTerminalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept; private: + struct TsfDataProvider : public Microsoft::Console::TSF::IDataProvider + { + TsfDataProvider(HwndTerminal* t) : + _terminal(t) {} + virtual ~TsfDataProvider() = default; + STDMETHODIMP TsfDataProvider::QueryInterface(REFIID, void**) noexcept override; + ULONG STDMETHODCALLTYPE TsfDataProvider::AddRef() noexcept override; + ULONG STDMETHODCALLTYPE TsfDataProvider::Release() noexcept override; + HWND GetHwnd() override; + RECT GetViewport() override; + RECT GetCursorPosition() override; + void HandleOutput(std::wstring_view text) override; + Microsoft::Console::Render::Renderer* GetRenderer() override; + HwndTerminal* _terminal; + }; wil::unique_hwnd _hwnd; FontInfoDesired _desiredFont; FontInfo _actualFont; @@ -106,6 +122,10 @@ private: std::optional _lastMouseClickPos; std::optional _singleClickTouchdownPos; + // _tsfHandle uses _tsfDataProvider. Destructors run from bottom to top; this maintains correct destruction order. + TsfDataProvider _tsfDataProvider{ this }; + Microsoft::Console::TSF::Handle _tsfHandle; + friend HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal); friend HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ til::CoordType width, _In_ til::CoordType height, _Out_ til::size* dimensions); friend HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ til::size dimensions, _Out_ til::size* dimensionsInPixels); @@ -129,6 +149,8 @@ private: HRESULT _CopyToSystemClipboard(const std::string& stringToCopy, LPCWSTR lpszFormat) const; void _PasteTextFromClipboard() noexcept; + void _FocusTSF() noexcept; + const unsigned int _NumberOfClicks(til::point clickPos, std::chrono::steady_clock::time_point clickTime) noexcept; HRESULT _StartSelection(LPARAM lParam) noexcept; HRESULT _MoveSelection(LPARAM lParam) noexcept; From 976a54d87cba22e21e83c7f719c516ec84bfc41e Mon Sep 17 00:00:00 2001 From: ALBIN BABU VARGHESE Date: Fri, 9 May 2025 19:00:46 -0400 Subject: [PATCH 039/177] Allow triple-click to select logical line (#18885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes #18877, by iteratively checking to see if a line is wrapped and moving up or down accordingly. **Current behavior:** When a user triple-clicks on a line that’s visually wrapped by the terminal, only the single physical row that was clicked gets selected. **Expected behavior:** A triple-click like in xterm, should select the entire logical line including all of its wrapped segments, from the true start through its true end, regardless of where the wrap occurred. **Why it matters:** Logical line selection is what users expect when they’re trying to grab one command or output block in full. Limiting the selection to just the current physical row can lead to copy/paste mistakes and a confusing experience whenever a long line wraps. ## Validation Steps Performed I ran the existing tests using `Invoke-OpenConsoleTests` and they were passing and I was also able to test the build on my machine. I added a test case as well ## PR Checklist Closes #18877 --- src/cascadia/TerminalCore/TerminalSelection.cpp | 12 ++++++++++++ .../UnitTests_TerminalCore/SelectionTest.cpp | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index 707415015a..d588b67cb0 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -294,9 +294,21 @@ std::pair Terminal::_ExpandSelectionAnchors(std::pair 0 && buffer.GetRowByOffset(start.y - 1).WasWrapForced()) + { + --start.y; + } + // climb down to the last row that is wrapped + while (end.y + 1 < height && buffer.GetRowByOffset(end.y).WasWrapForced()) + { + ++end.y; + } start = { bufferSize.Left(), start.y }; end = { bufferSize.RightExclusive(), end.y }; break; diff --git a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp index 1585fef905..d59ea73617 100644 --- a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp @@ -549,6 +549,21 @@ namespace TerminalCoreUnitTests ValidateLinearSelection(term, { 0, 10 }, { term.GetViewport().RightExclusive(), 10 }); } + TEST_METHOD(TripleClick_WrappedLine) + { + Terminal term{ Terminal::TestDummyMarker{} }; + DummyRenderer renderer{ &term }; + term.Create({ 10, 5 }, 0, renderer); + term.Write(L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + + // Simulate click at (x,y) = (3,1) + auto clickPos = til::point{ 3, 1 }; + term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line); + + // Validate selection area + ValidateLinearSelection(term, { 0, 0 }, { term.GetViewport().RightExclusive(), 2 }); + } + TEST_METHOD(TripleClickDrag_Horizontal) { Terminal term{ Terminal::TestDummyMarker{} }; From 95d021ac877283c91d32c4ab22537c7e354ecebc Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Sat, 10 May 2025 04:13:18 +0500 Subject: [PATCH 040/177] config: Use Microsoft.Windows.Settings module for enabling developer mode (#18886) Using the Microsoft.Windows.Settings module is the now the recommended way of configuring Windows Settings, including developer mode. --- .config/configuration.vsEnterprise.winget | 4 ++-- .config/configuration.vsProfessional.winget | 4 ++-- .config/configuration.winget | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/configuration.vsEnterprise.winget b/.config/configuration.vsEnterprise.winget index cf7f657d11..589244063d 100644 --- a/.config/configuration.vsEnterprise.winget +++ b/.config/configuration.vsEnterprise.winget @@ -2,14 +2,14 @@ # Reference: https://github.com/microsoft/terminal/blob/main/README.md#developer-guidance properties: resources: - - resource: Microsoft.Windows.Developer/DeveloperMode + - resource: Microsoft.Windows.Settings/WindowsSettings directives: description: Enable Developer Mode allowPrerelease: true # Requires elevation for the set operation securityContext: elevated settings: - Ensure: Present + DeveloperMode: true - resource: Microsoft.WinGet.DSC/WinGetPackage id: powershell directives: diff --git a/.config/configuration.vsProfessional.winget b/.config/configuration.vsProfessional.winget index d0b7508f36..549b6227bb 100644 --- a/.config/configuration.vsProfessional.winget +++ b/.config/configuration.vsProfessional.winget @@ -2,14 +2,14 @@ # Reference: https://github.com/microsoft/terminal/blob/main/README.md#developer-guidance properties: resources: - - resource: Microsoft.Windows.Developer/DeveloperMode + - resource: Microsoft.Windows.Settings/WindowsSettings directives: description: Enable Developer Mode allowPrerelease: true # Requires elevation for the set operation securityContext: elevated settings: - Ensure: Present + DeveloperMode: true - resource: Microsoft.WinGet.DSC/WinGetPackage id: powershell directives: diff --git a/.config/configuration.winget b/.config/configuration.winget index 1dcd7cf829..5d7823a002 100644 --- a/.config/configuration.winget +++ b/.config/configuration.winget @@ -2,14 +2,14 @@ # Reference: https://github.com/microsoft/terminal/blob/main/README.md#developer-guidance properties: resources: - - resource: Microsoft.Windows.Developer/DeveloperMode + - resource: Microsoft.Windows.Settings/WindowsSettings directives: description: Enable Developer Mode allowPrerelease: true # Requires elevation for the set operation securityContext: elevated settings: - Ensure: Present + DeveloperMode: true - resource: Microsoft.WinGet.DSC/WinGetPackage id: powershell directives: From 545eaf258d8d7a66717ac2ac9b1a1e37b271ad29 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 12 May 2025 16:06:22 -0500 Subject: [PATCH 041/177] Update to TouchdownBuildTask v5 (#18896) This is mandatory. --- build/pipelines/daily-loc-submission.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pipelines/daily-loc-submission.yml b/build/pipelines/daily-loc-submission.yml index 403501ed32..7fddafa6cc 100644 --- a/build/pipelines/daily-loc-submission.yml +++ b/build/pipelines/daily-loc-submission.yml @@ -47,7 +47,7 @@ steps: git config --local core.autocrlf true displayName: Prepare git submission environment -- task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@3 +- task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@5 displayName: 'Touchdown Build - 7105, PRODEXT' inputs: teamId: 7105 From f08321a0b24bd6870c2f868e19e7565193b615a1 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 12 May 2025 20:30:18 -0500 Subject: [PATCH 042/177] build: fix the tsa config yet again (#18901) We ship the org chart, baby! --- build/config/tsa.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config/tsa.json b/build/config/tsa.json index f07bc0b535..61924ef4bd 100644 --- a/build/config/tsa.json +++ b/build/config/tsa.json @@ -1,6 +1,6 @@ { "instanceUrl": "https://microsoft.visualstudio.com", "projectName": "OS", - "areaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\Terminal", + "areaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SHINE\\Terminal", "notificationAliases": ["condev@microsoft.com", "duhowett@microsoft.com"] } From 064d9af46edce58436f1d2d4c028be1119533ced Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 13 May 2025 10:46:16 -0700 Subject: [PATCH 043/177] Bind "Clear buffer" action to Ctrl+Shift+K (#18900) Closes #17894 --- src/cascadia/TerminalSettingsModel/defaults.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index d8840bb0cd..d24c765de8 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -745,6 +745,7 @@ { "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" }, { "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" }, { "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" }, + { "keys": "ctrl+shift+k", "id": "Terminal.ClearBuffer" }, // Visual Adjustments { "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" }, From 07c9a99273f998e3c758324b4d038cf5532bc302 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 13 May 2025 18:53:13 +0100 Subject: [PATCH 044/177] Render SGR 1 as bold when used with ITU colors (#18903) The `SGR 1` VT attribute can either be interpreted as a brighter color, or as a bolder font, depending on the _Intense text style_ setting. However, the concept of brightness only applies to the eight standard ANSI colors, so when `SGR 1` is configured as _bright_, it has no effect on the ITU T.416 colors (RGB and the 256 index colors). To address that, we now interpret `SGR 1` as a bolder font when applied to ITU colors, regardless of whether the _Intense text style_ option is set to bold or not. Note that this only applies to the Atlas render engine, since the GDI engine doesn't support bold fonts. ## Validation Steps Performed I've manually tested `SGR 1` applied to different color formats with the _Intense text style_ option set to _None_, and confirmed that the text is now rendered with a bold font for ITU colors, but not for ANSI/AIX colors. Closes #18284 --- src/buffer/out/TextAttribute.cpp | 5 +++++ src/buffer/out/TextAttribute.hpp | 1 + src/buffer/out/textBuffer.cpp | 4 ++-- src/renderer/atlas/AtlasEngine.cpp | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index f95edf6bea..76604686be 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -232,6 +232,11 @@ void TextAttribute::SetRightVerticalDisplayed(const bool isDisplayed) noexcept WI_UpdateFlag(_attrs, CharacterAttributes::RightGridline, isDisplayed); } +bool TextAttribute::IsBold(const bool intenseIsBold) const noexcept +{ + return IsIntense() && (intenseIsBold || !_foreground.CanBeBrightened()); +} + bool TextAttribute::IsIntense() const noexcept { return WI_IsFlagSet(_attrs, CharacterAttributes::Intense); diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index b1d69d1f3b..bca0df878c 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -115,6 +115,7 @@ public: return memcmp(this, &other, sizeof(TextAttribute)) != 0; } + bool IsBold(const bool intenseIsBold) const noexcept; bool IsLegacy() const noexcept; bool IsIntense() const noexcept; bool IsFaint() const noexcept; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 7cd232ad53..095195133b 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2278,7 +2278,7 @@ std::string TextBuffer::GenHTML(const CopyRequest& req, fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("color:{};"), fgHex); fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("background-color:{};"), bgHex); - if (isIntenseBold && attr.IsIntense()) + if (attr.IsBold(isIntenseBold)) { htmlBuilder += "font-weight:bold;"; } @@ -2528,7 +2528,7 @@ std::string TextBuffer::GenRTF(const CopyRequest& req, fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\cf{}"), fgIdx); fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\chshdng0\\chcbpat{}"), bgIdx); - if (isIntenseBold && attr.IsIntense()) + if (attr.IsBold(isIntenseBold)) { contentBuilder += "\\b"; } diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 90b9083b5d..856b46f0fb 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -651,7 +651,7 @@ try if (!isSettingDefaultBrushes) { auto attributes = FontRelevantAttributes::None; - WI_SetFlagIf(attributes, FontRelevantAttributes::Bold, textAttributes.IsIntense() && renderSettings.GetRenderMode(RenderSettings::Mode::IntenseIsBold)); + WI_SetFlagIf(attributes, FontRelevantAttributes::Bold, textAttributes.IsBold(renderSettings.GetRenderMode(RenderSettings::Mode::IntenseIsBold))); WI_SetFlagIf(attributes, FontRelevantAttributes::Italic, textAttributes.IsItalic()); if (_api.attributes != attributes) From 7359df0382189ca2194236879eff24b12f7740ff Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 13 May 2025 13:39:22 -0700 Subject: [PATCH 045/177] Fix remaining settings container previews in SUI (#18888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Went through the settings UI and checked which other setting containers were missing a preview. Here's the list: - starting directory - tab title - background image - answerback message - bell style Adding them was fairly straightforward. Tried to reuse existing resources when possible. The general pattern was to add a "Current" or "Preview" getter that just created the human-readable format of the setting. ## Validation Steps Performed ✅ value is shown (including special values!) ✅ each of these work with a screen reader Closes #18576 --- .../TerminalSettingsEditor/Appearances.cpp | 20 ++++- .../TerminalSettingsEditor/Appearances.h | 5 +- .../TerminalSettingsEditor/Appearances.idl | 1 + .../TerminalSettingsEditor/Appearances.xaml | 6 +- .../ProfileViewModel.cpp | 80 ++++++++++++++++--- .../TerminalSettingsEditor/ProfileViewModel.h | 6 +- .../ProfileViewModel.idl | 7 +- .../Profiles_Advanced.xaml | 1 + .../TerminalSettingsEditor/Profiles_Base.xaml | 6 +- .../Profiles_Terminal.xaml | 2 +- .../Resources/en-US/Resources.resw | 8 ++ 11 files changed, 116 insertions(+), 26 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index 536217cc6a..030239b0a3 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -219,7 +219,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // into the path TextBox, we properly update the checkbox and stored // _lastBgImagePath. Without this, then we'll permanently hide the text // box, prevent it from ever being changed again. - _NotifyChanges(L"UseDesktopBGImage", L"BackgroundImageSettingsVisible"); + _NotifyChanges(L"UseDesktopBGImage", L"BackgroundImageSettingsVisible", L"CurrentBackgroundImagePath"); } else if (viewModelProperty == L"BackgroundImageAlignment") { @@ -954,7 +954,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return GetLibraryResourceString(alignmentResourceKey); } - bool AppearanceViewModel::UseDesktopBGImage() + hstring AppearanceViewModel::CurrentBackgroundImagePath() const + { + const auto bgImagePath = BackgroundImagePath(); + if (bgImagePath.empty()) + { + return RS_(L"Appearance_BackgroundImageNone"); + } + else if (bgImagePath == L"desktopWallpaper") + { + return RS_(L"Profile_UseDesktopImage/Content"); + } + return bgImagePath; + } + + bool AppearanceViewModel::UseDesktopBGImage() const { return BackgroundImagePath() == L"desktopWallpaper"; } @@ -983,7 +997,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - bool AppearanceViewModel::BackgroundImageSettingsVisible() + bool AppearanceViewModel::BackgroundImageSettingsVisible() const { return !BackgroundImagePath().empty(); } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index 989f278c56..e8b1cac6c3 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -120,9 +120,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void UpdateFontSetting(const FontKeyValuePair* kv); // background image - bool UseDesktopBGImage(); + hstring CurrentBackgroundImagePath() const; + bool UseDesktopBGImage() const; void UseDesktopBGImage(const bool useDesktop); - bool BackgroundImageSettingsVisible(); + bool BackgroundImageSettingsVisible() const; void SetBackgroundImageOpacityFromPercentageValue(double percentageValue); void SetBackgroundImagePath(winrt::hstring path); hstring BackgroundImageAlignmentCurrentValue() const; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index 89096d857a..aedb516473 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -38,6 +38,7 @@ namespace Microsoft.Terminal.Settings.Editor void SetBackgroundImageOpacityFromPercentageValue(Double percentageValue); void SetBackgroundImagePath(String path); + String CurrentBackgroundImagePath { get; }; Boolean UseDesktopBGImage; Boolean BackgroundImageSettingsVisible { get; }; String BackgroundImageAlignmentCurrentValue { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index d4cff69560..7c77717311 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -533,13 +533,13 @@ @@ -547,7 +547,7 @@ + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 054c8480cc..6e66fac02a 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -431,7 +431,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (clickedItemTag == compatibilityTag) { - contentFrame().Navigate(xaml_typename(), winrt::make(_settingsClone.GlobalSettings())); + contentFrame().Navigate(xaml_typename(), winrt::make(_settingsClone)); const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Compatibility/Content"), BreadcrumbSubPage::None); _breadcrumbs.Append(crumb); } diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 46f74160d5..e1e96da833 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -2355,4 +2355,37 @@ None Text displayed when the answerback message is not defined.
- \ No newline at end of file + + Reset to default settings + + + Clear cache + + + The cache stores data related to persisting sessions and automatic profile generation. + + + Reset + + + Clear + + + This action is applied immediately and cannot be undone. + + + This action is applied immediately and cannot be undone. + + + Are you sure you want to reset your settings? + + + Are you sure you want to clear your cache? + + + Yes, reset my settings + + + Yes, clear the cache + + diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index a6e50e370a..e650d7c589 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -127,6 +127,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IObservableVector AllProfiles() const noexcept; winrt::Windows::Foundation::Collections::IObservableVector ActiveProfiles() const noexcept; Model::ActionMap ActionMap() const noexcept; + void ResetApplicationState() const; + void ResetToDefaultSettings(); void WriteSettingsToDisk(); Json::Value ToJson() const; Model::Profile ProfileDefaults() const; @@ -162,6 +164,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::com_ptr _createNewProfile(const std::wstring_view& name) const; Model::Profile _getProfileForCommandLine(const winrt::hstring& commandLine) const; void _refreshDefaultTerminals(); + void _writeSettingsToDisk(std::string_view contents); void _resolveDefaultProfile() const; void _resolveNewTabMenuProfiles() const; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 2fa41941d6..863d2fa751 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -23,6 +23,8 @@ namespace Microsoft.Terminal.Settings.Model CascadiaSettings(String userJSON, String inboxJSON); CascadiaSettings Copy(); + void ResetApplicationState(); + void ResetToDefaultSettings(); void WriteSettingsToDisk(); void LogSettingChanges(Boolean isJsonLoad); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index ea9bda7616..5fdb390b5a 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -1345,6 +1345,21 @@ winrt::hstring CascadiaSettings::DefaultSettingsPath() return winrt::hstring{ path.native() }; } +void CascadiaSettings::ResetApplicationState() const +{ + auto state = ApplicationState::SharedInstance(); + const auto hash = state.SettingsHash(); + state.Reset(); + state.SettingsHash(hash); + state.Flush(); +} + +void CascadiaSettings::ResetToDefaultSettings() +{ + ApplicationState::SharedInstance().Reset(); + _writeSettingsToDisk(LoadStringResource(IDR_USER_DEFAULTS)); +} + // Method Description: // - Write the current state of CascadiaSettings to our settings file // - Create a backup file with the current contents, if one does not exist @@ -1355,19 +1370,21 @@ winrt::hstring CascadiaSettings::DefaultSettingsPath() // - void CascadiaSettings::WriteSettingsToDisk() { - const auto settingsPath = _settingsPath(); - // write current settings to current settings file Json::StreamWriterBuilder wbuilder; wbuilder.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons wbuilder.settings_["indentation"] = " "; wbuilder.settings_["precision"] = 6; // prevent values like 1.1000000000000001 - FILETIME lastWriteTime{}; - const auto styledString{ Json::writeString(wbuilder, ToJson()) }; - til::io::write_utf8_string_to_file_atomic(settingsPath, styledString, &lastWriteTime); + _writeSettingsToDisk(Json::writeString(wbuilder, ToJson())); +} - _hash = _calculateHash(styledString, lastWriteTime); +void CascadiaSettings::_writeSettingsToDisk(std::string_view contents) +{ + FILETIME lastWriteTime{}; + til::io::write_utf8_string_to_file_atomic(_settingsPath(), contents, &lastWriteTime); + + _hash = _calculateHash(contents, lastWriteTime); // Persists the default terminal choice // GH#10003 - Only do this if _currentDefaultTerminal was actually initialized. From f769597d8914fbff88db3d72cff1d7b89334d4a8 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 14 May 2025 11:08:07 -0700 Subject: [PATCH 054/177] Persist window layout every few minutes (#18898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If `persistedWindowLayout` is enabled, we now persist the window layout every few minutes (excluding the text buffer). This was done by adding a `SafeDispatcherTimer` to the `WindowEmperor` that calls `PersistState()` every 5 minutes. For `BuildStartupKind`, I split up `Persist` into `PersistAll` and `PersistLayout`. This way, we go through all the same code flow that `Persist` had except for specifically serializing the buffer. ## Validation Steps Performed ✅ (with the timer set to 3 seconds) create a window layout and ensure the layout is restored after forcefully stopping Terminal (aka simulating a "crash") Closes #18838 --- src/cascadia/TerminalApp/IPaneContent.idl | 3 +- src/cascadia/TerminalApp/TerminalPage.cpp | 4 +-- src/cascadia/TerminalApp/TerminalPage.h | 2 +- .../TerminalApp/TerminalPaneContent.cpp | 3 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 4 +-- src/cascadia/TerminalApp/TerminalWindow.h | 2 +- src/cascadia/TerminalApp/TerminalWindow.idl | 2 +- .../WindowsTerminal/WindowEmperor.cpp | 29 ++++++++++++++++--- src/cascadia/WindowsTerminal/WindowEmperor.h | 3 ++ 9 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl index f4c6ce1395..ea2e900ca6 100644 --- a/src/cascadia/TerminalApp/IPaneContent.idl +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -8,7 +8,8 @@ namespace TerminalApp None, Content, MovePane, - Persist, + PersistLayout, + PersistAll }; runtimeclass BellEventArgs diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 581b0ddcf3..135e7cca94 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1953,7 +1953,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalPage::PersistState() + void TerminalPage::PersistState(bool serializeBuffer) { // This method may be called for a window even if it hasn't had a tab yet or lost all of them. // We shouldn't persist such windows. @@ -1968,7 +1968,7 @@ namespace winrt::TerminalApp::implementation for (auto tab : _tabs) { auto t = winrt::get_self(tab); - auto tabActions = t->BuildStartupActions(BuildStartupKind::Persist); + auto tabActions = t->BuildStartupActions(serializeBuffer ? BuildStartupKind::PersistAll : BuildStartupKind::PersistLayout); actions.insert(actions.end(), std::make_move_iterator(tabActions.begin()), std::make_move_iterator(tabActions.end())); } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 9837f9fe61..b369bd920e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -113,7 +113,7 @@ namespace winrt::TerminalApp::implementation safe_void_coroutine RequestQuit(); safe_void_coroutine CloseWindow(); - void PersistState(); + void PersistState(bool serializeBuffer); void ToggleFocusMode(); void ToggleFullscreen(); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 1ab5cd013c..c85009ea0c 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -141,7 +141,7 @@ namespace winrt::TerminalApp::implementation // "attach existing" rather than a "create" args.ContentId(_control.ContentId()); break; - case BuildStartupKind::Persist: + case BuildStartupKind::PersistAll: { const auto connection = _control.Connection(); const auto id = connection ? connection.SessionId() : winrt::guid{}; @@ -156,6 +156,7 @@ namespace winrt::TerminalApp::implementation } break; } + case BuildStartupKind::PersistLayout: default: break; } diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 9776679691..e1fd1741d1 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -266,11 +266,11 @@ namespace winrt::TerminalApp::implementation AppLogic::Current()->NotifyRootInitialized(); } - void TerminalWindow::PersistState() + void TerminalWindow::PersistState(bool serializeBuffer) { if (_root) { - _root->PersistState(); + _root->PersistState(serializeBuffer); } } diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 6e37e17947..fd27617846 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -71,7 +71,7 @@ namespace winrt::TerminalApp::implementation void Create(); - void PersistState(); + void PersistState(bool serializeBuffer); void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index a8fa0c97c5..baf58317df 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -59,7 +59,7 @@ namespace TerminalApp Boolean ShouldImmediatelyHandoffToElevated(); void HandoffToElevated(); - void PersistState(); + void PersistState(Boolean serializeBuffer); Windows.UI.Xaml.UIElement GetRoot(); diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 381b7f4c8e..cdf34d9a3b 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -314,6 +314,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) _createMessageWindow(windowClassName.c_str()); _setupGlobalHotkeys(); _checkWindowsForNotificationIcon(); + _setupSessionPersistence(_app.Logic().Settings().GlobalSettings().ShouldUsePersistedLayout()); // When the settings change, we'll want to update our global hotkeys // and our notification icon based on the new settings. @@ -323,6 +324,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) _assertIsMainThread(); _setupGlobalHotkeys(); _checkWindowsForNotificationIcon(); + _setupSessionPersistence(args.NewSettings().GlobalSettings().ShouldUsePersistedLayout()); } }); @@ -921,10 +923,22 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c return DefWindowProcW(window, message, wParam, lParam); } -void WindowEmperor::_finalizeSessionPersistence() const +void WindowEmperor::_setupSessionPersistence(bool enabled) { - const auto state = ApplicationState::SharedInstance(); + if (!enabled) + { + _persistStateTimer.Stop(); + return; + } + _persistStateTimer.Interval(std::chrono::minutes(5)); + _persistStateTimer.Tick([&](auto&&, auto&&) { + _persistState(ApplicationState::SharedInstance(), false); + }); + _persistStateTimer.Start(); +} +void WindowEmperor::_persistState(const ApplicationState& state, bool serializeBuffer) const +{ // Calling an `ApplicationState` setter triggers a write to state.json. // With this if condition we avoid an unnecessary write when persistence is disabled. if (state.PersistedWindowLayouts()) @@ -936,12 +950,19 @@ void WindowEmperor::_finalizeSessionPersistence() const { for (const auto& w : _windows) { - w->Logic().PersistState(); + w->Logic().PersistState(serializeBuffer); } } - // Ensure to write the state.json before we TerminateProcess() + // Ensure to write the state.json state.Flush(); +} + +void WindowEmperor::_finalizeSessionPersistence() const +{ + const auto state = ApplicationState::SharedInstance(); + + _persistState(state, true); if (_needsPersistenceCleanup) { diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 3024ab7a2c..bf63214030 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -64,6 +64,8 @@ private: void _registerHotKey(int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept; void _unregisterHotKey(int index) noexcept; void _setupGlobalHotkeys(); + void _setupSessionPersistence(bool enabled); + void _persistState(const winrt::Microsoft::Terminal::Settings::Model::ApplicationState& state, bool serializeBuffer) const; void _finalizeSessionPersistence() const; void _checkWindowsForNotificationIcon(); @@ -77,6 +79,7 @@ private: bool _notificationIconShown = false; bool _forcePersistence = false; bool _needsPersistenceCleanup = false; + SafeDispatcherTimer _persistStateTimer; std::optional _currentSystemThemeIsDark; int32_t _windowCount = 0; int32_t _messageBoxCount = 0; From 076746a7a62197111981ca706eac9ff948286133 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 14 May 2025 21:47:23 +0200 Subject: [PATCH 055/177] Implement custom text context menus to fix crashes (#18854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This works around a bug in WinUI where it creates a single context menu/flyout for text elements per thread, not per `XamlRoot`, similar to many other areas. Since the `XamlRoot` cannot change after creation, this means that once you've opened the flyout, you're locked into that window (= XAML root) forever. You can't open the flyout in another window and once you've closed that window, you can't open it anywhere at all. Closes #18599 ## Validation Steps Performed * Flies out right click in the * About dialog ✅ * Search dialog ✅ * Word delimiters setting ✅ * Launch size setting ✅ * Across two windows ✅ --- .github/actions/spelling/expect/expect.txt | 3 +- src/cascadia/TerminalApp/AboutDialog.xaml | 7 +- src/cascadia/TerminalApp/CommandPalette.xaml | 6 +- .../TerminalApp/MarkdownPaneContent.cpp | 1 + .../TerminalApp/MarkdownPaneContent.xaml | 13 +- .../TerminalApp/ScratchpadContent.cpp | 1 + .../TerminalApp/SnippetsPaneContent.xaml | 7 +- .../TerminalApp/SuggestionsControl.xaml | 18 +- .../TerminalApp/TabHeaderControl.xaml | 7 +- src/cascadia/TerminalApp/TabRowControl.xaml | 3 +- src/cascadia/TerminalApp/TerminalPage.xaml | 12 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 2 + .../TerminalControl/SearchBoxControl.xaml | 7 +- src/cascadia/TerminalControl/TermControl.xaml | 4 + .../CommonResources.xaml | 5 + .../UIHelpers/Resources/en-US/Resources.resw | 136 ++++++++++++ src/cascadia/UIHelpers/TextMenuFlyout.cpp | 204 ++++++++++++++++++ src/cascadia/UIHelpers/TextMenuFlyout.h | 43 ++++ src/cascadia/UIHelpers/TextMenuFlyout.idl | 7 + src/cascadia/UIHelpers/UIHelpers.vcxproj | 16 +- .../UIHelpers/UIHelpers.vcxproj.filters | 10 + src/cascadia/UIHelpers/init.cpp | 6 +- src/cascadia/UIHelpers/pch.h | 7 +- src/cascadia/UIMarkdown/CodeBlock.xaml | 7 +- src/cascadia/UIMarkdown/MarkdownToXaml.cpp | 2 + src/cascadia/UIMarkdown/UIMarkdown.vcxproj | 9 +- .../UIMarkdown/UIMarkdown.vcxproj.filters | 8 +- src/cascadia/UIMarkdown/pch.h | 6 +- 28 files changed, 521 insertions(+), 36 deletions(-) create mode 100644 src/cascadia/UIHelpers/Resources/en-US/Resources.resw create mode 100644 src/cascadia/UIHelpers/TextMenuFlyout.cpp create mode 100644 src/cascadia/UIHelpers/TextMenuFlyout.h create mode 100644 src/cascadia/UIHelpers/TextMenuFlyout.idl diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index f80ab40840..cc53f71e5f 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -128,6 +128,7 @@ Blt blu BLUESCROLL bmi +bodgy BODGY BOLDFONT Borland @@ -371,8 +372,8 @@ Dcd DColor dcommon DComposition -dde DDDCCC +dde DDESHARE DDevice DEADCHAR diff --git a/src/cascadia/TerminalApp/AboutDialog.xaml b/src/cascadia/TerminalApp/AboutDialog.xaml index a4257ec904..b9aebf9a9b 100644 --- a/src/cascadia/TerminalApp/AboutDialog.xaml +++ b/src/cascadia/TerminalApp/AboutDialog.xaml @@ -8,6 +8,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:TerminalApp" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:mtu="using:Microsoft.Terminal.UI" xmlns:mux="using:Microsoft.UI.Xaml.Controls" x:Uid="AboutDialog" DefaultButton="Close" @@ -17,6 +18,9 @@ + + + @@ -39,7 +43,7 @@ VerticalAlignment="Center" Orientation="Vertical" Visibility="{x:Bind UpdatesAvailable, Mode=OneWay}"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cut + "Cut" as contained in a Cut/Copy/Paste menu + + + Copy + "Copy" as contained in a Cut/Copy/Paste menu + + + Paste + "Paste" as contained in a Cut/Copy/Paste menu + + + Select All + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/TextMenuFlyout.cpp b/src/cascadia/UIHelpers/TextMenuFlyout.cpp new file mode 100644 index 0000000000..57dbc486f9 --- /dev/null +++ b/src/cascadia/UIHelpers/TextMenuFlyout.cpp @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TextMenuFlyout.h" + +#include + +#include "TextMenuFlyout.g.cpp" + +using namespace ::winrt::Windows::UI::Xaml; +using namespace ::winrt::Windows::UI::Xaml::Controls; +using namespace ::winrt::Windows::ApplicationModel::Resources::Core; +using namespace ::winrt::Microsoft::UI::Xaml::Controls; +using namespace ::winrt::Windows::System; +using namespace ::winrt::Windows::UI::Xaml::Input; + +using MenuFlyoutItemClick = void (*)(IInspectable const&, RoutedEventArgs const&); + +constexpr auto NullSymbol = static_cast(0); + +namespace winrt::Microsoft::Terminal::UI::implementation +{ +#pragma warning(suppress : 26455) // Default constructor should not throw. Declare it 'noexcept' (f.6). + TextMenuFlyout::TextMenuFlyout() + { + // Most of the initialization is delayed until the first call to MenuFlyout_Opening. + Opening({ this, &TextMenuFlyout::MenuFlyout_Opening }); + } + + void TextMenuFlyout::MenuFlyout_Opening(IInspectable const&, IInspectable const&) + { + auto target = Target(); + if (!target) + { + return; + } + hstring selection; + bool writable = false; + + // > Common group of selectable controls with common actions + // > The I in MIDL stands for... + // No common interface. + if (const auto box = target.try_as()) + { + target = box.GetTemplateChild(L"InputBox").as(); + } + if (const auto control = target.try_as()) + { + selection = control.SelectedText(); + } + else if (const auto control = target.try_as()) + { + selection = control.SelectedText(); + writable = true; + } + else if (const auto control = target.try_as()) + { + selection = control.SelectedText(); + } + + if (!_copy) + { + til::small_vector items; + + if (writable) + { + _cut = items.emplace_back(_createMenuItem(Symbol::Cut, RS_(L"Cut"), { this, &TextMenuFlyout::Cut_Click }, VirtualKeyModifiers::Control, VirtualKey::X)); + } + _copy = items.emplace_back(_createMenuItem(Symbol::Copy, RS_(L"Copy"), { this, &TextMenuFlyout::Copy_Click }, VirtualKeyModifiers::Control, VirtualKey::C)); + if (writable) + { + items.emplace_back(_createMenuItem(Symbol::Paste, RS_(L"Paste"), { this, &TextMenuFlyout::Paste_Click }, VirtualKeyModifiers::Control, VirtualKey::V)); + } + items.emplace_back(_createMenuItem(NullSymbol, RS_(L"SelectAll"), { this, &TextMenuFlyout::SelectAll_Click }, VirtualKeyModifiers::Control, VirtualKey::A)); + + Items().ReplaceAll({ items.data(), gsl::narrow_cast(items.size()) }); + } + + const auto visibilityOfItemsRequiringSelection = selection.empty() ? Visibility::Collapsed : Visibility::Visible; + if (_cut) + { + _cut.Visibility(visibilityOfItemsRequiringSelection); + } + _copy.Visibility(visibilityOfItemsRequiringSelection); + } + + void TextMenuFlyout::Cut_Click(IInspectable const&, RoutedEventArgs const&) + { + // NOTE: When the flyout closes, WinUI doesn't disconnect the accelerator keys. + // Since that means we'll get Ctrl+X/C/V callbacks forever, just ignore them. + // The TextBox will still handle those events... + auto target = Target(); + if (!target) + { + return; + } + + if (const auto box = target.try_as()) + { + target = box.GetTemplateChild(L"InputBox").as(); + } + if (const auto control = target.try_as()) + { + control.CutSelectionToClipboard(); + } + } + + void TextMenuFlyout::Copy_Click(IInspectable const&, RoutedEventArgs const&) + { + auto target = Target(); + if (!target) + { + return; + } + + if (const auto box = target.try_as()) + { + target = box.GetTemplateChild(L"InputBox").as(); + } + if (const auto control = target.try_as()) + { + control.CopySelectionToClipboard(); + } + else if (const auto control = target.try_as()) + { + control.CopySelectionToClipboard(); + } + else if (const auto control = target.try_as()) + { + control.CopySelectionToClipboard(); + } + } + + void TextMenuFlyout::Paste_Click(IInspectable const&, RoutedEventArgs const&) + { + auto target = Target(); + if (!target) + { + return; + } + + if (const auto box = target.try_as()) + { + target = box.GetTemplateChild(L"InputBox").as(); + } + if (const auto control = target.try_as()) + { + control.PasteFromClipboard(); + } + } + + void TextMenuFlyout::SelectAll_Click(IInspectable const&, RoutedEventArgs const&) + { + // BODGY: + // Once the flyout was open once, we'll get Ctrl+A events and the TextBox will + // ignore them. As such, we have to dig out the focused element as a fallback, + // because otherwise Ctrl+A will be permanently broken. Put differently, + // this is bodgy because WinUI 2.8 is buggy. There's no other solution here. + IInspectable target = Target(); + if (!target) + { + target = FocusManager::GetFocusedElement(XamlRoot()); + if (!target) + { + return; + } + } + + if (const auto box = target.try_as()) + { + target = box.GetTemplateChild(L"InputBox").as(); + } + if (const auto control = target.try_as()) + { + control.SelectAll(); + } + else if (const auto control = target.try_as()) + { + control.SelectAll(); + } + else if (const auto control = target.try_as()) + { + control.SelectAll(); + } + } + + MenuFlyoutItemBase TextMenuFlyout::_createMenuItem(Symbol symbol, hstring text, RoutedEventHandler click, VirtualKeyModifiers modifiers, VirtualKey key) + { + KeyboardAccelerator accel; + accel.Modifiers(modifiers); + accel.Key(key); + + MenuFlyoutItem item; + if (symbol != NullSymbol) + { + item.Icon(SymbolIcon{ std::move(symbol) }); + } + item.Text(std::move(text)); + item.Click(std::move(click)); + item.KeyboardAccelerators().Append(std::move(accel)); + return item; + } +} diff --git a/src/cascadia/UIHelpers/TextMenuFlyout.h b/src/cascadia/UIHelpers/TextMenuFlyout.h new file mode 100644 index 0000000000..87a6d9a3f9 --- /dev/null +++ b/src/cascadia/UIHelpers/TextMenuFlyout.h @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "TextMenuFlyout.g.h" + +namespace winrt::Microsoft::Terminal::UI::implementation +{ + // This custom flyout exists because WinUI 2 only supports 1 text block flyout + // *per thread* not per window. If you have >1 window per 1 thread, as we do, + // the focus will just be delegated to the window the flyout was first opened in. + // Once the first window is gone, WinUI will either do nothing or crash. + // See: GH#18599 + struct TextMenuFlyout : TextMenuFlyoutT + { + TextMenuFlyout(); + + void MenuFlyout_Opening(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& e); + void Cut_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void Copy_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void Paste_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void SelectAll_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + private: + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase _createMenuItem( + winrt::Windows::UI::Xaml::Controls::Symbol symbol, + winrt::hstring text, + winrt::Windows::UI::Xaml::RoutedEventHandler click, + winrt::Windows::System::VirtualKeyModifiers modifiers, + winrt::Windows::System::VirtualKey key); + + // These are always present. + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase _copy{ nullptr }; + // These are only set for writable controls. + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase _cut{ nullptr }; + }; +} + +namespace winrt::Microsoft::Terminal::UI::factory_implementation +{ + BASIC_FACTORY(TextMenuFlyout); +} diff --git a/src/cascadia/UIHelpers/TextMenuFlyout.idl b/src/cascadia/UIHelpers/TextMenuFlyout.idl new file mode 100644 index 0000000000..c43234db29 --- /dev/null +++ b/src/cascadia/UIHelpers/TextMenuFlyout.idl @@ -0,0 +1,7 @@ +namespace Microsoft.Terminal.UI +{ + [default_interface] runtimeclass TextMenuFlyout : Windows.UI.Xaml.Controls.MenuFlyout + { + TextMenuFlyout(); + } +} diff --git a/src/cascadia/UIHelpers/UIHelpers.vcxproj b/src/cascadia/UIHelpers/UIHelpers.vcxproj index 033c511413..eaae5b75ac 100644 --- a/src/cascadia/UIHelpers/UIHelpers.vcxproj +++ b/src/cascadia/UIHelpers/UIHelpers.vcxproj @@ -26,6 +26,9 @@ ResourceString.idl + + TextMenuFlyout.idl + @@ -41,6 +44,9 @@ ResourceString.idl + + TextMenuFlyout.idl + @@ -48,6 +54,13 @@ + + + + + Designer + + @@ -64,15 +77,12 @@ false - %(AdditionalDependencies);user32.lib;shell32.lib - - diff --git a/src/cascadia/UIHelpers/UIHelpers.vcxproj.filters b/src/cascadia/UIHelpers/UIHelpers.vcxproj.filters index 43bbd04bf0..637a4b0e1a 100644 --- a/src/cascadia/UIHelpers/UIHelpers.vcxproj.filters +++ b/src/cascadia/UIHelpers/UIHelpers.vcxproj.filters @@ -31,8 +31,18 @@ + + + + + + + + + Resources\en-US + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/init.cpp b/src/cascadia/UIHelpers/init.cpp index 689f4b67f6..6a22285bac 100644 --- a/src/cascadia/UIHelpers/init.cpp +++ b/src/cascadia/UIHelpers/init.cpp @@ -3,6 +3,8 @@ #include "pch.h" +#include + #pragma warning(suppress : 26440) // Not interested in changing the specification of DllMain to make it noexcept given it's an interface to the OS. BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) { @@ -11,11 +13,11 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hInstDll); break; - case DLL_PROCESS_DETACH: - break; default: break; } return TRUE; } + +UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"Microsoft.Terminal.UI/Resources") diff --git a/src/cascadia/UIHelpers/pch.h b/src/cascadia/UIHelpers/pch.h index 632428fe4e..5d22e5e502 100644 --- a/src/cascadia/UIHelpers/pch.h +++ b/src/cascadia/UIHelpers/pch.h @@ -25,16 +25,15 @@ #include -#include -#include +#include +#include #include #include -#include #include #include -#include +#include #include #include diff --git a/src/cascadia/UIMarkdown/CodeBlock.xaml b/src/cascadia/UIMarkdown/CodeBlock.xaml index 009c965458..38fea69ab8 100644 --- a/src/cascadia/UIMarkdown/CodeBlock.xaml +++ b/src/cascadia/UIMarkdown/CodeBlock.xaml @@ -8,6 +8,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Microsoft.Terminal.UI.Markdown" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:mtu="using:Microsoft.Terminal.UI" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" @@ -201,7 +202,11 @@ VerticalAlignment="Stretch"> + Text="{x:Bind Commandlines}"> + + + + diff --git a/src/cascadia/UIMarkdown/MarkdownToXaml.cpp b/src/cascadia/UIMarkdown/MarkdownToXaml.cpp index 81e70155f2..eb515898b2 100644 --- a/src/cascadia/UIMarkdown/MarkdownToXaml.cpp +++ b/src/cascadia/UIMarkdown/MarkdownToXaml.cpp @@ -72,6 +72,7 @@ WUX::Controls::RichTextBlock MarkdownToXaml::Convert(std::string_view markdownTe MarkdownToXaml::MarkdownToXaml(const winrt::hstring& baseUrl) : _baseUri{ baseUrl } { + _root.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{}); _root.IsTextSelectionEnabled(true); _root.TextWrapping(WUX::TextWrapping::WrapWholeWords); } @@ -155,6 +156,7 @@ void MarkdownToXaml::_EndParagraph() noexcept WUX::Controls::TextBlock MarkdownToXaml::_makeDefaultTextBlock() { WUX::Controls::TextBlock b{}; + b.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{}); b.IsTextSelectionEnabled(true); b.TextWrapping(WUX::TextWrapping::WrapWholeWords); return b; diff --git a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj index 66d61d3ad7..c23ec98a87 100644 --- a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj +++ b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj @@ -7,7 +7,6 @@ DynamicLibrary Console true - 4 nested - true @@ -78,10 +76,11 @@ {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} false + + {6515f03f-e56d-4db4-b23d-ac4fb80db36f} + - - - + \ No newline at end of file diff --git a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj.filters b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj.filters index ccbde7f1f8..89ab02dcde 100644 --- a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj.filters +++ b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj.filters @@ -25,14 +25,20 @@ Module + + + - + + + + \ No newline at end of file diff --git a/src/cascadia/UIMarkdown/pch.h b/src/cascadia/UIMarkdown/pch.h index aa6b7fa485..562900ebb9 100644 --- a/src/cascadia/UIMarkdown/pch.h +++ b/src/cascadia/UIMarkdown/pch.h @@ -25,21 +25,19 @@ #include -#include #include #include #include -#include -#include #include #include #include #include -#include #include +#include + // Manually include til after we include Windows.Foundation to give it winrt superpowers #include "til.h" From dd96ce4b8fe07d95127182aeec75e9fc7516687a Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 14 May 2025 14:47:01 -0700 Subject: [PATCH 056/177] Apply localization and documentation fixes for regex code (#18914) Applies some localization fixes for the regex strings and Settings_ResetApplicationState.HelpText. Also renames the `_validateX()` functions to `_validateAndPopulateXRegex()` for clarity. --- .../Resources/en-US/Resources.resw | 2 +- .../Resources/en-US/Resources.resw | 10 ++--- .../MatchProfilesEntry.cpp | 38 +++++++++---------- .../MatchProfilesEntry.h | 34 ++++++++--------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 79a9155134..fb3f8a476d 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -945,6 +945,6 @@ Move right - An invalid regex was found. + An invalid regular expression was found. \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index e1e96da833..0396faf3bc 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -2105,7 +2105,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Add a group of profiles that match at least one of the defined regex properties + Add a group of profiles that match at least one of the defined regular expression properties Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2121,15 +2121,15 @@ Header for a control that adds a folder to the new tab menu. - Profile name (Regex) + Profile name (regular expression) Header for a text box used to define a regex for the names of profiles to add. - Profile source (Regex) + Profile source (regular expression) Header for a text box used to define a regex for the sources of profiles to add. - Commandline (Regex) + Commandline (regular expression) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2362,7 +2362,7 @@ Clear cache - The cache stores data related to persisting sessions and automatic profile generation. + The cache stores data related to saved sessions and automatically generated profiles. Reset diff --git a/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.cpp b/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.cpp index 5dc4021954..dcb3057727 100644 --- a/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.cpp +++ b/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.cpp @@ -36,13 +36,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto entry = winrt::make_self(); JsonUtils::GetValueForKey(json, NameKey, entry->_Name); - entry->_validateName(); + entry->_validateAndPopulateNameRegex(); JsonUtils::GetValueForKey(json, CommandlineKey, entry->_Commandline); - entry->_validateCommandline(); + entry->_validateAndPopulateCommandlineRegex(); JsonUtils::GetValueForKey(json, SourceKey, entry->_Source); - entry->_validateSource(); + entry->_validateAndPopulateSourceRegex(); return entry; } @@ -53,22 +53,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return !(_invalidName || _invalidCommandline || _invalidSource); } -#define DEFINE_VALIDATE_FUNCTION(name) \ - void MatchProfilesEntry::_validate##name() noexcept \ - { \ - _invalid##name = false; \ - if (_##name.empty()) \ - { \ - /* empty field is valid*/ \ - return; \ - } \ - UErrorCode status = U_ZERO_ERROR; \ - _##name##Regex = til::ICU::CreateRegex(_##name, 0, &status); \ - if (U_FAILURE(status)) \ - { \ - _invalid##name = true; \ - _##name##Regex.reset(); \ - } \ +#define DEFINE_VALIDATE_FUNCTION(name) \ + void MatchProfilesEntry::_validateAndPopulate##name##Regex() noexcept \ + { \ + _invalid##name = false; \ + if (_##name.empty()) \ + { \ + /* empty field is valid*/ \ + return; \ + } \ + UErrorCode status = U_ZERO_ERROR; \ + _##name##Regex = til::ICU::CreateRegex(_##name, 0, &status); \ + if (U_FAILURE(status)) \ + { \ + _invalid##name = true; \ + _##name##Regex.reset(); \ + } \ } DEFINE_VALIDATE_FUNCTION(Name); diff --git a/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.h b/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.h index 8849291bbf..b16caa4b08 100644 --- a/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.h +++ b/src/cascadia/TerminalSettingsModel/MatchProfilesEntry.h @@ -23,23 +23,23 @@ Author(s): // The setter tries to instantiate the regex immediately and caches // it if successful. If it fails, it sets a boolean flag to track that // it failed. -#define DEFINE_MATCH_PROFILE_REGEX_PROPERTY(name) \ -public: \ - hstring name() const noexcept \ - { \ - return _##name; \ - } \ - void name(const hstring& value) noexcept \ - { \ - _##name = value; \ - _validate##name(); \ - } \ - \ -private: \ - void _validate##name() noexcept; \ - \ - hstring _##name; \ - til::ICU::unique_uregex _##name##Regex; \ +#define DEFINE_MATCH_PROFILE_REGEX_PROPERTY(name) \ +public: \ + hstring name() const noexcept \ + { \ + return _##name; \ + } \ + void name(const hstring& value) noexcept \ + { \ + _##name = value; \ + _validateAndPopulate##name##Regex(); \ + } \ + \ +private: \ + void _validateAndPopulate##name##Regex() noexcept; \ + \ + hstring _##name; \ + til::ICU::unique_uregex _##name##Regex; \ bool _invalid##name{ false }; namespace winrt::Microsoft::Terminal::Settings::Model::implementation From a093ca3d54ff00d79f00dc4dbd0e4f63ad96aa46 Mon Sep 17 00:00:00 2001 From: AgentK Date: Fri, 16 May 2025 01:56:58 +0300 Subject: [PATCH 057/177] Updated pane context menu (#18126) ## Motivation The motivation is that Windows users are more accustomed to working with GUI Menus using a mouse, unlike Linux users. ## Summary of the Pull Request added split pane with profile or duplicate up/down/left/right context menus as submenu added swap panes up/down/left/right context menus as submenu added toggle pane zoom context menu added close other panes context menu ## References : - Relevant Issues : (#18137) ## Type of change : - [x] New feature --------- Co-authored-by: Mike Griese --- .../Resources/en-US/Resources.resw | 39 +++++ src/cascadia/TerminalApp/TerminalPage.cpp | 147 ++++++++++++++++-- src/cascadia/TerminalApp/TerminalTab.h | 1 + .../Resources/en-US/Resources.resw | 10 +- src/cascadia/TerminalControl/TermControl.xaml | 12 +- .../TerminalControlLib.vcxproj | 13 +- 6 files changed, 198 insertions(+), 24 deletions(-) diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index fb3f8a476d..9ff10d00f4 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -214,6 +214,45 @@ Split pane + + Right click for split directions - right/down/up/left + + + Split pane down + + + Split pane right + + + Split pane up + + + Split pane left + + + Duplicate + + + Swap pane + + + Swap pane down + + + Swap pane right + + + Swap pane up + + + Swap pane left + + + Toggle pane zoom + + + Close other panes + Web search diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 135e7cca94..e72722f042 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -4988,9 +4988,10 @@ namespace winrt::TerminalApp::implementation }; }; - auto makeItem = [&menu, &makeCallback](const winrt::hstring& label, - const winrt::hstring& icon, - const auto& action) { + auto makeItem = [&makeCallback](const winrt::hstring& label, + const winrt::hstring& icon, + const auto& action, + auto& targetMenu) { AppBarButton button{}; if (!icon.empty()) @@ -5002,34 +5003,160 @@ namespace winrt::TerminalApp::implementation button.Label(label); button.Click(makeCallback(action)); - menu.SecondaryCommands().Append(button); + targetMenu.SecondaryCommands().Append(button); }; + auto makeMenuItem = [](const winrt::hstring& label, + const winrt::hstring& icon, + const auto& subMenu, + auto& targetMenu) { + AppBarButton button{}; + + if (!icon.empty()) + { + auto iconElement = UI::IconPathConverter::IconWUX(icon); + Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw); + button.Icon(iconElement); + } + + button.Label(label); + button.Flyout(subMenu); + targetMenu.SecondaryCommands().Append(button); + }; + + auto makeContextItem = [&makeCallback](const winrt::hstring& label, + const winrt::hstring& icon, + const winrt::hstring& tooltip, + const auto& action, + const auto& subMenu, + auto& targetMenu) { + AppBarButton button{}; + + if (!icon.empty()) + { + auto iconElement = UI::IconPathConverter::IconWUX(icon); + Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw); + button.Icon(iconElement); + } + + button.Label(label); + button.Click(makeCallback(action)); + WUX::Controls::ToolTipService::SetToolTip(button, box_value(tooltip)); + button.ContextFlyout(subMenu); + targetMenu.SecondaryCommands().Append(button); + }; + + const auto focusedProfile = _GetFocusedTabImpl()->GetFocusedProfile(); + auto separatorItem = AppBarSeparator{}; + auto activeProfiles = _settings.ActiveProfiles(); + auto activeProfileCount = gsl::narrow_cast(activeProfiles.Size()); + MUX::Controls::CommandBarFlyout splitPaneMenu{}; + // Wire up each item to the action that should be performed. By actually // connecting these to actions, we ensure the implementation is // consistent. This also leaves room for customizing this menu with // actions in the future. - makeItem(RS_(L"SplitPaneText"), L"\xF246", ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate } }); - makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr }); + makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr }, menu); + + const auto focusedProfileName = focusedProfile.Name(); + const auto focusedProfileIcon = focusedProfile.Icon(); + const auto splitPaneDuplicateText = RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName; // SplitPaneDuplicateText + + const auto splitPaneRightText = RS_(L"SplitPaneRightText"); + const auto splitPaneDownText = RS_(L"SplitPaneDownText"); + const auto splitPaneUpText = RS_(L"SplitPaneUpText"); + const auto splitPaneLeftText = RS_(L"SplitPaneLeftText"); + const auto splitPaneToolTipText = RS_(L"SplitPaneToolTipText"); + + MUX::Controls::CommandBarFlyout splitPaneContextMenu{}; + makeItem(splitPaneRightText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Right, .5, nullptr } }, splitPaneContextMenu); + makeItem(splitPaneDownText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Down, .5, nullptr } }, splitPaneContextMenu); + makeItem(splitPaneUpText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Up, .5, nullptr } }, splitPaneContextMenu); + makeItem(splitPaneLeftText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Left, .5, nullptr } }, splitPaneContextMenu); + + makeContextItem(splitPaneDuplicateText, focusedProfileIcon, splitPaneToolTipText, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Automatic, .5, nullptr } }, splitPaneContextMenu, splitPaneMenu); + + // add menu separator + const auto separatorAutoItem = AppBarSeparator{}; + + splitPaneMenu.SecondaryCommands().Append(separatorAutoItem); + + for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++) + { + const auto profile = activeProfiles.GetAt(profileIndex); + const auto profileName = profile.Name(); + const auto profileIcon = profile.Icon(); + + NewTerminalArgs args{}; + args.Profile(profileName); + + MUX::Controls::CommandBarFlyout splitPaneContextMenu{}; + makeItem(splitPaneRightText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Right, .5, args } }, splitPaneContextMenu); + makeItem(splitPaneDownText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Down, .5, args } }, splitPaneContextMenu); + makeItem(splitPaneUpText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Up, .5, args } }, splitPaneContextMenu); + makeItem(splitPaneLeftText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Left, .5, args } }, splitPaneContextMenu); + + makeContextItem(profileName, profileIcon, splitPaneToolTipText, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Automatic, .5, args } }, splitPaneContextMenu, splitPaneMenu); + } + + makeMenuItem(RS_(L"SplitPaneText"), L"\xF246", splitPaneMenu, menu); // Only wire up "Close Pane" if there's multiple panes. if (_GetFocusedTabImpl()->GetLeafPaneCount() > 1) { - makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr }); + MUX::Controls::CommandBarFlyout swapPaneMenu{}; + const auto rootPane = _GetFocusedTabImpl()->GetRootPane(); + const auto mruPanes = _GetFocusedTabImpl()->GetMruPanes(); + auto activePane = _GetFocusedTabImpl()->GetActivePane(); + rootPane->WalkTree([&](auto p) { + if (const auto& c{ p->GetTerminalControl() }) + { + if (c == control) + { + activePane = p; + } + } + }); + + if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Down, mruPanes)) + { + makeItem(RS_(L"SwapPaneDownText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Down } }, swapPaneMenu); + } + + if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Right, mruPanes)) + { + makeItem(RS_(L"SwapPaneRightText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Right } }, swapPaneMenu); + } + + if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Up, mruPanes)) + { + makeItem(RS_(L"SwapPaneUpText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Up } }, swapPaneMenu); + } + + if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Left, mruPanes)) + { + makeItem(RS_(L"SwapPaneLeftText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Left } }, swapPaneMenu); + } + + makeMenuItem(RS_(L"SwapPaneText"), L"\xF1CB", swapPaneMenu, menu); + + makeItem(RS_(L"TogglePaneZoomText"), L"\xE8A3", ActionAndArgs{ ShortcutAction::TogglePaneZoom, nullptr }, menu); + makeItem(RS_(L"CloseOtherPanesText"), L"\xE89F", ActionAndArgs{ ShortcutAction::CloseOtherPanes, nullptr }, menu); + makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr }, menu); } if (control.ConnectionState() >= ConnectionState::Closed) { - makeItem(RS_(L"RestartConnectionText"), L"\xE72C", ActionAndArgs{ ShortcutAction::RestartConnection, nullptr }); + makeItem(RS_(L"RestartConnectionText"), L"\xE72C", ActionAndArgs{ ShortcutAction::RestartConnection, nullptr }, menu); } if (withSelection) { - makeItem(RS_(L"SearchWebText"), L"\xF6FA", ActionAndArgs{ ShortcutAction::SearchForText, nullptr }); + makeItem(RS_(L"SearchWebText"), L"\xF6FA", ActionAndArgs{ ShortcutAction::SearchForText, nullptr }, menu); } - makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } }); + makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } }, menu); } void TerminalPage::_PopulateQuickFixMenu(const TermControl& control, diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index 5a2f67b295..29cc3d1b5e 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -91,6 +91,7 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const; std::shared_ptr GetRootPane() const { return _rootPane; } + std::vector GetMruPanes() const { return _mruPanes; } winrt::TerminalApp::TerminalTabStatus TabStatus() { diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index 90648381e1..85446823b5 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -262,10 +262,18 @@ Please either install the missing font or choose another one. The tooltip for a paste button + Find... + The label of a button for searching for the text + + Find... The label of a button for searching for the selected text + Find + The tooltip for a button for searching for the text + + Find The tooltip for a button for searching for the selected text @@ -334,4 +342,4 @@ Please either install the missing font or choose another one. Suggested input: {0} {Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index ace9f1abbb..6fa5e6aebb 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -52,6 +52,10 @@ x:Uid="SelectOutputButton" Click="_SelectOutputHandler" Icon="AlignLeft" /> + @@ -65,10 +69,6 @@ Click="_PasteCommandHandler" Icon="Paste" /> - @@ -82,6 +82,10 @@ x:Uid="SelectOutputWithSelectionButton" Click="_SelectOutputHandler" Icon="AlignLeft" /> + diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj index f585755f20..90d053424f 100644 --- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj @@ -9,7 +9,6 @@ StaticLibrary Console true - 3 nested - - true true - - @@ -143,7 +138,9 @@ - + + Designer + @@ -183,9 +180,7 @@
- - - + \ No newline at end of file From 26cd15a14b0bfc06a951c800d960336328176173 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 23 May 2025 01:02:58 +0200 Subject: [PATCH 058/177] Eagerly persist on WM_ENDSESSION (#18912) Once an application has returned from handling `WM_ENDSESSION` the OS may kill the app at any point. This means we must persist our state while inside the message handler. Closes #17179 ## Validation Steps Performed Honestly, none. It's a race condition in the first place. --- src/cascadia/WindowsTerminal/WindowEmperor.cpp | 12 ++++++++++-- src/cascadia/WindowsTerminal/WindowEmperor.h | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index cdf34d9a3b..e57256cb2a 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -899,7 +899,8 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c RegisterApplicationRestart(nullptr, RESTART_NO_CRASH | RESTART_NO_HANG); return TRUE; case WM_ENDSESSION: - _forcePersistence = true; + _finalizeSessionPersistence(); + _skipPersistence = true; PostQuitMessage(0); return 0; default: @@ -946,7 +947,7 @@ void WindowEmperor::_persistState(const ApplicationState& state, bool serializeB state.PersistedWindowLayouts(nullptr); } - if (_forcePersistence || _app.Logic().Settings().GlobalSettings().ShouldUsePersistedLayout()) + if (_app.Logic().Settings().GlobalSettings().ShouldUsePersistedLayout()) { for (const auto& w : _windows) { @@ -960,6 +961,13 @@ void WindowEmperor::_persistState(const ApplicationState& state, bool serializeB void WindowEmperor::_finalizeSessionPersistence() const { + if (_skipPersistence) + { + // We received WM_ENDSESSION and persisted the state. + // We don't need to persist it again. + return; + } + const auto state = ApplicationState::SharedInstance(); _persistState(state, true); diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index bf63214030..c84882665f 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -77,7 +77,7 @@ private: UINT WM_TASKBARCREATED = 0; HMENU _currentWindowMenu = nullptr; bool _notificationIconShown = false; - bool _forcePersistence = false; + bool _skipPersistence = false; bool _needsPersistenceCleanup = false; SafeDispatcherTimer _persistStateTimer; std::optional _currentSystemThemeIsDark; From 32feec087bde810b75f0acdcd63ea63b86fef1bd Mon Sep 17 00:00:00 2001 From: Windows Console Service Bot <14666831+consvc@users.noreply.github.com> Date: Thu, 22 May 2025 18:52:40 -0500 Subject: [PATCH 059/177] Localization Updates - main - 05/22/2025 23:06:26 (#18908) --- .../Resources/de-DE/Resources.resw | 42 ++++++ .../Resources/es-ES/Resources.resw | 42 ++++++ .../Resources/fr-FR/Resources.resw | 42 ++++++ .../Resources/it-IT/Resources.resw | 42 ++++++ .../Resources/ja-JP/Resources.resw | 42 ++++++ .../Resources/ko-KR/Resources.resw | 42 ++++++ .../Resources/pt-BR/Resources.resw | 42 ++++++ .../Resources/qps-ploc/Resources.resw | 42 ++++++ .../Resources/qps-ploca/Resources.resw | 42 ++++++ .../Resources/qps-plocm/Resources.resw | 42 ++++++ .../Resources/ru-RU/Resources.resw | 42 ++++++ .../Resources/zh-CN/Resources.resw | 42 ++++++ .../Resources/zh-TW/Resources.resw | 42 ++++++ .../Resources/de-DE/Resources.resw | 8 ++ .../Resources/es-ES/Resources.resw | 8 ++ .../Resources/fr-FR/Resources.resw | 8 ++ .../Resources/it-IT/Resources.resw | 8 ++ .../Resources/ja-JP/Resources.resw | 8 ++ .../Resources/ko-KR/Resources.resw | 8 ++ .../Resources/pt-BR/Resources.resw | 8 ++ .../Resources/qps-ploc/Resources.resw | 10 +- .../Resources/qps-ploca/Resources.resw | 10 +- .../Resources/qps-plocm/Resources.resw | 10 +- .../Resources/ru-RU/Resources.resw | 8 ++ .../Resources/zh-CN/Resources.resw | 8 ++ .../Resources/zh-TW/Resources.resw | 8 ++ .../Resources/de-DE/Resources.resw | 52 ++++++- .../Resources/es-ES/Resources.resw | 52 ++++++- .../Resources/fr-FR/Resources.resw | 52 ++++++- .../Resources/it-IT/Resources.resw | 52 ++++++- .../Resources/ja-JP/Resources.resw | 52 ++++++- .../Resources/ko-KR/Resources.resw | 52 ++++++- .../Resources/pt-BR/Resources.resw | 52 ++++++- .../Resources/qps-ploc/Resources.resw | 54 ++++++- .../Resources/qps-ploca/Resources.resw | 54 ++++++- .../Resources/qps-plocm/Resources.resw | 54 ++++++- .../Resources/ru-RU/Resources.resw | 52 ++++++- .../Resources/zh-CN/Resources.resw | 52 ++++++- .../Resources/zh-TW/Resources.resw | 52 ++++++- .../UIHelpers/Resources/de-DE/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/es-ES/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/fr-FR/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/it-IT/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/ja-JP/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/ko-KR/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/pt-BR/Resources.resw | 136 ++++++++++++++++++ .../Resources/qps-ploc/Resources.resw | 136 ++++++++++++++++++ .../Resources/qps-ploca/Resources.resw | 136 ++++++++++++++++++ .../Resources/qps-plocm/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/ru-RU/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/zh-CN/Resources.resw | 136 ++++++++++++++++++ .../UIHelpers/Resources/zh-TW/Resources.resw | 136 ++++++++++++++++++ 52 files changed, 3048 insertions(+), 58 deletions(-) create mode 100644 src/cascadia/UIHelpers/Resources/de-DE/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/es-ES/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/fr-FR/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/it-IT/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/ja-JP/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/ko-KR/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/pt-BR/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/qps-ploc/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/qps-ploca/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/qps-plocm/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/ru-RU/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/zh-CN/Resources.resw create mode 100644 src/cascadia/UIHelpers/Resources/zh-TW/Resources.resw diff --git a/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw b/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw index dafd51d375..07d4509b7c 100644 --- a/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw @@ -213,6 +213,45 @@ Bereich teilen + + Mit der rechten Maustaste klicken für Richtungen zum Teilen – rechts/unten/oben/links + + + Bereich nach unten teilen + + + Bereich nach rechts teilen + + + Bereich nach oben teilen + + + Bereich nach links teilen + + + Duplikat + + + Bereich austauschen + + + Bereich nach unten wechseln + + + Bereich nach rechts wechseln + + + Bereich nach oben wechseln + + + Bereich nach links wechseln + + + Zoomfenster umschalten + + + Andere Bereiche schließen + Websuche @@ -939,4 +978,7 @@ Nach rechts + + Ein ungültiger regulärer Ausdruck wurde entdeckt. + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw b/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw index 5554ab65d9..3ef00fe6a1 100644 --- a/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw @@ -210,6 +210,45 @@ Dividir panel + + Haga clic con el botón derecho para dividir las direcciones: derecha/abajo/arriba/izquierda + + + Panel dividido hacia abajo + + + Panel dividido a la derecha + + + Panel dividido hacia arriba + + + Panel dividido a la izquierda + + + Duplicar + + + Intercambiar panel + + + Intercambiar panel hacia abajo + + + Intercambiar panel derecho + + + Intercambiar panel hacia arriba + + + Intercambiar panel izquierdo + + + Zoom de panel de alternancia + + + Cerrar otros paneles + Búsqueda en la web @@ -936,4 +975,7 @@ Mover a la derecha + + Se ha encontrado una expresión regular no válida. + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw index 7985c115fe..6d897738c0 100644 --- a/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw @@ -210,6 +210,45 @@ Fractionner le volet + + Cliquez avec le bouton droit pour afficher les directions de séparation – droite/bas/haut/gauche + + + Fractionner le volet vers le bas + + + Fractionner le volet à droite + + + Fractionner le volet vers le haut + + + Fractionner le volet à gauche + + + Dupliquer + + + Permuter le volet + + + Permuter le volet vers le bas + + + Permuter le volet à droite + + + Permuter le volet vers le haut + + + Permuter le volet à gauche + + + Basculer le zoom du volet + + + Fermer les autres volets + Recherche web @@ -936,4 +975,7 @@ Déplacer vers la droite + + Une expression régulière non valide a été trouvée. + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw b/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw index 09638c2271..c8ae13a833 100644 --- a/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw @@ -210,6 +210,45 @@ Suddividi riquadro + + Fai clic con il pulsante destro del mouse per le direzioni di divisione: destra/giù/su/sinistra + + + Dividi riquadro in giù + + + Dividi riquadro a destra + + + Dividi riquadro in su + + + Dividi riquadro a sinistra + + + Duplica + + + Scambia riquadro + + + Scambia riquadro in giù + + + Scambia riquadro a destra + + + Scambia riquadro in su + + + Scambia riquadro a sinistra + + + Attiva/disattiva zoom riquadro + + + Chiudi altri riquadri + Ricerca nel Web @@ -936,4 +975,7 @@ Sposta a destra + + È stata trovata un'espressione regolare non valida. + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw index 4db76c8857..462193c2ce 100644 --- a/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw @@ -211,6 +211,45 @@ ウィンドウを分割する + + 分割する方向を右クリック - 右/下/上/左 + + + 下にペインを分割 + + + 右にペインを分割 + + + 上にペインを分割 + + + 左にペインを分割 + + + 複製 + + + ウィンドウを入れ替える + + + 下とペインを入れ替え + + + 右とペインを入れ替え + + + 上とペインを入れ替え + + + 左とペインを入れ替え + + + ウィンドウのズームの切り替え + + + 他のウィンドウを閉じる + Web 検索 @@ -937,4 +976,7 @@ 右へ移動 + + 無効な正規表現が見つかりました。 + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw index af54a3ad3e..14fdffdaa8 100644 --- a/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw @@ -210,6 +210,45 @@ 분할 창 + + 오른쪽 클릭으로 방향 분할 - 오른쪽/아래쪽/위쪽/왼쪽 + + + 창 아래로 분할 + + + 창 오른쪽으로 분할 + + + 창 위로 분할 + + + 창 왼쪽으로 분할 + + + 중복 + + + 창 스왑 + + + 창 아래로 전환 + + + 창 오른쪽으로 전환 + + + 창 위로 전환 + + + 창 왼쪽으로 전환 + + + 창 확대/축소 토글 + + + 다른 창 닫기 + 웹 검색 @@ -936,4 +975,7 @@ 오른쪽으로 이동 + + 잘못된 정규식을 찾았습니다. + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw index 4fb173f76c..b75bf1c3b1 100644 --- a/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw @@ -210,6 +210,45 @@ Dividir painel + + Clique com o botão direito para obter direções de divisão - direita/para baixo/para cima/esquerda + + + Dividir painel para baixo + + + Dividir painel à direita + + + Dividir painel para cima + + + Dividir painel à esquerda + + + Duplicar + + + Painel de troca + + + Alternar painel para baixo + + + Alternar painel à direita + + + Alternar painel para cima + + + Alternar painel para a esquerda + + + Alternar o zoom do painel + + + Fechar outros painéis + Pesquisa na Web @@ -936,4 +975,7 @@ Mover para a direita + + Foi encontrada uma expressão regular inválida. + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw index f9c83a98c9..f737463a65 100644 --- a/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw @@ -214,6 +214,45 @@ Šрŀіт φªňë !!! + + Ґΐĝĥτ ςļìск ƒôŗ ѕрℓΐť δįяęčŧįόпŝ - гΐġћŧ/đôшň/µρ/ľëƒт !!! !!! !!! !!! !!! + + + Śφľīţ φǻńě ðбẃή !!! ! + + + Śрĺїť рâйê řіġнţ !!! ! + + + Ŝрļįŧ рâⁿë ūφ !!! + + + Ѕрľîţ φâиè ļзƒţ !!! ! + + + Đύрľįčäтè !!! + + + Šŵάφ рąņё !!! + + + Ѕщǻφ ρåπê ďōщπ !!! ! + + + Şώǻρ ρâпέ ŕïġђţ !!! ! + + + Śωãр φàňє цр !!! + + + Šŵªφ ρäήę łеƒť !!! ! + + + Тøģġľε φªηê żοбм !!! ! + + + Ċłǿѕē ōŧĥзŗ рάпèś !!! !! + Ẅёв şĕаŕčĥ !!! @@ -944,4 +983,7 @@ Мóνε яίģнŧ !!! + + Дʼn ΐňνãľîδ ŗěğµℓдř ë×ρяēѕšιбή ẃăѕ ƒοůʼnđ. !!! !!! !!! !!! + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw index f9c83a98c9..f737463a65 100644 --- a/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw @@ -214,6 +214,45 @@ Šрŀіт φªňë !!! + + Ґΐĝĥτ ςļìск ƒôŗ ѕрℓΐť δįяęčŧįόпŝ - гΐġћŧ/đôшň/µρ/ľëƒт !!! !!! !!! !!! !!! + + + Śφľīţ φǻńě ðбẃή !!! ! + + + Śрĺїť рâйê řіġнţ !!! ! + + + Ŝрļįŧ рâⁿë ūφ !!! + + + Ѕрľîţ φâиè ļзƒţ !!! ! + + + Đύрľįčäтè !!! + + + Šŵάφ рąņё !!! + + + Ѕщǻφ ρåπê ďōщπ !!! ! + + + Şώǻρ ρâпέ ŕïġђţ !!! ! + + + Śωãр φàňє цр !!! + + + Šŵªφ ρäήę łеƒť !!! ! + + + Тøģġľε φªηê żοбм !!! ! + + + Ċłǿѕē ōŧĥзŗ рάпèś !!! !! + Ẅёв şĕаŕčĥ !!! @@ -944,4 +983,7 @@ Мóνε яίģнŧ !!! + + Дʼn ΐňνãľîδ ŗěğµℓдř ë×ρяēѕšιбή ẃăѕ ƒοůʼnđ. !!! !!! !!! !!! + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw index f9c83a98c9..f737463a65 100644 --- a/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw @@ -214,6 +214,45 @@ Šрŀіт φªňë !!! + + Ґΐĝĥτ ςļìск ƒôŗ ѕрℓΐť δįяęčŧįόпŝ - гΐġћŧ/đôшň/µρ/ľëƒт !!! !!! !!! !!! !!! + + + Śφľīţ φǻńě ðбẃή !!! ! + + + Śрĺїť рâйê řіġнţ !!! ! + + + Ŝрļįŧ рâⁿë ūφ !!! + + + Ѕрľîţ φâиè ļзƒţ !!! ! + + + Đύрľįčäтè !!! + + + Šŵάφ рąņё !!! + + + Ѕщǻφ ρåπê ďōщπ !!! ! + + + Şώǻρ ρâпέ ŕïġђţ !!! ! + + + Śωãр φàňє цр !!! + + + Šŵªφ ρäήę łеƒť !!! ! + + + Тøģġľε φªηê żοбм !!! ! + + + Ċłǿѕē ōŧĥзŗ рάпèś !!! !! + Ẅёв şĕаŕčĥ !!! @@ -944,4 +983,7 @@ Мóνε яίģнŧ !!! + + Дʼn ΐňνãľîδ ŗěğµℓдř ë×ρяēѕšιбή ẃăѕ ƒοůʼnđ. !!! !!! !!! !!! + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw index c1410ce2dd..4bff030fdd 100644 --- a/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw @@ -210,6 +210,45 @@ Разделить область + + Щелкните правой кнопкой мыши для направлений разделения — вправо, вниз, вверх, влево + + + Разделить панель вниз + + + Разделить панель вправо + + + Разделить панель вверх + + + Разделить панель влево + + + Дублировать + + + Переключить панель + + + Переключить панель вниз + + + Переключить панель вправо + + + Переключить панель вверх + + + Переключить панель влево + + + Переключить масштаб области + + + Закрыть другие панели + Поиск в Интернете @@ -936,4 +975,7 @@ Вправо + + Обнаружено недопустимое регулярное выражение. + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw index a2e85cd607..d5935358d5 100644 --- a/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw @@ -210,6 +210,45 @@ 拆分窗格 + + 右键单击以查看拆分方向 - 右/下/上/左 + + + 向下拆分窗格 + + + 向右拆分窗格 + + + 向上拆分窗格 + + + 向左拆分窗格 + + + 复制 + + + 交换窗格 + + + 向下交换窗格 + + + 向右交换窗格 + + + 向上交换窗格 + + + 向左交换窗格 + + + 切换窗格缩放 + + + 关闭其他窗格 + 网络搜索 @@ -936,4 +975,7 @@ 右移 + + 发现无效的正则表达式。 + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw index ec80712720..26689ad636 100644 --- a/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw @@ -210,6 +210,45 @@ 分割窗格 + + 以滑鼠右鍵按一下以選擇分割方向 - 右/下/上/左 + + + 向下分割窗格 + + + 向右分割窗格 + + + 向上分割窗格 + + + 向左分割窗格 + + + 複製 + + + 交換窗格 + + + 向下交換窗格 + + + 向右交換窗格 + + + 向上交換窗格 + + + 向左交換窗格 + + + 切換窗格縮放 + + + 關閉其他窗格 + 網頁搜尋 @@ -936,4 +975,7 @@ 右移 + + 發現無效的規則運算式。 + \ No newline at end of file diff --git a/src/cascadia/TerminalControl/Resources/de-DE/Resources.resw b/src/cascadia/TerminalControl/Resources/de-DE/Resources.resw index 5533ac90a8..4becb02528 100644 --- a/src/cascadia/TerminalControl/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/de-DE/Resources.resw @@ -262,10 +262,18 @@ Installieren Sie entweder die fehlende Schriftart, oder wählen Sie eine andere The tooltip for a paste button + Suchen... + The label of a button for searching for the text + + Suchen... The label of a button for searching for the selected text + Suchen + The tooltip for a button for searching for the text + + Suchen The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/es-ES/Resources.resw b/src/cascadia/TerminalControl/Resources/es-ES/Resources.resw index f2556bb2c6..07b1cbcbbe 100644 --- a/src/cascadia/TerminalControl/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/es-ES/Resources.resw @@ -262,10 +262,18 @@ Instale la fuente que falta o elija otra. The tooltip for a paste button + Buscar... + The label of a button for searching for the text + + Buscar... The label of a button for searching for the selected text + Buscar + The tooltip for a button for searching for the text + + Buscar The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalControl/Resources/fr-FR/Resources.resw index f12f296270..4eea4c3d68 100644 --- a/src/cascadia/TerminalControl/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/fr-FR/Resources.resw @@ -262,10 +262,18 @@ Installez la police manquante ou choisissez-en une autre. The tooltip for a paste button + Rechercher... + The label of a button for searching for the text + + Rechercher... The label of a button for searching for the selected text + Rechercher + The tooltip for a button for searching for the text + + Rechercher The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/it-IT/Resources.resw b/src/cascadia/TerminalControl/Resources/it-IT/Resources.resw index e602293582..cbbf2adf81 100644 --- a/src/cascadia/TerminalControl/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/it-IT/Resources.resw @@ -262,10 +262,18 @@ Installare il tipo di carattere mancante o sceglierne un altro. The tooltip for a paste button + Cerca... + The label of a button for searching for the text + + Cerca... The label of a button for searching for the selected text + Trova + The tooltip for a button for searching for the text + + Trova The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalControl/Resources/ja-JP/Resources.resw index 13e7f41a4a..325fcdd28e 100644 --- a/src/cascadia/TerminalControl/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/ja-JP/Resources.resw @@ -262,10 +262,18 @@ The tooltip for a paste button + 検索... + The label of a button for searching for the text + + 検索... The label of a button for searching for the selected text + 見つける + The tooltip for a button for searching for the text + + 見つける The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalControl/Resources/ko-KR/Resources.resw index bd9f2887f3..9ef713a8cf 100644 --- a/src/cascadia/TerminalControl/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/ko-KR/Resources.resw @@ -262,10 +262,18 @@ The tooltip for a paste button + 찾기... + The label of a button for searching for the text + + 찾기... The label of a button for searching for the selected text + 찾기 + The tooltip for a button for searching for the text + + 찾기 The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalControl/Resources/pt-BR/Resources.resw index e1d2d2e2bf..c08fbb1ffd 100644 --- a/src/cascadia/TerminalControl/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/pt-BR/Resources.resw @@ -262,10 +262,18 @@ Instale a fonte ausente ou escolha outra. The tooltip for a paste button + Localizar... + The label of a button for searching for the text + + Localizar... The label of a button for searching for the selected text + Localizar + The tooltip for a button for searching for the text + + Localizar The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalControl/Resources/qps-ploc/Resources.resw index bf67ca0a24..2e772ba1ce 100644 --- a/src/cascadia/TerminalControl/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/qps-ploc/Resources.resw @@ -263,10 +263,18 @@ ₣їņď... !! + The label of a button for searching for the text + + + ₣íŋđ... !! The label of a button for searching for the selected text ₣ΐņđ ! + The tooltip for a button for searching for the text + + + ₣іήđ ! The tooltip for a button for searching for the selected text @@ -334,4 +342,4 @@ Şůğğĕşŧеδ íήρύť: {0} !!! !!! {Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalControl/Resources/qps-ploca/Resources.resw index bf67ca0a24..2e772ba1ce 100644 --- a/src/cascadia/TerminalControl/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/qps-ploca/Resources.resw @@ -263,10 +263,18 @@ ₣їņď... !! + The label of a button for searching for the text + + + ₣íŋđ... !! The label of a button for searching for the selected text ₣ΐņđ ! + The tooltip for a button for searching for the text + + + ₣іήđ ! The tooltip for a button for searching for the selected text @@ -334,4 +342,4 @@ Şůğğĕşŧеδ íήρύť: {0} !!! !!! {Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalControl/Resources/qps-plocm/Resources.resw index bf67ca0a24..2e772ba1ce 100644 --- a/src/cascadia/TerminalControl/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/qps-plocm/Resources.resw @@ -263,10 +263,18 @@ ₣їņď... !! + The label of a button for searching for the text + + + ₣íŋđ... !! The label of a button for searching for the selected text ₣ΐņđ ! + The tooltip for a button for searching for the text + + + ₣іήđ ! The tooltip for a button for searching for the selected text @@ -334,4 +342,4 @@ Şůğğĕşŧеδ íήρύť: {0} !!! !!! {Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalControl/Resources/ru-RU/Resources.resw index e3682e213d..24f8c2999b 100644 --- a/src/cascadia/TerminalControl/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/ru-RU/Resources.resw @@ -262,10 +262,18 @@ The tooltip for a paste button + Найти… + The label of a button for searching for the text + + Найти… The label of a button for searching for the selected text + Найти + The tooltip for a button for searching for the text + + Найти The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalControl/Resources/zh-CN/Resources.resw index edd933add7..56e74730eb 100644 --- a/src/cascadia/TerminalControl/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/zh-CN/Resources.resw @@ -262,10 +262,18 @@ The tooltip for a paste button + 查找... + The label of a button for searching for the text + + 查找... The label of a button for searching for the selected text + 查找 + The tooltip for a button for searching for the text + + 查找 The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalControl/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalControl/Resources/zh-TW/Resources.resw index d79a9592e4..0b14cd1d66 100644 --- a/src/cascadia/TerminalControl/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/zh-TW/Resources.resw @@ -262,10 +262,18 @@ The tooltip for a paste button + 搜尋... + The label of a button for searching for the text + + 搜尋... The label of a button for searching for the selected text + 尋找 + The tooltip for a button for searching for the text + + 尋找 The tooltip for a button for searching for the selected text diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 679417567f..4f291d928b 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Fügen Sie eine Gruppe von Profilen hinzu, die mindestens einer der definierten Eigenschaften entsprechen. + Fügen Sie eine Gruppe von Profilen hinzu, die mindestens einer der definierten Eigenschaften für reguläre Ausdrücke entsprechen. Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - Profilname + Profilname (regulärer Ausdruck) Header for a text box used to define a regex for the names of profiles to add. - Profilquelle + Profilquelle (regulärer Ausdruck) Header for a text box used to define a regex for the sources of profiles to add. - Befehlszeile + Befehlszeile (regulärer Ausdruck) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ Diese Option wird durch eine Unternehmensrichtlinie verwaltet und kann hier nicht geändert werden. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Weitere Informationen zu regulären Ausdrücken + + + Keine + Text displayed when the background image path is not defined. + + + Keine + Text displayed when the answerback message is not defined. + + + Auf Standardeinstellungen zurücksetzen + + + Cache löschen + + + Im Cache werden Daten gespeichert, die mit gespeicherten Sitzungen und automatisch generierten Profilen zusammenhängen. + + + Zurücksetzen + + + Löschen + + + Diese Aktion wird sofort angewendet und kann nicht rückgängig gemacht werden. + + + Diese Aktion wird sofort angewendet und kann nicht rückgängig gemacht werden. + + + Möchten Sie Ihre Einstellungen zurücksetzen? + + + Möchten Sie den Cache wirklich löschen? + + + Ja, meine Einstellungen zurücksetzen + + + Ja, Cache löschen + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index 0be7181131..35a70e5a4b 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Agregar un grupo de perfiles que coincidan con al menos una de las propiedades definidas + Agregar un grupo de perfiles que coincidan al menos con una de las propiedades de expresión regular definidas Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - Nombre del perfil + Nombre del perfil (expresión regular) Header for a text box used to define a regex for the names of profiles to add. - Origen del perfil + Origen del perfil (expresión regular) Header for a text box used to define a regex for the sources of profiles to add. - Commandline + Línea de comandos (expresión regular) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ Esta opción está administrada por una directiva de empresa y no se puede cambiar aquí. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Más información sobre las expresiones regulares + + + Ninguno + Text displayed when the background image path is not defined. + + + Ninguno + Text displayed when the answerback message is not defined. + + + Restablecer a la configuración predeterminada + + + Borrar caché + + + La caché guarda datos relacionados con las sesiones guardadas y los perfiles generados automáticamente. + + + Restablecer + + + Despejado + + + Esta acción se aplica inmediatamente y no se puede deshacer. + + + Esta acción se aplica inmediatamente y no se puede deshacer. + + + ¿Estás seguro de que quieres restablecer la configuración? + + + ¿Seguro que quieres borrar la memoria caché? + + + Sí, restablecer mi configuración + + + Sí, borrar la caché + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index 78d6d4f832..3c5f48dc49 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Ajouter un groupe de profils qui correspondent à au moins une des propriétés définies + Ajouter un groupe de profils qui correspondent à au moins une des propriétés d’expression régulière définies Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - Nom de profil + Nom du profil (expression régulière) Header for a text box used to define a regex for the names of profiles to add. - Source du profil + Source du profil (expression régulière) Header for a text box used to define a regex for the sources of profiles to add. - Ligne de commande + Ligne de commande (expression régulière) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ Cette option est gérée par la stratégie d’entreprise et ne peut pas être modifiée ici. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + En savoir plus sur les expressions régulières + + + Aucun + Text displayed when the background image path is not defined. + + + Aucun + Text displayed when the answerback message is not defined. + + + Revenir aux paramètres par défaut + + + Effacer le cache + + + Le cache stocke les données relatives aux sessions enregistrées et aux profils générés automatiquement. + + + Réinitialiser + + + Dégagé + + + Cette action est appliquée immédiatement et ne peut pas être annulée. + + + Cette action est appliquée immédiatement et ne peut pas être annulée. + + + Voulez-vous vraiment réinitialiser vos paramètres ? + + + Voulez-vous vraiment effacer votre cache du navigateur ? + + + Oui, réinitialisez mes paramètres + + + Oui, vider le cache + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index c314989f0a..7eb7211d70 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Aggiungi un gruppo di profili che corrispondono ad almeno una delle proprietà definite + Aggiungi un gruppo di profili che corrispondono ad almeno una delle proprietà dell'espressione regolare definita Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - Nome profilo + Nome del profilo (espressione regolare) Header for a text box used to define a regex for the names of profiles to add. - Origine profilo + Origine profilo (espressione regolare) Header for a text box used to define a regex for the sources of profiles to add. - Commandline + Riga di comando (espressione regolare) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ Questa opzione è gestita da criteri aziendali e non può essere modificata qui. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Scopri di più sulle espressioni regolari + + + Nessuno + Text displayed when the background image path is not defined. + + + Nessuno + Text displayed when the answerback message is not defined. + + + Ripristina le impostazioni predefinite + + + Cancella cache + + + La cache archivia i dati relativi alle sessioni salvate e ai profili generati automaticamente. + + + Reimposta + + + Sereno + + + Questa azione viene applicata immediatamente e non può essere annullata. + + + Questa azione viene applicata immediatamente e non può essere annullata. + + + Vuoi reimpostare le impostazioni? + + + Vuoi cancellare la cache? + + + Sì, reimposta le mie impostazioni + + + Sì, cancella la cache + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index d7f660c8de..97a17fa9d7 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - 定義されたプロパティの少なくとも 1 つに一致するプロファイルのグループを追加します + 定義された正規表現プロパティの少なくとも 1 つに一致するプロファイルのグループを追加します Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - プロファイル名 + プロファイル名 (正規表現) Header for a text box used to define a regex for the names of profiles to add. - プロファイル ソース + プロファイル ソース (正規表現) Header for a text box used to define a regex for the sources of profiles to add. - Commandline + コマンドライン (正規表現) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ このオプションはエンタープライズ ポリシーによって管理されているため、ここで変更することはできません。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 正規表現について詳細を表示する + + + なし + Text displayed when the background image path is not defined. + + + なし + Text displayed when the answerback message is not defined. + + + 初期設定にリセット + + + キャッシュのクリア + + + キャッシュには、保存されたセッションと自動生成されたプロファイルに関連するデータが格納されます。 + + + リセット + + + キャンセル + + + この操作はすぐに適用され、元に戻すことはできません。 + + + この操作はすぐに適用され、元に戻すことはできません。 + + + 設定をリセットしますか? + + + キャッシュをクリアしますか? + + + はい、設定をリセットします + + + はい、キャッシュをクリアします + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index 6c0cdccc25..6061445a4c 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - 정의된 속성 중 하나 이상과 일치하는 프로필 그룹을 추가합니다. + 정의된 정규식 속성 중 하나 이상과 일치하는 프로필 그룹을 추가합니다. Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - 프로필 이름 + 프로필 이름(정규식) Header for a text box used to define a regex for the names of profiles to add. - 프로필 원본 + 프로필 원본(정규식) Header for a text box used to define a regex for the sources of profiles to add. - 명령줄 + 명령줄(정규식) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ 이 옵션은 엔터프라이즈 정책에 의해 관리되므로 여기에서 변경할 수 없습니다. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 정규식에 대한 자세한 정보 + + + 없음 + Text displayed when the background image path is not defined. + + + 없음 + Text displayed when the answerback message is not defined. + + + 기본 설정으로 초기화 + + + 캐시 지우기 + + + 캐시는 저장된 세션과 관련된 데이터 및 자동으로 생성된 프로필을 저장합니다. + + + 다시 설정 + + + 취소 + + + 이 작업은 즉시 적용되며 실행 취소할 수 없습니다. + + + 이 작업은 즉시 적용되며 실행 취소할 수 없습니다. + + + 설정을 초기화하시겠습니까? + + + 캐시를 지우시겠습니까? + + + 예, 설정을 초기화합니다. + + + 예, 캐시를 지웁니다. + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index 207ba0e0f3..522b910a92 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Adicionar um grupo de perfis que correspondam a pelo menos uma das propriedades definidas + Adicionar um grupo de perfis que correspondam a pelo menos uma das propriedades de expressão regular definidas Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - Nome do perfil + Nome do perfil (expressão regular) Header for a text box used to define a regex for the names of profiles to add. - Origem do perfil + Origem do perfil (expressão regular) Header for a text box used to define a regex for the sources of profiles to add. - Commandline + Linha de comando (expressão regular) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ Esta opção é gerenciada pela política corporativa e não pode ser alterada aqui. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Saiba mais sobre expressões regulares + + + Nenhum + Text displayed when the background image path is not defined. + + + Nenhum + Text displayed when the answerback message is not defined. + + + Redefinir para as configurações padrão + + + Limpar cache + + + O cache armazena dados relacionados a sessões salvas e perfis gerados automaticamente. + + + Restaurar + + + Limpar + + + Esta ação é aplicada imediatamente e não pode ser desfeita. + + + Esta ação é aplicada imediatamente e não pode ser desfeita. + + + Tem certeza de que deseja redefinir as configurações? + + + Tem certeza de que deseja limpar o cache? + + + Sim, redefinir minhas configurações + + + Sim, limpar o cache + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index e0ca3c51b9..970ddb22f5 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -2105,7 +2105,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Аðđ á ġŕôũρ οƒ φřθƒїłєŝ ŧнăť мąţςĥ аτ ĺěàѕτ θņė ŏƒ ţђę δεƒįňèď φŗōрεřтìěş !!! !!! !!! !!! !!! !!! !!! + Аðđ á ġŕôũρ οƒ φřθƒїłєŝ ŧнăť мąţςĥ аτ ĺěàѕτ θņė ŏƒ ţђę δεƒįňèď řêğџŀäř èхρŕéşѕįбñ рřбφĕřţĭęś !!! !!! !!! !!! !!! !!! !!! !!! !!! Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2121,15 +2121,15 @@ Header for a control that adds a folder to the new tab menu. - Ρґоƒîłĕ ňǻмε !!! + Ρґоƒîłĕ ňǻмε (ŗěğΰłář ёхρѓέŝšĩǿň) !!! !!! !!! Header for a text box used to define a regex for the names of profiles to add. - Ρřοƒϊļë šőµřĉε !!! ! + Ρřοƒϊļë šőµřĉε (гęģůłάґ е×рřĕşšιóⁿ) !!! !!! !!! ! Header for a text box used to define a regex for the sources of profiles to add. - Çόммάηðłϊйё !!! + Çόммάηðłϊйё (я℮ġϋℓąř єжφгèѕśįóп) !!! !!! !!! Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2344,4 +2344,48 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. - \ No newline at end of file + + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! + + + Иθñé ! + Text displayed when the background image path is not defined. + + + Ŋøπë ! + Text displayed when the answerback message is not defined. + + + Яєšèţ ŧθ ďēƒªũĺť śέττιŋĝş !!! !!! ! + + + Сℓёāř ςàċђë !!! + + + Ŧђё ¢âćћз ѕтõŕèś ďдтä ґєłäťěð τò ѕάνêď śέşŝϊσŋŝ àňđ åùŧóмăτïςάľłÿ ġēʼnęřдţзδ рřŏƒìļëş. !!! !!! !!! !!! !!! !!! !!! !!! ! + + + Ѓęѕεт ! + + + Ćļĕáг ! + + + Ţнįś ā¢тîōń įš āррŀїėđ ίммęđíάτ℮ℓý αηδ čãлηõť вэ џⁿđбήё. !!! !!! !!! !!! !!! ! + + + Ťђìѕ âćťιöη ϊş ąρрľįêδ ìмmεðιдţέĺγ ăʼnď çªňŋŏŧ вέ ũйđόηę. !!! !!! !!! !!! !!! ! + + + Āяě ỳōμ śυřê ŷοц ẁаņτ τô яėşéŧ ўőũґ ŝэтŧîπģŝ? !!! !!! !!! !!! ! + + + Àŗè уоϋ ŝџгє ўŏϋ шåⁿť тο ςℓэāѓ γόџя ¢áсĥє? !!! !!! !!! !!! + + + Ў℮ś, řэŝēŧ мγ ŝεττίήğѕ !!! !!! + + + Υėŝ, ςŀёãř ŧнę сǻςнэ !!! !!! + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index e0ca3c51b9..970ddb22f5 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -2105,7 +2105,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Аðđ á ġŕôũρ οƒ φřθƒїłєŝ ŧнăť мąţςĥ аτ ĺěàѕτ θņė ŏƒ ţђę δεƒįňèď φŗōрεřтìěş !!! !!! !!! !!! !!! !!! !!! + Аðđ á ġŕôũρ οƒ φřθƒїłєŝ ŧнăť мąţςĥ аτ ĺěàѕτ θņė ŏƒ ţђę δεƒįňèď řêğџŀäř èхρŕéşѕįбñ рřбφĕřţĭęś !!! !!! !!! !!! !!! !!! !!! !!! !!! Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2121,15 +2121,15 @@ Header for a control that adds a folder to the new tab menu. - Ρґоƒîłĕ ňǻмε !!! + Ρґоƒîłĕ ňǻмε (ŗěğΰłář ёхρѓέŝšĩǿň) !!! !!! !!! Header for a text box used to define a regex for the names of profiles to add. - Ρřοƒϊļë šőµřĉε !!! ! + Ρřοƒϊļë šőµřĉε (гęģůłάґ е×рřĕşšιóⁿ) !!! !!! !!! ! Header for a text box used to define a regex for the sources of profiles to add. - Çόммάηðłϊйё !!! + Çόммάηðłϊйё (я℮ġϋℓąř єжφгèѕśįóп) !!! !!! !!! Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2344,4 +2344,48 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. - \ No newline at end of file + + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! + + + Иθñé ! + Text displayed when the background image path is not defined. + + + Ŋøπë ! + Text displayed when the answerback message is not defined. + + + Яєšèţ ŧθ ďēƒªũĺť śέττιŋĝş !!! !!! ! + + + Сℓёāř ςàċђë !!! + + + Ŧђё ¢âćћз ѕтõŕèś ďдтä ґєłäťěð τò ѕάνêď śέşŝϊσŋŝ àňđ åùŧóмăτïςάľłÿ ġēʼnęřдţзδ рřŏƒìļëş. !!! !!! !!! !!! !!! !!! !!! !!! ! + + + Ѓęѕεт ! + + + Ćļĕáг ! + + + Ţнįś ā¢тîōń įš āррŀїėđ ίммęđíάτ℮ℓý αηδ čãлηõť вэ џⁿđбήё. !!! !!! !!! !!! !!! ! + + + Ťђìѕ âćťιöη ϊş ąρрľįêδ ìмmεðιдţέĺγ ăʼnď çªňŋŏŧ вέ ũйđόηę. !!! !!! !!! !!! !!! ! + + + Āяě ỳōμ śυřê ŷοц ẁаņτ τô яėşéŧ ўőũґ ŝэтŧîπģŝ? !!! !!! !!! !!! ! + + + Àŗè уоϋ ŝџгє ўŏϋ шåⁿť тο ςℓэāѓ γόџя ¢áсĥє? !!! !!! !!! !!! + + + Ў℮ś, řэŝēŧ мγ ŝεττίήğѕ !!! !!! + + + Υėŝ, ςŀёãř ŧнę сǻςнэ !!! !!! + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index e0ca3c51b9..970ddb22f5 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -2105,7 +2105,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Аðđ á ġŕôũρ οƒ φřθƒїłєŝ ŧнăť мąţςĥ аτ ĺěàѕτ θņė ŏƒ ţђę δεƒįňèď φŗōрεřтìěş !!! !!! !!! !!! !!! !!! !!! + Аðđ á ġŕôũρ οƒ φřθƒїłєŝ ŧнăť мąţςĥ аτ ĺěàѕτ θņė ŏƒ ţђę δεƒįňèď řêğџŀäř èхρŕéşѕįбñ рřбφĕřţĭęś !!! !!! !!! !!! !!! !!! !!! !!! !!! Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2121,15 +2121,15 @@ Header for a control that adds a folder to the new tab menu. - Ρґоƒîłĕ ňǻмε !!! + Ρґоƒîłĕ ňǻмε (ŗěğΰłář ёхρѓέŝšĩǿň) !!! !!! !!! Header for a text box used to define a regex for the names of profiles to add. - Ρřοƒϊļë šőµřĉε !!! ! + Ρřοƒϊļë šőµřĉε (гęģůłάґ е×рřĕşšιóⁿ) !!! !!! !!! ! Header for a text box used to define a regex for the sources of profiles to add. - Çόммάηðłϊйё !!! + Çόммάηðłϊйё (я℮ġϋℓąř єжφгèѕśįóп) !!! !!! !!! Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2344,4 +2344,48 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. - \ No newline at end of file + + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! + + + Иθñé ! + Text displayed when the background image path is not defined. + + + Ŋøπë ! + Text displayed when the answerback message is not defined. + + + Яєšèţ ŧθ ďēƒªũĺť śέττιŋĝş !!! !!! ! + + + Сℓёāř ςàċђë !!! + + + Ŧђё ¢âćћз ѕтõŕèś ďдтä ґєłäťěð τò ѕάνêď śέşŝϊσŋŝ àňđ åùŧóмăτïςάľłÿ ġēʼnęřдţзδ рřŏƒìļëş. !!! !!! !!! !!! !!! !!! !!! !!! ! + + + Ѓęѕεт ! + + + Ćļĕáг ! + + + Ţнįś ā¢тîōń įš āррŀїėđ ίммęđíάτ℮ℓý αηδ čãлηõť вэ џⁿđбήё. !!! !!! !!! !!! !!! ! + + + Ťђìѕ âćťιöη ϊş ąρрľįêδ ìмmεðιдţέĺγ ăʼnď çªňŋŏŧ вέ ũйđόηę. !!! !!! !!! !!! !!! ! + + + Āяě ỳōμ śυřê ŷοц ẁаņτ τô яėşéŧ ўőũґ ŝэтŧîπģŝ? !!! !!! !!! !!! ! + + + Àŗè уоϋ ŝџгє ўŏϋ шåⁿť тο ςℓэāѓ γόџя ¢áсĥє? !!! !!! !!! !!! + + + Ў℮ś, řэŝēŧ мγ ŝεττίήğѕ !!! !!! + + + Υėŝ, ςŀёãř ŧнę сǻςнэ !!! !!! + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index 9c6b2b8486..ed6f9e8e29 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - Добавить группу профилей, соответствующих хотя бы одному из определенных свойств + Добавить группу профилей, соответствующих хотя бы одному из определенных свойств регулярного выражения Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - Имя профиля + Имя профиля (регулярное выражение) Header for a text box used to define a regex for the names of profiles to add. - Источник профиля + Источник профиля (регулярное выражение) Header for a text box used to define a regex for the sources of profiles to add. - Командная строка + Командная строка (регулярное выражение) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ Этот параметр управляется политикой предприятия и не может быть изменен здесь. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Подробнее о регулярных выражениях + + + Нет + Text displayed when the background image path is not defined. + + + Нет + Text displayed when the answerback message is not defined. + + + Восстановить параметры по умолчанию + + + Очистить кэш + + + В кэше хранятся данные, связанные с сохраненными сеансами и автоматически созданными профилями. + + + Сбросить + + + Ясно + + + Это действие применяется немедленно и не может быть отменено. + + + Это действие применяется немедленно и не может быть отменено. + + + Действительно сбросить параметры? + + + Действительно очистить кэш? + + + Да, сбросить параметры + + + Да, очистить кэш + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index 48f70b7149..096fdca93a 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - 添加至少匹配一个已定义属性的配置文件组 + 添加至少匹配一个定义正则表达式属性的配置文件组 Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - 配置文件名称 + 配置文件名称(正则表达式) Header for a text box used to define a regex for the names of profiles to add. - 配置文件源 + 配置文件来源(正则表达式) Header for a text box used to define a regex for the sources of profiles to add. - 命令行 + 命令行(正则表达式) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ 此选项由企业策略管理,无法在此处更改。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 了解有关正则表达式的更多信息 + + + + Text displayed when the background image path is not defined. + + + + Text displayed when the answerback message is not defined. + + + 重置为默认设置 + + + 清除缓存 + + + 缓存会存储与已保存会话及自动生成的配置文件相关的数据。 + + + 重置 + + + 清除 + + + 此作立即应用,无法撤消。 + + + 此作立即应用,无法撤消。 + + + 是否确实要重置设置? + + + 是否确实要清除缓存? + + + 是,重置设置 + + + 是,清除缓存 + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index 647caf9c78..4666ecd933 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -2101,7 +2101,7 @@ Header for a control that adds any remaining profiles to the new tab menu. - 新增至少符合其中一個已定義屬性的配置檔群組 + 新增至少符合其中一個已定義正則表達式屬性的配置檔群組 Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles". @@ -2117,15 +2117,15 @@ Header for a control that adds a folder to the new tab menu. - 設定檔名稱 + 設定檔名稱 (規則運算式) Header for a text box used to define a regex for the names of profiles to add. - 設定檔來源 + 設定檔來源 (規則運算式) Header for a text box used to define a regex for the sources of profiles to add. - 命令列 + Commandline (規則運算式) Header for a text box used to define a regex for the commandlines of profiles to add. @@ -2340,4 +2340,48 @@ 此選項由企業原則管理,無法在此變更。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 深入了解規則運算式 + + + + Text displayed when the background image path is not defined. + + + + Text displayed when the answerback message is not defined. + + + 重設至預設設定 + + + 清除快取 + + + 快取會儲存與已儲存之工作階段和自動產生設定檔相關的資料。 + + + 重設 + + + 晴朗 + + + 此動作會立即套用且無法復原。 + + + 此動作會立即套用且無法復原。 + + + 確定要重設您的設定嗎? + + + 確定要清除快取嗎? + + + 是的,重設我的設定 + + + 是的,清除快取 + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/de-DE/Resources.resw b/src/cascadia/UIHelpers/Resources/de-DE/Resources.resw new file mode 100644 index 0000000000..147375bc16 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/de-DE/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ausschneiden + "Cut" as contained in a Cut/Copy/Paste menu + + + Kopieren + "Copy" as contained in a Cut/Copy/Paste menu + + + Einfügen + "Paste" as contained in a Cut/Copy/Paste menu + + + Alles auswählen + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/es-ES/Resources.resw b/src/cascadia/UIHelpers/Resources/es-ES/Resources.resw new file mode 100644 index 0000000000..c4747c4733 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/es-ES/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cortar + "Cut" as contained in a Cut/Copy/Paste menu + + + Copiar + "Copy" as contained in a Cut/Copy/Paste menu + + + Pegar + "Paste" as contained in a Cut/Copy/Paste menu + + + Seleccionar todo + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/fr-FR/Resources.resw b/src/cascadia/UIHelpers/Resources/fr-FR/Resources.resw new file mode 100644 index 0000000000..25be5734ec --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/fr-FR/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Couper + "Cut" as contained in a Cut/Copy/Paste menu + + + Copier + "Copy" as contained in a Cut/Copy/Paste menu + + + Coller + "Paste" as contained in a Cut/Copy/Paste menu + + + Sélectionner tout + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/it-IT/Resources.resw b/src/cascadia/UIHelpers/Resources/it-IT/Resources.resw new file mode 100644 index 0000000000..a8e144c2ef --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/it-IT/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Taglia + "Cut" as contained in a Cut/Copy/Paste menu + + + Copia + "Copy" as contained in a Cut/Copy/Paste menu + + + Incolla + "Paste" as contained in a Cut/Copy/Paste menu + + + Seleziona tutto + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/ja-JP/Resources.resw b/src/cascadia/UIHelpers/Resources/ja-JP/Resources.resw new file mode 100644 index 0000000000..b87a07f71c --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/ja-JP/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 切り取り + "Cut" as contained in a Cut/Copy/Paste menu + + + コピー + "Copy" as contained in a Cut/Copy/Paste menu + + + 貼り付け + "Paste" as contained in a Cut/Copy/Paste menu + + + すべて選択 + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/ko-KR/Resources.resw b/src/cascadia/UIHelpers/Resources/ko-KR/Resources.resw new file mode 100644 index 0000000000..ef0902c477 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/ko-KR/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 잘라내기 + "Cut" as contained in a Cut/Copy/Paste menu + + + 복사 + "Copy" as contained in a Cut/Copy/Paste menu + + + 붙여넣기 + "Paste" as contained in a Cut/Copy/Paste menu + + + 모두 선택 + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/pt-BR/Resources.resw b/src/cascadia/UIHelpers/Resources/pt-BR/Resources.resw new file mode 100644 index 0000000000..66f820dc2a --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/pt-BR/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Recortar + "Cut" as contained in a Cut/Copy/Paste menu + + + Copiar + "Copy" as contained in a Cut/Copy/Paste menu + + + Colar + "Paste" as contained in a Cut/Copy/Paste menu + + + Selecionar Tudo + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/qps-ploc/Resources.resw b/src/cascadia/UIHelpers/Resources/qps-ploc/Resources.resw new file mode 100644 index 0000000000..438b4260d9 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/qps-ploc/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ĉύţ + "Cut" as contained in a Cut/Copy/Paste menu + + + Çбφÿ ! + "Copy" as contained in a Cut/Copy/Paste menu + + + Рåşţέ ! + "Paste" as contained in a Cut/Copy/Paste menu + + + Śėľєċŧ Áľľ !!! + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/qps-ploca/Resources.resw b/src/cascadia/UIHelpers/Resources/qps-ploca/Resources.resw new file mode 100644 index 0000000000..438b4260d9 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/qps-ploca/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ĉύţ + "Cut" as contained in a Cut/Copy/Paste menu + + + Çбφÿ ! + "Copy" as contained in a Cut/Copy/Paste menu + + + Рåşţέ ! + "Paste" as contained in a Cut/Copy/Paste menu + + + Śėľєċŧ Áľľ !!! + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/qps-plocm/Resources.resw b/src/cascadia/UIHelpers/Resources/qps-plocm/Resources.resw new file mode 100644 index 0000000000..438b4260d9 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/qps-plocm/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ĉύţ + "Cut" as contained in a Cut/Copy/Paste menu + + + Çбφÿ ! + "Copy" as contained in a Cut/Copy/Paste menu + + + Рåşţέ ! + "Paste" as contained in a Cut/Copy/Paste menu + + + Śėľєċŧ Áľľ !!! + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/ru-RU/Resources.resw b/src/cascadia/UIHelpers/Resources/ru-RU/Resources.resw new file mode 100644 index 0000000000..3be08c4670 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/ru-RU/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Вырезать + "Cut" as contained in a Cut/Copy/Paste menu + + + Копировать + "Copy" as contained in a Cut/Copy/Paste menu + + + Вставить + "Paste" as contained in a Cut/Copy/Paste menu + + + Выделить все + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/zh-CN/Resources.resw b/src/cascadia/UIHelpers/Resources/zh-CN/Resources.resw new file mode 100644 index 0000000000..4f8bffd80d --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/zh-CN/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 剪切 + "Cut" as contained in a Cut/Copy/Paste menu + + + 复制 + "Copy" as contained in a Cut/Copy/Paste menu + + + 粘贴 + "Paste" as contained in a Cut/Copy/Paste menu + + + 全选 + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file diff --git a/src/cascadia/UIHelpers/Resources/zh-TW/Resources.resw b/src/cascadia/UIHelpers/Resources/zh-TW/Resources.resw new file mode 100644 index 0000000000..ff8daf42f8 --- /dev/null +++ b/src/cascadia/UIHelpers/Resources/zh-TW/Resources.resw @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 剪下 + "Cut" as contained in a Cut/Copy/Paste menu + + + 複製 + "Copy" as contained in a Cut/Copy/Paste menu + + + 貼上 + "Paste" as contained in a Cut/Copy/Paste menu + + + 全選 + "Select All" as contained in a Cut/Copy/Paste menu + + \ No newline at end of file From b50eaa19e0cfa396d40d2359c1b64e73e2d6af4f Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 22 May 2025 16:56:00 -0700 Subject: [PATCH 060/177] Add branding and unpackaged metadata to a few telemetry events (#18926) ## Summary of the Pull Request Adds branding and distribution metadata to the following telemetry events: - ActionDispatched (branding only) - JsonSettingsChanged - UISettingsChanged - SessionBecameInteractive Also removes the settings logger output from the debugger and some leftover debugging functions. Adds a label to the XSettingsChanged settings value to make it easier to read on the backend. --- .../TerminalApp/ShortcutActionDispatch.cpp | 12 ++ .../NewTabMenuViewModel.cpp | 135 ------------------ .../NewTabMenuViewModel.h | 8 -- .../CascadiaSettingsSerialization.cpp | 28 ++-- .../WindowsTerminal/WindowEmperor.cpp | 14 ++ 5 files changed, 46 insertions(+), 151 deletions(-) diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index 3f7e509ab1..28cb64b5aa 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "ShortcutActionDispatch.h" +#include "WtExeUtils.h" #include "ShortcutActionDispatch.g.cpp" @@ -53,11 +54,22 @@ namespace winrt::TerminalApp::implementation if (handled) { +#if defined(WT_BRANDING_RELEASE) + constexpr uint8_t branding = 3; +#elif defined(WT_BRANDING_PREVIEW) + constexpr uint8_t branding = 2; +#elif defined(WT_BRANDING_CANARY) + constexpr uint8_t branding = 1; +#else + constexpr uint8_t branding = 0; +#endif + TraceLoggingWrite( g_hTerminalAppProvider, "ActionDispatched", TraceLoggingDescription("Event emitted when an action was successfully performed"), TraceLoggingValue(static_cast(actionAndArgs.Action()), "Action"), + TraceLoggingValue(branding, "Branding"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.cpp b/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.cpp index 62986177cc..f04c24346e 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.cpp @@ -363,7 +363,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation const auto& entryVM = make(profileEntry); CurrentView().Append(entryVM); - _PrintAll(); return entryVM; } return nullptr; @@ -374,8 +373,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Model::SeparatorEntry separatorEntry; const auto& entryVM = make(separatorEntry); CurrentView().Append(entryVM); - - _PrintAll(); return entryVM; } @@ -390,8 +387,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Reset state after adding the entry AddFolderName({}); _folderTreeCache = nullptr; - - _PrintAll(); return entryVM; } @@ -410,7 +405,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation ProfileMatcherSource({}); ProfileMatcherCommandline({}); - _PrintAll(); return entryVM; } @@ -422,7 +416,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _NotifyChanges(L"IsRemainingProfilesEntryMissing"); - _PrintAll(); return entryVM; } @@ -496,134 +489,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _folderEntry.Icon(); } - void NewTabMenuViewModel::_PrintAll() - { -#ifdef _DEBUG - OutputDebugString(L"---Model:---\n"); - _PrintModel(_Settings.GlobalSettings().NewTabMenu()); - OutputDebugString(L"\n"); - OutputDebugString(L"---VM:---\n"); - _PrintVM(_rootEntries); - OutputDebugString(L"\n"); -#endif - } - -#ifdef _DEBUG - void NewTabMenuViewModel::_PrintModel(Windows::Foundation::Collections::IVector list, std::wstring prefix) - { - if (!list) - { - return; - } - - for (auto&& e : list) - { - _PrintModel(e, prefix); - } - } - - void NewTabMenuViewModel::_PrintModel(const Model::NewTabMenuEntry& e, std::wstring prefix) - { - switch (e.Type()) - { - case NewTabMenuEntryType::Profile: - { - const auto& pe = e.as(); - OutputDebugString(fmt::format(L"{}Profile: {}\n", prefix, pe.Profile().Name()).c_str()); - break; - } - case NewTabMenuEntryType::Action: - { - const auto& actionEntry = e.as(); - OutputDebugString(fmt::format(L"{}Action: {}\n", prefix, actionEntry.ActionId()).c_str()); - break; - } - case NewTabMenuEntryType::Separator: - { - OutputDebugString(fmt::format(L"{}Separator\n", prefix).c_str()); - break; - } - case NewTabMenuEntryType::Folder: - { - const auto& fe = e.as(); - OutputDebugString(fmt::format(L"{}Folder: {}\n", prefix, fe.Name()).c_str()); - _PrintModel(fe.RawEntries(), prefix + L" "); - break; - } - case NewTabMenuEntryType::MatchProfiles: - { - const auto& matchProfilesEntry = e.as(); - OutputDebugString(fmt::format(L"{}MatchProfiles: {}\n", prefix, matchProfilesEntry.Name()).c_str()); - break; - } - case NewTabMenuEntryType::RemainingProfiles: - { - OutputDebugString(fmt::format(L"{}RemainingProfiles\n", prefix).c_str()); - break; - } - default: - break; - } - } - - void NewTabMenuViewModel::_PrintVM(Windows::Foundation::Collections::IVector list, std::wstring prefix) - { - if (!list) - { - return; - } - - for (auto&& e : list) - { - _PrintVM(e, prefix); - } - } - - void NewTabMenuViewModel::_PrintVM(const Editor::NewTabMenuEntryViewModel& e, std::wstring prefix) - { - switch (e.Type()) - { - case NewTabMenuEntryType::Profile: - { - const auto& pe = e.as(); - OutputDebugString(fmt::format(L"{}Profile: {}\n", prefix, pe.ProfileEntry().Profile().Name()).c_str()); - break; - } - case NewTabMenuEntryType::Action: - { - const auto& actionEntry = e.as(); - OutputDebugString(fmt::format(L"{}Action: {}\n", prefix, actionEntry.ActionEntry().ActionId()).c_str()); - break; - } - case NewTabMenuEntryType::Separator: - { - OutputDebugString(fmt::format(L"{}Separator\n", prefix).c_str()); - break; - } - case NewTabMenuEntryType::Folder: - { - const auto& fe = e.as(); - OutputDebugString(fmt::format(L"{}Folder: {}\n", prefix, fe.Name()).c_str()); - _PrintVM(fe.Entries(), prefix + L" "); - break; - } - case NewTabMenuEntryType::MatchProfiles: - { - const auto& matchProfilesEntry = e.as(); - OutputDebugString(fmt::format(L"{}MatchProfiles: {}\n", prefix, matchProfilesEntry.DisplayText()).c_str()); - break; - } - case NewTabMenuEntryType::RemainingProfiles: - { - OutputDebugString(fmt::format(L"{}RemainingProfiles\n", prefix).c_str()); - break; - } - default: - break; - } - } -#endif - NewTabMenuEntryViewModel::NewTabMenuEntryViewModel(const NewTabMenuEntryType type) noexcept : _Type{ type } { diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.h b/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.h index c5486f68b5..9a1ea51e2b 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.h @@ -68,14 +68,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation static bool _IsRemainingProfilesEntryMissing(const Windows::Foundation::Collections::IVector& entries); void _FolderPropertyChanged(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); - - void _PrintAll(); -#ifdef _DEBUG - void _PrintModel(Windows::Foundation::Collections::IVector list, std::wstring prefix = L""); - void _PrintModel(const Model::NewTabMenuEntry& e, std::wstring prefix = L""); - void _PrintVM(Windows::Foundation::Collections::IVector list, std::wstring prefix = L""); - void _PrintVM(const Editor::NewTabMenuEntryViewModel& vm, std::wstring prefix = L""); -#endif }; struct FolderTreeViewEntry : FolderTreeViewEntryT diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 5fdb390b5a..2efcc1de39 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -27,6 +27,7 @@ #include "ProfileEntry.h" #include "FolderEntry.h" #include "MatchProfilesEntry.h" +#include "WtExeUtils.h" using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::ApplicationModel::AppExtensions; @@ -1685,10 +1686,22 @@ void CascadiaSettings::LogSettingChanges(bool isJsonLoad) const changes.insert(change); } +#if defined(WT_BRANDING_RELEASE) + constexpr uint8_t branding = 3; +#elif defined(WT_BRANDING_PREVIEW) + constexpr uint8_t branding = 2; +#elif defined(WT_BRANDING_CANARY) + constexpr uint8_t branding = 1; +#else + constexpr uint8_t branding = 0; +#endif + const uint8_t distribution = IsPackaged() ? 2 : + IsPortableMode() ? 1 : + 0; + // report changes for (const auto& change : changes) { -#ifndef _DEBUG // A `isJsonLoad ? "JsonSettingsChanged" : "UISettingsChanged"` // would be nice, but that apparently isn't allowed in the macro below. // Also, there's guidance to not send too much data all in one event, @@ -1698,7 +1711,9 @@ void CascadiaSettings::LogSettingChanges(bool isJsonLoad) const TraceLoggingWrite(g_hSettingsModelProvider, "JsonSettingsChanged", TraceLoggingDescription("Event emitted when settings.json change"), - TraceLoggingValue(change.data()), + TraceLoggingValue(change.data(), "Setting"), + TraceLoggingValue(branding, "Branding"), + TraceLoggingValue(distribution, "Distribution"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } @@ -1707,14 +1722,11 @@ void CascadiaSettings::LogSettingChanges(bool isJsonLoad) const TraceLoggingWrite(g_hSettingsModelProvider, "UISettingsChanged", TraceLoggingDescription("Event emitted when settings change via the UI"), - TraceLoggingValue(change.data()), + TraceLoggingValue(change.data(), "Setting"), + TraceLoggingValue(branding, "Branding"), + TraceLoggingValue(distribution, "Distribution"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } -#else - OutputDebugStringA(isJsonLoad ? "JsonSettingsChanged - " : "UISettingsChanged - "); - OutputDebugStringA(change.data()); - OutputDebugStringA("\n"); -#endif // !_DEBUG } } diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index e57256cb2a..1a750af650 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -399,10 +399,24 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) { if (!loggedInteraction) { +#if defined(WT_BRANDING_RELEASE) + constexpr uint8_t branding = 3; +#elif defined(WT_BRANDING_PREVIEW) + constexpr uint8_t branding = 2; +#elif defined(WT_BRANDING_CANARY) + constexpr uint8_t branding = 1; +#else + constexpr uint8_t branding = 0; +#endif + const uint8_t distribution = IsPackaged() ? 2 : + _app.Logic().Settings().IsPortableMode() ? 1 : + 0; TraceLoggingWrite( g_hWindowsTerminalProvider, "SessionBecameInteractive", TraceLoggingDescription("Event emitted when the session was interacted with"), + TraceLoggingValue(branding, "Branding"), + TraceLoggingValue(distribution, "Distribution"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); loggedInteraction = true; From 2d64a3a4ab0d0d1c5b9085a022f5319460ef68d7 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 22 May 2025 16:57:04 -0700 Subject: [PATCH 061/177] Fix some build errors from updated VS build agents (#18927) Looks like there's a new VS version on the build agents. This just goes through and fixes any issues they found. There's still a COMPILER CRASH though. --- src/cascadia/TerminalApp/TerminalPage.cpp | 6 +++--- .../TerminalControl/InteractivityAutomationPeer.cpp | 2 +- src/host/ut_host/Host.UnitTests.vcxproj | 2 +- src/host/ut_host/Host.UnitTests.vcxproj.filters | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index e72722f042..702b4a36ee 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2303,7 +2303,7 @@ namespace winrt::TerminalApp::implementation // for it. The Title change will be propagated upwards through the tab's // PropertyChanged event handler. void TerminalPage::_activePaneChanged(winrt::TerminalApp::TerminalTab sender, - Windows::Foundation::IInspectable args) + Windows::Foundation::IInspectable /*args*/) { if (const auto tab{ _GetTerminalTabImpl(sender) }) { @@ -5382,8 +5382,8 @@ namespace winrt::TerminalApp::implementation _sendDraggedTabToWindow(winrt::to_hstring(args.TargetWindow()), args.TabIndex(), std::nullopt); } - void TerminalPage::_onTabDroppedOutside(winrt::IInspectable sender, - winrt::MUX::Controls::TabViewTabDroppedOutsideEventArgs e) + void TerminalPage::_onTabDroppedOutside(winrt::IInspectable /*sender*/, + winrt::MUX::Controls::TabViewTabDroppedOutsideEventArgs /*e*/) { // Get the current pointer point from the CoreWindow const auto& pointerPoint{ CoreWindow::GetForCurrentThread().PointerPosition() }; diff --git a/src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp b/src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp index 757796e76c..77780108c8 100644 --- a/src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp +++ b/src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp @@ -118,7 +118,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return WrapArrayOfTextRangeProviders(pReturnVal); } - XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::RangeFromChild(XamlAutomation::IRawElementProviderSimple childElement) + XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::RangeFromChild(XamlAutomation::IRawElementProviderSimple /*childElement*/) { UIA::ITextRangeProvider* returnVal; // ScreenInfoUiaProvider doesn't actually use parameter, so just pass in nullptr diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index 1fbaaaff14..c914e48adf 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -78,7 +78,7 @@ - + diff --git a/src/host/ut_host/Host.UnitTests.vcxproj.filters b/src/host/ut_host/Host.UnitTests.vcxproj.filters index ebf93cbf27..dd573753e3 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj.filters +++ b/src/host/ut_host/Host.UnitTests.vcxproj.filters @@ -83,7 +83,7 @@ Header Files - + Header Files From c594fcc91bb9fb4fedc6b2a055c7ea01eb361a20 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 27 May 2025 17:56:11 -0500 Subject: [PATCH 062/177] fuzzing: make fuzzing work with vcpkg and the new library layout (#18970) We need to replace the vcpkg "target triplet" configs with ones that enable ASAN! --- .../fuzzing/arm64-windows-static.cmake | 6 ++++++ dep/vcpkg-overlay-triplets/fuzzing/x64-windows-static.cmake | 6 ++++++ dep/vcpkg-overlay-triplets/fuzzing/x86-windows-static.cmake | 6 ++++++ src/common.build.pre.props | 4 +++- 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 dep/vcpkg-overlay-triplets/fuzzing/arm64-windows-static.cmake create mode 100644 dep/vcpkg-overlay-triplets/fuzzing/x64-windows-static.cmake create mode 100644 dep/vcpkg-overlay-triplets/fuzzing/x86-windows-static.cmake diff --git a/dep/vcpkg-overlay-triplets/fuzzing/arm64-windows-static.cmake b/dep/vcpkg-overlay-triplets/fuzzing/arm64-windows-static.cmake new file mode 100644 index 0000000000..41bcac2329 --- /dev/null +++ b/dep/vcpkg-overlay-triplets/fuzzing/arm64-windows-static.cmake @@ -0,0 +1,6 @@ +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE static) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CXX_FLAGS /fsanitize=address) +set(VCPKG_C_FLAGS /fsanitize=address) diff --git a/dep/vcpkg-overlay-triplets/fuzzing/x64-windows-static.cmake b/dep/vcpkg-overlay-triplets/fuzzing/x64-windows-static.cmake new file mode 100644 index 0000000000..909e747923 --- /dev/null +++ b/dep/vcpkg-overlay-triplets/fuzzing/x64-windows-static.cmake @@ -0,0 +1,6 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE static) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CXX_FLAGS /fsanitize=address) +set(VCPKG_C_FLAGS /fsanitize=address) diff --git a/dep/vcpkg-overlay-triplets/fuzzing/x86-windows-static.cmake b/dep/vcpkg-overlay-triplets/fuzzing/x86-windows-static.cmake new file mode 100644 index 0000000000..60a3c5e9cb --- /dev/null +++ b/dep/vcpkg-overlay-triplets/fuzzing/x86-windows-static.cmake @@ -0,0 +1,6 @@ +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE static) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CXX_FLAGS /fsanitize=address) +set(VCPKG_C_FLAGS /fsanitize=address) diff --git a/src/common.build.pre.props b/src/common.build.pre.props index 4e2346eb05..b3f57bf230 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -262,7 +262,7 @@ FUZZING_BUILD;%(PreprocessorDefinitions) - libsancov.lib;clang_rt.asan-$(OCClangArchitectureName).lib;%(AdditionalDependencies) + libsancov.lib;clang_rt.asan_dynamic-$(OCClangArchitectureName).lib;%(AdditionalDependencies) @@ -279,12 +279,14 @@ arm64 --x-feature=terminal + $(VcpkgAdditionalInstallOptions) --overlay-triplets=$(SolutionDir)\dep\vcpkg-overlay-triplets\fuzzing false $(SolutionDir)\obj\$(Platform)\vcpkg + $(SolutionDir)\obj\$(Platform)\vcpkg-fuzzing $(VCPKG_ROOT) $(VsInstallRoot)\VC\vcpkg From 3acb3d510b4b1e7511312263cba426bf8eaffa8e Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 27 May 2025 17:56:27 -0500 Subject: [PATCH 063/177] bodgy: inline FillRow in FillTextBuffer to work around DD-2413379 (#18971) Something about FillRow tickles the compiler and causes it to ICE during LTCG. See DD-2413379 for more. DevCom: https://developercommunity.visualstudio.com/t/VS-2022-1714-p11-cannot-complete-a-bui/10864784 --- src/inc/test/CommonState.hpp | 85 ++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp index 66ddfb5b00..82d8707653 100644 --- a/src/inc/test/CommonState.hpp +++ b/src/inc/test/CommonState.hpp @@ -216,7 +216,43 @@ public: for (til::CoordType iRow = 0; iRow < cRowsToFill; iRow++) { ROW& row = textBuffer.GetMutableRowByOffset(iRow); - FillRow(&row, iRow & 1); + + // fill a row + // - Each row is populated with L"AB\u304bC\u304dDE " + // - 7 characters, 6 spaces. 13 total + // - The characters take up first 9 columns. (The wide glyphs take up 2 columns each) + // - か = \x304b, き = \x304d + + uint16_t column = 0; + for (const auto& ch : std::wstring_view{ L"AB\u304bC\u304dDE " }) + { + const uint16_t width = ch >= 0x80 ? 2 : 1; + row.ReplaceCharacters(column, width, { &ch, 1 }); + column += width; + } + + // A = bright red on dark gray + // This string starts at index 0 + auto Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY); + row.SetAttrToEnd(0, Attr); + + // BかC = dark gold on bright blue + // This string starts at index 1 + Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); + row.SetAttrToEnd(1, Attr); + + // き = bright white on dark purple + // This string starts at index 5 + Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE); + row.SetAttrToEnd(5, Attr); + + // DE = black on dark green + // This string starts at index 7 + Attr = TextAttribute(BACKGROUND_GREEN); + row.SetAttrToEnd(7, Attr); + + // odd rows forced a wrap + row.SetWrapForced(iRow & 1); } textBuffer.GetCursor().SetYPosition(cRowsToFill); @@ -233,51 +269,4 @@ private: FontInfo m_pFontInfo; std::unique_ptr m_backupTextBufferInfo; std::unique_ptr m_readHandle; - - void FillRow(ROW* pRow, bool wrapForced) - { - // fill a row - // - Each row is populated with L"AB\u304bC\u304dDE " - // - 7 characters, 6 spaces. 13 total - // - The characters take up first 9 columns. (The wide glyphs take up 2 columns each) - // - か = \x304b, き = \x304d - - uint16_t column = 0; - for (const auto& ch : std::wstring_view{ L"AB\u304bC\u304dDE " }) - { - const uint16_t width = ch >= 0x80 ? 2 : 1; - pRow->ReplaceCharacters(column, width, { &ch, 1 }); - column += width; - } - - // A = bright red on dark gray - // This string starts at index 0 - auto Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY); - pRow->SetAttrToEnd(0, Attr); - - // BかC = dark gold on bright blue - // This string starts at index 1 - Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); - pRow->SetAttrToEnd(1, Attr); - - // き = bright white on dark purple - // This string starts at index 5 - Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE); - pRow->SetAttrToEnd(5, Attr); - - // DE = black on dark green - // This string starts at index 7 - Attr = TextAttribute(BACKGROUND_GREEN); - pRow->SetAttrToEnd(7, Attr); - - // odd rows forced a wrap - if (wrapForced) - { - pRow->SetWrapForced(true); - } - else - { - pRow->SetWrapForced(false); - } - } }; From e332c67f5147439ae27f44fa539a425d55c10543 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 28 May 2025 12:03:02 -0700 Subject: [PATCH 064/177] Add an Extensions page to the Settings UI (#18559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request adds an Extensions page to the Settings UI, which lets you enable/disable extensions and see how they affect your settings (i.e. adding/modifying profiles and adding color schemes). This page is specifically designed for fragment extensions and dynamic profile generators, but can be expanded on in the future as we develop a more advanced extensions model. App extensions extract the name and icon from the extension package and display it in the UI. Dynamic profile generators extract the name and icon from the generator and display it in the UI. We prefer to use the display name for breadcrumbs when possible. A "NEW" badge was added to the Extensions page's `NavigationViewItem` to highlight that it's new. It goes away once the user visits it. ## Detailed Description of the Pull Request / Additional comments - Settings Model changes: - `FragmentSettings` represents a parsed json fragment extension. - `FragmentProfileEntry` and `FragmentColorSchemeEntry` are used to track profiles and color schemes added/modified - `ExtensionPackage` bundles the `FragmentSettings` together. This is how we represent multiple JSON files in one extension. - `IDynamicProfileGenerator` exposes a `DisplayName` and `Icon` - `ExtensionPackage`s created from app extensions extract the `DisplayName` and `Icon` from the extension - `ApplicationState` is used to track which badges have been dismissed and prevent them from appearing again - a `std::unordered_set` is used to keep track of the dismissed badges, but we only expose a get and append function via the IDL to interact with it - Editor changes - view models: - `ExtensionsViewModel` operates as the main view model for the page. - `FragmentProfileViewModel` and `FragmentColorSchemeViewModel` are used to reference specific components of fragments. They also provide support for navigating to the linked profile or color scheme via the settings UI! - `ExtensionPackageViewModel` is a VM for a group of extensions exposed by a single source. This is mainly needed because a single source can have multiple JSON fragments in it. This is used for the navigators on the main page. Can be extended to provide additional information (i.e. package logo, package name, etc.) - `CurrentExtensionPackage` is used to track which extension package is currently in view, if applicable (similar to how the new tab menu page works) - Editor changes - views: - `Extensions.xaml` uses _a lot_ of data templates. These are reused in `ItemsControl`s to display extension components. - `ExtensionPackageTemplateSelector` is used to display `ExtensionPackage`s with metadata vs simple ones that just have a source (i.e. Git) - Added a `NewInfoBadge` style that is just an InfoBadge with "New" in it instead of a number or an icon. Based on https://github.com/microsoft/PowerToys/pull/36939 - The visibility is bound to a `get` call to the `ApplicationState` conducted via the `ExtensionsPageViewModel`. The VM is also responsible for updating the state. - Lazy loading extension objects - Since most instances of Terminal won't actually open the settings UI, it doesn't make sense to create all the extension objects upon startup. Instead, we defer creating those objects until the user actually navigates to the Extensions page. This is most of the work that happened in `CascadiaSettingsSerialization.cpp`. The `SettingsLoader` can be used specifically to load and create the extension objects. ## Validation Steps ✅ Keyboard navigation feels right ✅ Screen reader reads all info on screen properly ✅ Accessibility Insights FastPass found no issues ✅ "Discard changes" retains subpage, but removes any changes ✅ Extensions page nav item displays a badge if page hasn't been visited ✅ The badge is dismissed when the user visits the page ## Follow-ups - Streamline a process for adding extensions from the new page - Long-term, we can reuse the InfoBadge system and make the following minor changes: - `SettingContainer`: display the badge and add logic to read/write `ApplicationState` appropriately (similarly to above) - `XPageViewModel`: - count all the badges that will be displayed and expose/bind that to `InfoBadge.Value` - If a whole page is new, we can just style the badge using the `NewInfoBadge` style --- .../ProfileGeneratorIcons/AzureCloudShell.png | Bin 0 -> 943 bytes .../ProfileGeneratorIcons/PowerShell.png | Bin 0 -> 1357 bytes .../ProfileGeneratorIcons/SSH.png | Bin 0 -> 787 bytes .../ProfileGeneratorIcons/VisualStudio.png | Bin 0 -> 1616 bytes .../ProfileGeneratorIcons/WSL.png | Bin 0 -> 1668 bytes src/cascadia/CascadiaResources.build.items | 5 + .../TerminalSettingsEditor/AddProfile.xaml | 2 +- .../CommonResources.xaml | 25 +- .../TerminalSettingsEditor/Extensions.cpp | 509 ++++++++++++++++++ .../TerminalSettingsEditor/Extensions.h | 193 +++++++ .../TerminalSettingsEditor/Extensions.idl | 81 +++ .../TerminalSettingsEditor/Extensions.xaml | 496 +++++++++++++++++ .../TerminalSettingsEditor/MainPage.cpp | 125 ++++- .../TerminalSettingsEditor/MainPage.h | 6 + .../TerminalSettingsEditor/MainPage.idl | 6 +- .../TerminalSettingsEditor/MainPage.xaml | 14 +- ...Microsoft.Terminal.Settings.Editor.vcxproj | 15 + .../TerminalSettingsEditor/NewTabMenu.cpp | 2 - .../TerminalSettingsEditor/NewTabMenu.h | 1 - .../TerminalSettingsEditor/NewTabMenu.xaml | 2 +- .../Resources/en-US/Resources.resw | 47 ++ .../SettingContainerStyle.xaml | 60 ++- .../ApplicationState.cpp | 25 + .../TerminalSettingsModel/ApplicationState.h | 5 +- .../ApplicationState.idl | 2 + .../AzureCloudShellGenerator.cpp | 13 + .../AzureCloudShellGenerator.h | 2 + .../CascadiaSettings.cpp | 14 + .../TerminalSettingsModel/CascadiaSettings.h | 109 +++- .../CascadiaSettings.idl | 38 ++ .../CascadiaSettingsSerialization.cpp | 296 +++++++--- .../GlobalAppSettings.cpp | 8 + .../IDynamicProfileGenerator.h | 2 + .../PowershellCoreProfileGenerator.cpp | 12 + .../PowershellCoreProfileGenerator.h | 2 + .../Resources/en-US/Resources.resw | 76 ++- .../SshHostGenerator.cpp | 12 + .../TerminalSettingsModel/SshHostGenerator.h | 2 + .../VisualStudioGenerator.cpp | 12 + .../VisualStudioGenerator.h | 2 + .../WslDistroGenerator.cpp | 15 +- .../WslDistroGenerator.h | 2 + 42 files changed, 2123 insertions(+), 115 deletions(-) create mode 100644 src/cascadia/CascadiaPackage/ProfileGeneratorIcons/AzureCloudShell.png create mode 100644 src/cascadia/CascadiaPackage/ProfileGeneratorIcons/PowerShell.png create mode 100644 src/cascadia/CascadiaPackage/ProfileGeneratorIcons/SSH.png create mode 100644 src/cascadia/CascadiaPackage/ProfileGeneratorIcons/VisualStudio.png create mode 100644 src/cascadia/CascadiaPackage/ProfileGeneratorIcons/WSL.png create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.h create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.idl create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.xaml diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/AzureCloudShell.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/AzureCloudShell.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ee454ce9802c662f8d431af38699055b8b24d7 GIT binary patch literal 943 zcmV;g15o^lP)^|iCCegRRn5ji6EOU zSRhm(Sd#{~cm!X=yH(%RQCWfvr}{@jNe(QW}ekix(3O zEH5v&g%HIw#5MEdeSaj0r=%h7I1ak5Bc&Xe zt4&T$Sq~!a`97v;29@e)WG@UzbfgJI8D8lOvF79k7I(dXKlKhlIKFMYlJPkN5 zBpLv?+8&~AmIe~<`I28ZZM+~8*_+PyI5uY9gB8Wr9jJ|Dd4Imf-f}jP{!DJhQims)*ZEc^Q;*&+aRJDujsiZC5{{ z)mAT2{N3lD;KNs+6;9!3cv2NG_zw5obmKDM2m=afRS7ZPjuQ#kK^@epyN+~pea>i#* z&c*8av}~K{zUiGB%~LJhw^RQHnuJ;4qk5y0(}X~OVYFR+Zdm7*Wg*A5k7}+s!j7t? z2O;0n3i+8AO-(lPJFKc>TA|JQu6UbFmQfX1_0^} z>y(5=U^pf5??O+!vTFkXS2i|^Zcv&FrF@`vsETs!P_6KT5F)*2^%4HF_!}WnF{5(; R5S#!2002ovPDHLkV1oKIv;_bF literal 0 HcmV?d00001 diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/PowerShell.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/PowerShell.png new file mode 100644 index 0000000000000000000000000000000000000000..72f5e54e31af3712cd9ee4a3b89a6d98e7230c7e GIT binary patch literal 1357 zcmV-T1+w~yP)9 z-+Knq$ls8Rrba>_k*xUBSj)BOCrS|EYgCzuf< zB#ek==ZG0a9npc2T`vi1n#{?1Lz2n!c6GyGh=Kry(UDQD6dWsqW26j@?gmcgWPNYU z{R`yP9wh>n0ee5iEm9Ko@WZKcI8k*FBDESB2@VwT9U&wmgCwL_LS&5JVDOTdx)cdG zh8>Yy@}9DqK^J=;WISwylQr)GMMaCiMQM<9kt#Gd3ehtpOlIqB5CM{Z)_Cqox7IH? zhO6EE{B>hO9DH6=0#sBq4~&+TUX%jaURYQ-on%b+l2-6x79l@kV91aTPM=cOpW6MQ2*>FxoA`7ZNct=Wt7LFcE zhymse=$|wBXNr)7024hiU+BEgs}|>0aJheesQM^0o;%Nz*zt5WytH$dLLFF=cmW3r zP@F!&8yU5R4pM+2V7uN+DONd_49)lVD(hg}HN}&tDBcSZ`piJof(em`1PZ95d%)mh zqR-DzdE_22Fa=0n<>ZCNn4AEu4RxT`#qtmSW}ATUHA#{}gK5DRtO)}rz_`ik;pFED zbpC}2Fc@6pPK`3K|7CCN-3_g0Y9-)bw01*bWefOj3`q(N5|v=gE3gD<8Jl?niWRVU zJRbF=YeFlImV1zj&_Mmsa@fDGfCu)>-C=>tk6!?ny#xMnvRw zz#T!J02BS_@UWK4xsY!JTc5~+&hI{hY|AEzgi*;AVd-%(;6dCxDytoS+6a!m%P?a*3n}YkASW{& ztXUSw&3=^hOvug2gvYE|5U$B=Y=!bYE1e!jJj;%nPpDY~6(eGxyHy&XSaP>5{< z4lHgH)3dlbdo8EX>4Tx04R}tkv&MmKpe$iQ%j3f9Lyl%kfAzR5EXHhDi*;)X)CnqU~=gfG%+M8 zE{=k0!NHHks)LKOt`4q(Aou~|;_9U6A|?JWDYS_3;J6>}?mh0_0YbCJG^=ME&~)3( zBok7;up)(D5k&|hB8bV%GE&)v9DK*uJpz2ai}Ni1bAOILwP-OQAQ8_p!?cOliKjPh zgY!PI$SSf*d`>)O(glehxvqHp#<}dWz%xZNmzg6LiKTKED_zVgrbawP98)!&@`b#| zD(5ZETCL97_v9~(7WL&U*J%zTiAAK4h6Dw5R8fPCIITJ<7BaM-@bM41{xrE1a#g{| zF^>&skX=9cAN=mtDosv!NznuldU2eO5g@z^v>J}{ee5``6Cn5uT(Ud*lat9cEGGtSBr65hAR07`5=$i__z%9_b>h;#%$LRx*qpp^2fP+I| zv`pD+KJSin_xA6Zc7H!o6mptvbX6w+000JJOGiWi{{a60|De66lK=n!32;bRa{vG? zBLDy{BLR4&KXw2B00(qQO+^Re3Wk>caVsHnX#M&ZX=9kV?EeWm_RDb^43GNX&YFB4EzsN!($xm=8KNC&h> zT$mZmjFb{`&M$Tg0JoJ`d3e9NA%#czjWJf6^bj!cxB9;!WZ;&8D!)B_{$5|uPzqWz zUNbP%90Qrn={YL`>>*8SiFw1U^Qo`9wgZvXI8&>c(YvIFh* RdH4VT002ovPDHLkV1ff0TR{K- literal 0 HcmV?d00001 diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/VisualStudio.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/VisualStudio.png new file mode 100644 index 0000000000000000000000000000000000000000..c41b6cb5811d6f6f508bc546fade8bc852343473 GIT binary patch literal 1616 zcmV-W2Cw;vP)vOI3V4ep#?*zn(1d*MXn#zY6Sv_}H?t;ewfSmgj z`7nSqFZz?x4?qr`J{(rg9h&eT8364ZtK>Gz_v~C>fkA+fke^O9RZq`gV@?2H6o211 zT)g7LLpiTxDROd-zVm9X?A*;E_@M}gPJSFzYKDzj0NCmA;&?SB! zxFJ3AZds7hqyrQRy5v1CQ>}dzbbCqX*E%J5HTpuQS?>#p)uRsp(YNJTi7wqPQs>;# zADeYR6ilC-f9e1nFJ3JRDXNsKEI$_@(d5ogg-|=P_u|Hx&I?NPtn# z840SFBZ98WykdUXfA@`Vs&u!)-z87HsNxDb5+H?+ay@*nn&rGv6NBd*#)i&i>FKZYs(p+r^ki zH4)3dT7ztYr_}|bD|m=0DxVZwzjYl>)yH5)%IIETjLU2aibmYNV@)(K6<>7J1Y z+Yi9y9}O4G0D9=c_2A<8Ik<4`bi$y{nzn3mhijke$S!MX1p;Bridep+BOnFoAuW2> z4j_h@K}2OpO@zuR1t%}p_;ys7T(N(lQ7df-QYEkLczQjo%&t%%EY(pNDwc;1ex2`C zk?N4qh!O3Z^W~|KAmSmNYNEI7p_{s`1mi%(g0WDZeX<=^tyl%2m{xf=Ib#`=4}7_^ z*NIRH6#$|Zi75lg^RpTeujv_B(-6UA7cpD6IkXQG1xNo@x2=Iy%Nayq0AOkG#Or-Y z6)3?=p+IU)DF&EzR8la@z$lu=_V?D+y(a9T`-Zb*qcovOlg`y`&k%?<2!o*X;fb9C zknpa{O*xcNSt?tUPEWdUjN~i=7Q!#z!-es))}Nw)^PlW`dN7Dh>o?=fc^Q?3Y7^jK zXf{2NMgfKeYnper4B+ssJAY??yKyPO0DoOLHl$4l=d@UP`%GyT2rwd!LBOj`;JP_d_bYg(ABy! zKOIbwjYY$1YemgjI+cN|f7aoz#!W{Nm!lPd#vq~sArOcAPgdfrX|TE^b%|>>(iSE! zVI?nJypk<+Mjcc8%%m5>Wt5^ZkR!MA=i(<)k%Y1A8% zR)i6ujsO-fY=u?^;FhXr^q2VRW#Ihy8Ma?{z167}ojwSYiZlqP^HOgYYCExF6v8z& z0w6`58_9x9GdX_(9LfX;hrA)ebZ01_hwR>o#1#t(j8NHx!Xcfw4Q4BMl^HLTce`2^ zFCSah(gyb*#x?Z=giP0guXcRb`~KQ741=Cz=e6|>?t^7Q>acACa^eIBnnr*3u_Cpl zQrxwS>asy8>?)6J9g9D&ymP)lOzn5n^QNR4i~#O{%dnec;= zZ3C2>9+lr%>@@0>rrb>DJbL$alqtxPg^SYU88NErnDbcwup3nJP+A_@HbUsTXnxGl z#m*`6?RA)A?Aq5pKqfzn`WwXe-9+=UC9jWqqTykxFl#enXZwe@<_IJmH2=#z$hl)G zm@1InF+RLRZ5qKy0OIEabF>%@WSHB$r+E-zo<^lm^M%($hgwM|X|09Hsk`t(`v$j$ zH#TY+rI-6#2JPIu{W9t;=jxug*F|m`C{6sJ8PH7=M#G6P7kAEM-WagsP~lW3?Qm7* zdE(w($gO>@LEcNR^tTS)Z~eUgI^JF0FiP?EacoN#Hjj!)k(G<6+{ORFHUJj^nd3VP-1$+8=K|gH zh-bdv0_H~r&d3kxw&CS^D_L=wCuz{UBcT!thoAD_GYL@)! zN-2QN+qZ8|U$}5VIyyR}s;XLgdymSQGiPM)-o0X)W+n>{FL?%RHXAuPIZ3bAD^L7% zH#JqItY}!qOD`SdD_?ztii!#f3JNmrj>X0`7VDTyCJ~XT)2C0T+?>;!0>gajo+N$e z&QjwKa`MgBnV6W&IfoNl6qYMrX%6v9G zJuRCyZ4y82uxeNs|VBGuK^qLdQgFi`wq#q*U? z2kYwUBoqosUtiyX0HdR$($>}{_4V~qR#ql}1$_U*cESBVpKqgK7__#w($v(nAhE~e zVcWKCjEsyB3Wd;hU0ol|wb=sX0}ls-K{K6Bvw8DobbWF2B^(YD2n4X(?GzOiab5f~ z*#flay8iL<@^XT~AgflbTJ*aW7Z=mo+KOQq`2GIO@p{0P4Zs>**IgwgC9GVz5|77| zRq?d9x6{_vhRfy39902%iyg%Zs7@pj*zI=0;c!;sbMA6#$W^|>`>Wv)w6A24{Me}LUTe-WjirKJVK zn196gbUjN`(QztEZR9!?TiYFMy;rBXUKPD}RgBmAlP_K$)i(e%1*4Oa*Rj$5%-8RC zd}GaRHlJxzh=8s`sn6ih@d;l3<5B#6KP@dSb1y9^i@zS(ONIL_mRFjztu(0%=;)fl zk)!z6Rm(i`x8w`DPDJFUE|^*O@HSQ%w};AgbPX~NmIcd$IuES36OYH(zkffGNMxR^ zDMdOlfuSobYbHk8nz^o~pt;`Gux^EIYdVd(r46kav8joOTuY_rUPiXuox^>tKEBwo z1*g+FclD2rLhLd&V|JZb0u2?q@!7TJqngHYw?o|ku+3D0({AEWYIa}|@aAhgvI+FW zyG%~a&cM*n5Wm^;G`ZR(bX}Wuf$PuA_@qo~ieyH|0LXP|Se7jFdo{nI^M3j)x#HEZ0wDj6kl}VamHqwq)nItsVqbUsNM}#{2Sa1lpMm78Nf?Oam=wP;z`NBceVNBI~#0YN~Itrue ProfileIcons\%(RecursiveDir)%(FileName)%(Extension) + + + true + ProfileGeneratorIcons\%(RecursiveDir)%(FileName)%(Extension) + true diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml index b278f71ae6..3ea92ad0f6 100644 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml +++ b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml @@ -59,7 +59,7 @@ + IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" /> diff --git a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml index 7655deb559..8f26f152ba 100644 --- a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml +++ b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml @@ -1204,7 +1204,7 @@ - + @@ -1227,7 +1227,8 @@ Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" /> - + + diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp new file mode 100644 index 0000000000..ffaff9a33d --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -0,0 +1,509 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "Extensions.h" +#include "Extensions.g.cpp" +#include "ExtensionPackageViewModel.g.cpp" +#include "ExtensionsViewModel.g.cpp" +#include "FragmentProfileViewModel.g.cpp" +#include "ExtensionPackageTemplateSelector.g.cpp" + +#include +#include "..\WinRTUtils\inc\Utils.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml::Navigation; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + static constexpr std::wstring_view ExtensionPageId{ L"page.extensions" }; + + Extensions::Extensions() + { + InitializeComponent(); + + _extensionPackageIdentifierTemplateSelector = Resources().Lookup(box_value(L"ExtensionPackageIdentifierTemplateSelector")).as(); + + Automation::AutomationProperties::SetName(ActiveExtensionsList(), RS_(L"Extensions_ActiveExtensionsHeader/Text")); + Automation::AutomationProperties::SetName(ModifiedProfilesList(), RS_(L"Extensions_ModifiedProfilesHeader/Text")); + Automation::AutomationProperties::SetName(AddedProfilesList(), RS_(L"Extensions_AddedProfilesHeader/Text")); + Automation::AutomationProperties::SetName(AddedColorSchemesList(), RS_(L"Extensions_AddedColorSchemesHeader/Text")); + } + + void Extensions::OnNavigatedTo(const NavigationEventArgs& e) + { + _ViewModel = e.Parameter().as(); + auto vmImpl = get_self(_ViewModel); + vmImpl->ExtensionPackageIdentifierTemplateSelector(_extensionPackageIdentifierTemplateSelector); + vmImpl->LazyLoadExtensions(); + vmImpl->MarkAsVisited(); + } + + void Extensions::ExtensionNavigator_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/) + { + const auto extPkgVM = sender.as().Tag().as(); + _ViewModel.CurrentExtensionPackage(extPkgVM); + } + + void Extensions::NavigateToProfile_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/) + { + const auto& profileGuid = sender.as().Tag().as(); + get_self(_ViewModel)->NavigateToProfile(profileGuid); + } + + void Extensions::NavigateToColorScheme_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/) + { + const auto& schemeVM = sender.as().Tag().as(); + get_self(_ViewModel)->NavigateToColorScheme(schemeVM); + } + + ExtensionsViewModel::ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM) : + _settings{ settings }, + _colorSchemesPageVM{ colorSchemesPageVM }, + _extensionsLoaded{ false } + { + UpdateSettings(settings, colorSchemesPageVM); + + PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { + const auto viewModelProperty{ args.PropertyName() }; + + const bool extensionPackageChanged = viewModelProperty == L"CurrentExtensionPackage"; + const bool profilesModifiedChanged = viewModelProperty == L"ProfilesModified"; + const bool profilesAddedChanged = viewModelProperty == L"ProfilesAdded"; + const bool colorSchemesAddedChanged = viewModelProperty == L"ColorSchemesAdded"; + if (extensionPackageChanged || (!IsExtensionView() && (profilesModifiedChanged || profilesAddedChanged || colorSchemesAddedChanged))) + { + // Use these booleans to track which of our observable vectors need to be refreshed. + // This prevents a full refresh of the UI when enabling/disabling extensions. + // If the CurrentExtensionPackage changed, we want to update all components. + // Otherwise, just update the ones that we were notified about. + const bool updateProfilesModified = extensionPackageChanged || profilesModifiedChanged; + const bool updateProfilesAdded = extensionPackageChanged || profilesAddedChanged; + const bool updateColorSchemesAdded = extensionPackageChanged || colorSchemesAddedChanged; + _UpdateListViews(updateProfilesModified, updateProfilesAdded, updateColorSchemesAdded); + + if (extensionPackageChanged) + { + _NotifyChanges(L"IsExtensionView", L"CurrentExtensionPackageIdentifierTemplate"); + } + else if (profilesModifiedChanged) + { + _NotifyChanges(L"NoProfilesModified"); + } + else if (profilesAddedChanged) + { + _NotifyChanges(L"NoProfilesAdded"); + } + else if (colorSchemesAddedChanged) + { + _NotifyChanges(L"NoSchemesAdded"); + } + } + }); + } + + void ExtensionsViewModel::_UpdateListViews(bool updateProfilesModified, bool updateProfilesAdded, bool updateColorSchemesAdded) + { + // STL vectors to track relevant components for extensions to display in UI + std::vector profilesModifiedTotal; + std::vector profilesAddedTotal; + std::vector colorSchemesAddedTotal; + + // Helper lambda to add the contents of an extension package to the current view. + auto addPackageContentsToView = [&](const Editor::ExtensionPackageViewModel& extPkg) { + auto extPkgVM = get_self(extPkg); + for (const auto& ext : extPkgVM->FragmentExtensions()) + { + if (updateProfilesModified) + { + for (const auto& profile : ext.ProfilesModified()) + { + profilesModifiedTotal.push_back(profile); + } + } + if (updateProfilesAdded) + { + for (const auto& profile : ext.ProfilesAdded()) + { + profilesAddedTotal.push_back(profile); + } + } + if (updateColorSchemesAdded) + { + for (const auto& scheme : ext.ColorSchemesAdded()) + { + colorSchemesAddedTotal.push_back(scheme); + } + } + } + }; + + // Populate the STL vectors that we want to update + if (const auto currentExtensionPackage = CurrentExtensionPackage()) + { + // Update all of the views to reflect the current extension package, if one is selected. + addPackageContentsToView(currentExtensionPackage); + } + else + { + // Only populate the views with components from enabled extensions + for (const auto& extPkg : _extensionPackages) + { + if (extPkg.Enabled()) + { + addPackageContentsToView(extPkg); + } + } + } + + // Sort the lists linguistically for nicer presentation. + // Update the WinRT lists bound to UI. + if (updateProfilesModified) + { + std::sort(profilesModifiedTotal.begin(), profilesModifiedTotal.end(), FragmentProfileViewModel::SortAscending); + _profilesModifiedView = winrt::single_threaded_observable_vector(std::move(profilesModifiedTotal)); + } + if (updateProfilesAdded) + { + std::sort(profilesAddedTotal.begin(), profilesAddedTotal.end(), FragmentProfileViewModel::SortAscending); + _profilesAddedView = winrt::single_threaded_observable_vector(std::move(profilesAddedTotal)); + } + if (updateColorSchemesAdded) + { + std::sort(colorSchemesAddedTotal.begin(), colorSchemesAddedTotal.end(), FragmentColorSchemeViewModel::SortAscending); + _colorSchemesAddedView = winrt::single_threaded_observable_vector(std::move(colorSchemesAddedTotal)); + } + } + + void ExtensionsViewModel::UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM) + { + _settings = settings; + _colorSchemesPageVM = colorSchemesPageVM; + _CurrentExtensionPackage = nullptr; + + // The extension packages may not be loaded yet because we want to wait until we actually navigate to the page to do so. + // In that case, omit "updating" them. They'll get the proper references when we lazy load them. + if (_extensionPackages) + { + for (const auto& extPkg : _extensionPackages) + { + get_self(extPkg)->UpdateSettings(_settings); + } + } + } + + void ExtensionsViewModel::LazyLoadExtensions() + { + if (_extensionsLoaded) + { + return; + } + std::vector extensions = wil::to_vector(_settings.Extensions()); + + // these vectors track components all extensions successfully added + std::vector extensionPackages; + std::vector profilesModifiedTotal; + std::vector profilesAddedTotal; + std::vector colorSchemesAddedTotal; + for (const auto& extPkg : extensions) + { + auto extPkgVM = winrt::make_self(extPkg, _settings); + for (const auto& fragExt : extPkg.FragmentsView()) + { + const auto extensionEnabled = GetExtensionState(fragExt.Source(), _settings); + + // these vectors track everything the current extension attempted to bring in + std::vector currentProfilesModified; + std::vector currentProfilesAdded; + std::vector currentColorSchemesAdded; + + if (fragExt.ModifiedProfilesView()) + { + for (const auto&& entry : fragExt.ModifiedProfilesView()) + { + // Ensure entry successfully modifies a profile before creating and registering the object + if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) + { + auto vm = winrt::make(entry, fragExt, deducedProfile); + currentProfilesModified.push_back(vm); + if (extensionEnabled) + { + profilesModifiedTotal.push_back(vm); + } + } + } + } + + if (fragExt.NewProfilesView()) + { + for (const auto&& entry : fragExt.NewProfilesView()) + { + // Ensure entry successfully points to a profile before creating and registering the object. + // The profile may have been removed by the user. + if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) + { + auto vm = winrt::make(entry, fragExt, deducedProfile); + currentProfilesAdded.push_back(vm); + if (extensionEnabled) + { + profilesAddedTotal.push_back(vm); + } + } + } + } + + if (fragExt.ColorSchemesView()) + { + for (const auto&& entry : fragExt.ColorSchemesView()) + { + for (const auto& schemeVM : _colorSchemesPageVM.AllColorSchemes()) + { + if (schemeVM.Name() == entry.ColorSchemeName()) + { + auto vm = winrt::make(entry, fragExt, schemeVM); + currentColorSchemesAdded.push_back(vm); + if (extensionEnabled) + { + colorSchemesAddedTotal.push_back(vm); + } + } + } + } + } + + // sort the lists linguistically for nicer presentation + std::sort(currentProfilesModified.begin(), currentProfilesModified.end(), FragmentProfileViewModel::SortAscending); + std::sort(currentProfilesAdded.begin(), currentProfilesAdded.end(), FragmentProfileViewModel::SortAscending); + std::sort(currentColorSchemesAdded.begin(), currentColorSchemesAdded.end(), FragmentColorSchemeViewModel::SortAscending); + + extPkgVM->FragmentExtensions().Append(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded)); + extPkgVM->PropertyChanged([&](const IInspectable& sender, const PropertyChangedEventArgs& args) { + const auto viewModelProperty{ args.PropertyName() }; + if (viewModelProperty == L"Enabled") + { + // If the extension was enabled/disabled, + // check if any of its fragments modified profiles, added profiles, or added color schemes. + // Only notify what was affected! + bool hasModifiedProfiles = false; + bool hasAddedProfiles = false; + bool hasAddedColorSchemes = false; + for (const auto& fragExtVM : sender.as()->FragmentExtensions()) + { + const auto profilesModified = fragExtVM.ProfilesModified(); + const auto profilesAdded = fragExtVM.ProfilesAdded(); + const auto colorSchemesAdded = fragExtVM.ColorSchemesAdded(); + hasModifiedProfiles |= profilesModified && profilesModified.Size() > 0; + hasAddedProfiles |= profilesAdded && profilesAdded.Size() > 0; + hasAddedColorSchemes |= colorSchemesAdded && colorSchemesAdded.Size() > 0; + } + if (hasModifiedProfiles) + { + _NotifyChanges(L"ProfilesModified"); + } + if (hasAddedProfiles) + { + _NotifyChanges(L"ProfilesAdded"); + } + if (hasAddedColorSchemes) + { + _NotifyChanges(L"ColorSchemesAdded"); + } + } + }); + } + extensionPackages.push_back(*extPkgVM); + } + + // sort the lists linguistically for nicer presentation + std::sort(extensionPackages.begin(), extensionPackages.end(), ExtensionPackageViewModel::SortAscending); + std::sort(profilesModifiedTotal.begin(), profilesModifiedTotal.end(), FragmentProfileViewModel::SortAscending); + std::sort(profilesAddedTotal.begin(), profilesAddedTotal.end(), FragmentProfileViewModel::SortAscending); + std::sort(colorSchemesAddedTotal.begin(), colorSchemesAddedTotal.end(), FragmentColorSchemeViewModel::SortAscending); + + _extensionPackages = single_threaded_observable_vector(std::move(extensionPackages)); + _profilesModifiedView = single_threaded_observable_vector(std::move(profilesModifiedTotal)); + _profilesAddedView = single_threaded_observable_vector(std::move(profilesAddedTotal)); + _colorSchemesAddedView = single_threaded_observable_vector(std::move(colorSchemesAddedTotal)); + _extensionsLoaded = true; + } + + Windows::UI::Xaml::DataTemplate ExtensionsViewModel::CurrentExtensionPackageIdentifierTemplate() const + { + return _ExtensionPackageIdentifierTemplateSelector.SelectTemplate(CurrentExtensionPackage()); + } + + bool ExtensionsViewModel::DisplayBadge() const noexcept + { + return !Model::ApplicationState::SharedInstance().BadgeDismissed(ExtensionPageId); + } + + // Returns true if the extension is enabled, false otherwise + bool ExtensionsViewModel::GetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings) + { + if (const auto& disabledExtensions = settings.GlobalSettings().DisabledProfileSources()) + { + uint32_t ignored; + return !disabledExtensions.IndexOf(extensionSource, ignored); + } + // "disabledProfileSources" not defined --> all extensions are enabled + return true; + } + + // Enable/Disable an extension + void ExtensionsViewModel::SetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings, bool enableExt) + { + // get the current status of the extension + uint32_t idx; + bool currentlyEnabled = true; + const auto& disabledExtensions = settings.GlobalSettings().DisabledProfileSources(); + if (disabledExtensions) + { + currentlyEnabled = !disabledExtensions.IndexOf(extensionSource, idx); + } + + // current status mismatches the desired status, + // update the list of disabled extensions + if (currentlyEnabled != enableExt) + { + // If we're disabling an extension and we don't have "disabledProfileSources" defined, + // create it in the model directly + if (!disabledExtensions && !enableExt) + { + std::vector disabledProfileSources{ extensionSource }; + settings.GlobalSettings().DisabledProfileSources(single_threaded_vector(std::move(disabledProfileSources))); + return; + } + + // Update the list of disabled extensions + if (enableExt) + { + disabledExtensions.RemoveAt(idx); + } + else + { + disabledExtensions.Append(extensionSource); + } + } + } + + Thickness Extensions::CalculateMargin(bool hidden) + { + return ThicknessHelper::FromLengths(/*left*/ 0, + /*top*/ hidden ? 0 : 20, + /*right*/ 0, + /*bottom*/ 0); + } + + void ExtensionsViewModel::NavigateToProfile(const guid profileGuid) + { + NavigateToProfileRequested.raise(*this, profileGuid); + } + + void ExtensionsViewModel::NavigateToColorScheme(const Editor::ColorSchemeViewModel& schemeVM) + { + _colorSchemesPageVM.CurrentScheme(schemeVM); + NavigateToColorSchemeRequested.raise(*this, nullptr); + } + + void ExtensionsViewModel::MarkAsVisited() + { + Model::ApplicationState::SharedInstance().DismissBadge(ExtensionPageId); + _NotifyChanges(L"DisplayBadge"); + } + + bool ExtensionPackageViewModel::SortAscending(const Editor::ExtensionPackageViewModel& lhs, const Editor::ExtensionPackageViewModel& rhs) + { + auto getKey = [&](const Editor::ExtensionPackageViewModel& pkgVM) { + const auto pkg = pkgVM.Package(); + const auto displayName = pkg.DisplayName(); + return displayName.empty() ? pkg.Source() : displayName; + }; + + return til::compare_linguistic_insensitive(getKey(lhs), getKey(rhs)) < 0; + } + + void ExtensionPackageViewModel::UpdateSettings(const Model::CascadiaSettings& settings) + { + const auto oldEnabled = Enabled(); + _settings = settings; + if (oldEnabled != Enabled()) + { + // The enabled state of the extension has changed, notify the UI + _NotifyChanges(L"Enabled"); + } + } + + hstring ExtensionPackageViewModel::Scope() const noexcept + { + return _package.Scope() == Model::FragmentScope::User ? RS_(L"Extensions_ScopeUser") : RS_(L"Extensions_ScopeSystem"); + } + + bool ExtensionPackageViewModel::Enabled() const + { + return ExtensionsViewModel::GetExtensionState(_package.Source(), _settings); + } + + void ExtensionPackageViewModel::Enabled(bool val) + { + if (Enabled() != val) + { + ExtensionsViewModel::SetExtensionState(_package.Source(), _settings, val); + _NotifyChanges(L"Enabled"); + } + } + + // Returns the accessible name for the extension package in the following format: + // ", " + hstring ExtensionPackageViewModel::AccessibleName() const noexcept + { + hstring name; + const auto source = _package.Source(); + if (const auto displayName = _package.DisplayName(); !displayName.empty()) + { + return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), displayName, source) }; + } + return source; + } + + bool FragmentProfileViewModel::SortAscending(const Editor::FragmentProfileViewModel& lhs, const Editor::FragmentProfileViewModel& rhs) + { + return til::compare_linguistic_insensitive(lhs.Profile().Name(), rhs.Profile().Name()) < 0; + } + + hstring FragmentProfileViewModel::AccessibleName() const noexcept + { + return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), Profile().Name(), SourceName()) }; + } + + bool FragmentColorSchemeViewModel::SortAscending(const Editor::FragmentColorSchemeViewModel& lhs, const Editor::FragmentColorSchemeViewModel& rhs) + { + return til::compare_linguistic_insensitive(lhs.ColorSchemeVM().Name(), rhs.ColorSchemeVM().Name()) < 0; + } + + hstring FragmentColorSchemeViewModel::AccessibleName() const noexcept + { + return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), ColorSchemeVM().Name(), SourceName()) }; + } + + DataTemplate ExtensionPackageTemplateSelector::SelectTemplateCore(const IInspectable& item, const DependencyObject& /*container*/) + { + return SelectTemplateCore(item); + } + + DataTemplate ExtensionPackageTemplateSelector::SelectTemplateCore(const IInspectable& item) + { + if (const auto extPkgVM = item.try_as()) + { + if (!extPkgVM.Package().DisplayName().empty()) + { + return ComplexTemplate(); + } + return DefaultTemplate(); + } + return nullptr; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h new file mode 100644 index 0000000000..6b7eafad57 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.h @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "Extensions.g.h" +#include "ExtensionsViewModel.g.h" +#include "ExtensionPackageViewModel.g.h" +#include "FragmentExtensionViewModel.g.h" +#include "FragmentProfileViewModel.g.h" +#include "FragmentColorSchemeViewModel.g.h" +#include "ExtensionPackageTemplateSelector.g.h" +#include "ViewModelHelpers.h" +#include "Utils.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct Extensions : public HasScrollViewer, ExtensionsT + { + public: + Windows::UI::Xaml::Thickness CalculateMargin(bool hidden); + + Extensions(); + + void OnNavigatedTo(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e); + + void ExtensionNavigator_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + void NavigateToProfile_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + void NavigateToColorScheme_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + + WINRT_PROPERTY(Editor::ExtensionsViewModel, ViewModel, nullptr); + + private: + Editor::ExtensionPackageTemplateSelector _extensionPackageIdentifierTemplateSelector; + }; + + struct ExtensionsViewModel : ExtensionsViewModelT, ViewModelHelper + { + public: + ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM); + + // Properties + Windows::UI::Xaml::DataTemplate CurrentExtensionPackageIdentifierTemplate() const; + bool IsExtensionView() const noexcept { return _CurrentExtensionPackage != nullptr; } + bool NoExtensionPackages() const noexcept { return _extensionPackages.Size() == 0; } + bool NoProfilesModified() const noexcept { return _profilesModifiedView.Size() == 0; } + bool NoProfilesAdded() const noexcept { return _profilesAddedView.Size() == 0; } + bool NoSchemesAdded() const noexcept { return _colorSchemesAddedView.Size() == 0; } + bool DisplayBadge() const noexcept; + + // Views + Windows::Foundation::Collections::IObservableVector ExtensionPackages() const noexcept { return _extensionPackages; } + Windows::Foundation::Collections::IObservableVector ProfilesModified() const noexcept { return _profilesModifiedView; } + Windows::Foundation::Collections::IObservableVector ProfilesAdded() const noexcept { return _profilesAddedView; } + Windows::Foundation::Collections::IObservableVector ColorSchemesAdded() const noexcept { return _colorSchemesAddedView; } + + // Methods + void LazyLoadExtensions(); + void UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM); + void NavigateToProfile(const guid profileGuid); + void NavigateToColorScheme(const Editor::ColorSchemeViewModel& schemeVM); + void MarkAsVisited(); + + static bool GetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings); + static void SetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings, bool enableExt); + + til::typed_event NavigateToProfileRequested; + til::typed_event NavigateToColorSchemeRequested; + + VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ExtensionPackageViewModel, CurrentExtensionPackage, nullptr); + WINRT_PROPERTY(Editor::ExtensionPackageTemplateSelector, ExtensionPackageIdentifierTemplateSelector, nullptr); + + private: + Model::CascadiaSettings _settings; + Editor::ColorSchemesPageViewModel _colorSchemesPageVM; + Windows::Foundation::Collections::IObservableVector _extensionPackages; + Windows::Foundation::Collections::IObservableVector _profilesModifiedView; + Windows::Foundation::Collections::IObservableVector _profilesAddedView; + Windows::Foundation::Collections::IObservableVector _colorSchemesAddedView; + bool _extensionsLoaded; + + void _UpdateListViews(bool updateProfilesModified, bool updateProfilesAdded, bool updateColorSchemesAdded); + }; + + struct ExtensionPackageViewModel : ExtensionPackageViewModelT, ViewModelHelper + { + public: + ExtensionPackageViewModel(const Model::ExtensionPackage& pkg, const Model::CascadiaSettings& settings) : + _package{ pkg }, + _settings{ settings }, + _fragmentExtensions{ single_threaded_observable_vector() } {} + + static bool SortAscending(const Editor::ExtensionPackageViewModel& lhs, const Editor::ExtensionPackageViewModel& rhs); + + void UpdateSettings(const Model::CascadiaSettings& settings); + + Model::ExtensionPackage Package() const noexcept { return _package; } + hstring Scope() const noexcept; + bool Enabled() const; + void Enabled(bool val); + hstring AccessibleName() const noexcept; + Windows::Foundation::Collections::IObservableVector FragmentExtensions() { return _fragmentExtensions; } + + private: + Model::ExtensionPackage _package; + Model::CascadiaSettings _settings; + Windows::Foundation::Collections::IObservableVector _fragmentExtensions; + }; + + struct FragmentExtensionViewModel : FragmentExtensionViewModelT, ViewModelHelper + { + public: + FragmentExtensionViewModel(const Model::FragmentSettings& fragment, + std::vector& profilesModified, + std::vector& profilesAdded, + std::vector& colorSchemesAdded) : + _fragment{ fragment }, + _profilesModified{ single_threaded_vector(std::move(profilesModified)) }, + _profilesAdded{ single_threaded_vector(std::move(profilesAdded)) }, + _colorSchemesAdded{ single_threaded_vector(std::move(colorSchemesAdded)) } {} + + Model::FragmentSettings Fragment() const noexcept { return _fragment; } + Windows::Foundation::Collections::IVectorView ProfilesModified() const noexcept { return _profilesModified.GetView(); } + Windows::Foundation::Collections::IVectorView ProfilesAdded() const noexcept { return _profilesAdded.GetView(); } + Windows::Foundation::Collections::IVectorView ColorSchemesAdded() const noexcept { return _colorSchemesAdded.GetView(); } + + private: + Model::FragmentSettings _fragment; + Windows::Foundation::Collections::IVector _profilesModified; + Windows::Foundation::Collections::IVector _profilesAdded; + Windows::Foundation::Collections::IVector _colorSchemesAdded; + }; + + struct FragmentProfileViewModel : FragmentProfileViewModelT, ViewModelHelper + { + public: + FragmentProfileViewModel(const Model::FragmentProfileEntry& entry, const Model::FragmentSettings& fragment, const Model::Profile& deducedProfile) : + _entry{ entry }, + _fragment{ fragment }, + _deducedProfile{ deducedProfile } {} + + static bool SortAscending(const Editor::FragmentProfileViewModel& lhs, const Editor::FragmentProfileViewModel& rhs); + + Model::Profile Profile() const { return _deducedProfile; }; + hstring SourceName() const { return _fragment.Source(); } + hstring Json() const { return _entry.Json(); } + hstring AccessibleName() const noexcept; + + private: + Model::FragmentProfileEntry _entry; + Model::FragmentSettings _fragment; + Model::Profile _deducedProfile; + }; + + struct FragmentColorSchemeViewModel : FragmentColorSchemeViewModelT, ViewModelHelper + { + public: + FragmentColorSchemeViewModel(const Model::FragmentColorSchemeEntry& entry, const Model::FragmentSettings& fragment, const Editor::ColorSchemeViewModel& deducedSchemeVM) : + _entry{ entry }, + _fragment{ fragment }, + _deducedSchemeVM{ deducedSchemeVM } {} + + static bool SortAscending(const Editor::FragmentColorSchemeViewModel& lhs, const Editor::FragmentColorSchemeViewModel& rhs); + + Editor::ColorSchemeViewModel ColorSchemeVM() const { return _deducedSchemeVM; }; + hstring SourceName() const { return _fragment.Source(); } + hstring Json() const { return _entry.Json(); } + hstring AccessibleName() const noexcept; + + private: + Model::FragmentColorSchemeEntry _entry; + Model::FragmentSettings _fragment; + Editor::ColorSchemeViewModel _deducedSchemeVM; + }; + + struct ExtensionPackageTemplateSelector : public ExtensionPackageTemplateSelectorT + { + public: + ExtensionPackageTemplateSelector() = default; + + Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container); + Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item); + + WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, DefaultTemplate, nullptr); + WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ComplexTemplate, nullptr); + }; +}; + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + BASIC_FACTORY(Extensions); + BASIC_FACTORY(ExtensionPackageTemplateSelector); +} diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.idl b/src/cascadia/TerminalSettingsEditor/Extensions.idl new file mode 100644 index 0000000000..4dc3018c36 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "ColorSchemesPageViewModel.idl"; + +namespace Microsoft.Terminal.Settings.Editor +{ + [default_interface] runtimeclass Extensions : Windows.UI.Xaml.Controls.Page + { + Extensions(); + ExtensionsViewModel ViewModel { get; }; + + Windows.UI.Xaml.Thickness CalculateMargin(Boolean hidden); + } + + [default_interface] runtimeclass ExtensionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + // Properties + ExtensionPackageViewModel CurrentExtensionPackage; + Windows.UI.Xaml.DataTemplate CurrentExtensionPackageIdentifierTemplate { get; }; + Boolean IsExtensionView { get; }; + Boolean NoExtensionPackages { get; }; + Boolean NoProfilesModified { get; }; + Boolean NoProfilesAdded { get; }; + Boolean NoSchemesAdded { get; }; + Boolean DisplayBadge { get; }; + + // Views + IVector ExtensionPackages { get; }; + IObservableVector ProfilesModified { get; }; + IObservableVector ProfilesAdded { get; }; + IObservableVector ColorSchemesAdded { get; }; + + // Methods + void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, ColorSchemesPageViewModel colorSchemesPageVM); + + event Windows.Foundation.TypedEventHandler NavigateToProfileRequested; + event Windows.Foundation.TypedEventHandler NavigateToColorSchemeRequested; + } + + [default_interface] runtimeclass ExtensionPackageViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + Microsoft.Terminal.Settings.Model.ExtensionPackage Package { get; }; + Boolean Enabled; + String Scope { get; }; + String AccessibleName { get; }; + IVector FragmentExtensions { get; }; + } + + [default_interface] runtimeclass FragmentExtensionViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + Microsoft.Terminal.Settings.Model.FragmentSettings Fragment { get; }; + IVectorView ProfilesModified { get; }; + IVectorView ProfilesAdded { get; }; + IVectorView ColorSchemesAdded { get; }; + } + + [default_interface] runtimeclass FragmentProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + Microsoft.Terminal.Settings.Model.Profile Profile { get; }; + String SourceName { get; }; + String Json { get; }; + String AccessibleName { get; }; + } + + [default_interface] runtimeclass FragmentColorSchemeViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + ColorSchemeViewModel ColorSchemeVM { get; }; + String SourceName { get; }; + String Json { get; }; + String AccessibleName { get; }; + } + + [default_interface] runtimeclass ExtensionPackageTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector + { + ExtensionPackageTemplateSelector(); + + Windows.UI.Xaml.DataTemplate DefaultTemplate; + Windows.UI.Xaml.DataTemplate ComplexTemplate; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml new file mode 100644 index 0000000000..eec3e69290 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 6e66fac02a..098827d9a9 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -9,6 +9,7 @@ #include "Compatibility.h" #include "Rendering.h" #include "RenderingViewModel.h" +#include "Extensions.h" #include "Actions.h" #include "ProfileViewModel.h" #include "GlobalAppearance.h" @@ -45,6 +46,7 @@ static const std::wstring_view renderingTag{ L"Rendering_Nav" }; static const std::wstring_view compatibilityTag{ L"Compatibility_Nav" }; static const std::wstring_view actionsTag{ L"Actions_Nav" }; static const std::wstring_view newTabMenuTag{ L"NewTabMenu_Nav" }; +static const std::wstring_view extensionsTag{ L"Extensions_Nav" }; static const std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" }; static const std::wstring_view addProfileTag{ L"AddProfile" }; static const std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" }; @@ -112,6 +114,33 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } }); + auto extensionsVMImpl = winrt::make_self(_settingsClone, _colorSchemesPageVM); + extensionsVMImpl->NavigateToProfileRequested({ this, &MainPage::_NavigateToProfileHandler }); + extensionsVMImpl->NavigateToColorSchemeRequested({ this, &MainPage::_NavigateToColorSchemeHandler }); + _extensionsVM = *extensionsVMImpl; + _extensionsViewModelChangedRevoker = _extensionsVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { + const auto settingName{ args.PropertyName() }; + if (settingName == L"CurrentExtensionPackage") + { + if (const auto& currentExtensionPackage = _extensionsVM.CurrentExtensionPackage()) + { + const auto& pkg = currentExtensionPackage.Package(); + const auto label = pkg.DisplayName().empty() ? pkg.Source() : pkg.DisplayName(); + const auto crumb = winrt::make(box_value(currentExtensionPackage), label, BreadcrumbSubPage::Extensions_Extension); + _breadcrumbs.Append(crumb); + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); + } + else + { + // If we don't have a current extension package, we're at the root of the Extensions page + _breadcrumbs.Clear(); + const auto crumb = winrt::make(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + } + contentFrame().Navigate(xaml_typename(), _extensionsVM); + } + }); + // Make sure to initialize the profiles _after_ we have initialized the color schemes page VM, because we pass // that VM into the appearance VMs within the profiles _InitializeProfilesList(); @@ -162,6 +191,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Update the Nav State with the new version of the settings _colorSchemesPageVM.UpdateSettings(_settingsClone); _newTabMenuPageVM.UpdateSettings(_settingsClone); + _extensionsVM.UpdateSettings(_settingsClone, _colorSchemesPageVM); // We'll update the profile in the _profilesNavState whenever we actually navigate to one @@ -183,7 +213,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { // found the one that was selected before the refresh SettingsNav().SelectedItem(item); - _Navigate(*stringTag, crumb->SubPage()); + _Navigate(*breadcrumbStringTag, crumb->SubPage()); return; } } @@ -198,6 +228,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return; } } + else if (const auto& breadcrumbExtensionPackage{ crumb->Tag().try_as() }) + { + if (stringTag == extensionsTag) + { + // navigate to the Extensions page, + // _Navigate() will handle trying to find the right subpage + SettingsNav().SelectedItem(item); + _Navigate(breadcrumbExtensionPackage, BreadcrumbSubPage::Extensions_Extension); + return; + } + } } else if (const auto& profileTag{ tag.try_as() }) { @@ -457,6 +498,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _breadcrumbs.Append(crumb); } } + else if (clickedItemTag == extensionsTag) + { + if (_extensionsVM.CurrentExtensionPackage()) + { + // Setting CurrentExtensionPackage triggers the PropertyChanged event, + // which will navigate to the correct page and update the breadcrumbs appropriately + _extensionsVM.CurrentExtensionPackage(nullptr); + } + else + { + contentFrame().Navigate(xaml_typename(), _extensionsVM); + const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + } + } else if (clickedItemTag == globalProfileTag) { auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone, Dispatcher()) }; @@ -587,6 +643,40 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void MainPage::_Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage) + { + _PreNavigateHelper(); + + contentFrame().Navigate(xaml_typename(), _extensionsVM); + const auto crumb = winrt::make(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + + if (subPage == BreadcrumbSubPage::None) + { + _extensionsVM.CurrentExtensionPackage(nullptr); + } + else + { + bool found = false; + for (const auto& pkgVM : _extensionsVM.ExtensionPackages()) + { + if (pkgVM.Package().Source() == extPkgVM.Package().Source()) + { + // Take advantage of the PropertyChanged event to navigate + // to the correct extension package and build the breadcrumbs as we go + _extensionsVM.CurrentExtensionPackage(pkgVM); + found = true; + break; + } + } + if (!found) + { + // If we couldn't find a reasonable match, just go back to the root + _extensionsVM.CurrentExtensionPackage(nullptr); + } + } + } + void MainPage::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) { _settingsClone.LogSettingChanges(false); @@ -612,6 +702,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _Navigate(*ntmEntryViewModel, subPage); } + else if (const auto extPkgViewModel = tag.try_as()) + { + _Navigate(*extPkgViewModel, subPage); + } else { _Navigate(tag.as(), subPage); @@ -809,6 +903,35 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _breadcrumbs; } + void MainPage::_NavigateToProfileHandler(const IInspectable& /*sender*/, winrt::guid profileGuid) + { + for (auto&& menuItem : _menuItemSource) + { + if (const auto& navViewItem{ menuItem.try_as() }) + { + if (const auto& tag{ navViewItem.Tag() }) + { + if (const auto& profileTag{ tag.try_as() }) + { + if (profileTag->OriginalProfileGuid() == profileGuid) + { + SettingsNav().SelectedItem(menuItem); + _Navigate(*profileTag, BreadcrumbSubPage::None); + return; + } + } + } + } + } + // Silently fail if the profile wasn't found + } + + void MainPage::_NavigateToColorSchemeHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) + { + SettingsNav().SelectedItem(ColorSchemesNavItem()); + _Navigate(hstring{ colorSchemesTag }, BreadcrumbSubPage::ColorSchemes_Edit); + } + winrt::Windows::UI::Xaml::Media::Brush MainPage::BackgroundBrush() { return SettingsNav().Background(); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 582f8813b1..aebeb516e3 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush(); Windows::Foundation::Collections::IObservableVector Breadcrumbs() noexcept; + Editor::ExtensionsViewModel ExtensionsVM() const noexcept { return _extensionsVM; } til::typed_event OpenJson; @@ -68,16 +69,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage); void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage); void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage); + void _Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage); + void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid); + void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args); void _UpdateBackgroundForMica(); void _MoveXamlParsedNavItemsIntoItemSource(); winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr }; + winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr }; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ntmViewModelChangedRevoker; + Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _extensionsViewModelChangedRevoker; }; } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.idl b/src/cascadia/TerminalSettingsEditor/MainPage.idl index 0f5a6e49b8..390ae5cb80 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "Extensions.idl"; + namespace Microsoft.Terminal.Settings.Editor { // Due to a XAML Compiler bug, it is hard for us to propagate an HWND into a XAML-using runtimeclass. @@ -20,7 +22,8 @@ namespace Microsoft.Terminal.Settings.Editor Profile_Terminal, Profile_Advanced, ColorSchemes_Edit, - NewTabMenu_Folder + NewTabMenu_Folder, + Extensions_Extension }; runtimeclass Breadcrumb : Windows.Foundation.IStringable @@ -42,6 +45,7 @@ namespace Microsoft.Terminal.Settings.Editor void SetHostingWindow(UInt64 window); Windows.Foundation.Collections.IObservableVector Breadcrumbs { get; }; + ExtensionsViewModel ExtensionsVM { get; }; Windows.UI.Xaml.Media.Brush BackgroundBrush { get; }; } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index c6d36e8f20..9e23093f73 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -120,7 +120,8 @@ - @@ -155,6 +156,17 @@ + + + + + + + + + NewTabMenuViewModel.idl Code + + Extensions.xaml + Code + Profiles_Base.xaml Code @@ -199,6 +203,9 @@ Designer + + Designer + Designer @@ -309,6 +316,10 @@ NewTabMenuViewModel.idl Code + + Extensions.xaml + Code + Profiles_Base.xaml Code @@ -408,6 +419,10 @@ + + Extensions.xaml + Code + Profiles_Base.xaml Code diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp b/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp index 901b809f34..25b7f8ef77 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp @@ -22,8 +22,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { InitializeComponent(); - _entryTemplateSelector = Resources().Lookup(box_value(L"NewTabMenuEntryTemplateSelector")).as(); - // Ideally, we'd bind IsEnabled to something like mtu:Converters.isEmpty(NewTabMenuListView.SelectedItems.Size) in the XAML, // but the XAML compiler can't find NewTabMenuListView when we try that. Rather than copying the list of selected items over // to the view model, we'll just do this instead (much simpler). diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenu.h b/src/cascadia/TerminalSettingsEditor/NewTabMenu.h index cb54fd11b9..f305c44c88 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenu.h +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenu.h @@ -42,7 +42,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_OBSERVABLE_PROPERTY(Editor::NewTabMenuViewModel, ViewModel, _PropertyChangedHandlers, nullptr); private: - Editor::NewTabMenuEntryTemplateSelector _entryTemplateSelector{ nullptr }; Editor::NewTabMenuEntryViewModel _draggedEntry{ nullptr }; void _ScrollToEntry(const Editor::NewTabMenuEntryViewModel& entry); diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml b/src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml index 54acab56e1..fb3273a969 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml @@ -321,7 +321,7 @@ - + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 0396faf3bc..7949d017ca 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -684,6 +684,10 @@ Actions Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensions + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Background opacity Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2344,6 +2348,49 @@ This option is managed by enterprise policy and cannot be changed here. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Active Extensions + + + Modified Profiles + + + Added Profiles + + + Added Color Schemes + + + Learn more about extensions + + + Navigate to profile + + + Navigate to profile + + + Navigate to color scheme + + + Navigate to color scheme + + + Current User + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + Scope + Header for the installation scope of the extension + + + NEW + Text is used on an info badge for new navigation items. Must be all caps. + Learn more about regular expressions diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml index c4d3c7a580..e5ac30b9bb 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml +++ b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml @@ -134,6 +134,7 @@ + + - @@ -228,6 +234,45 @@ + + + FindFragmentsAndMergeIntoUserSettings must be called after MergeInboxIntoUserSettings. - loader.FindFragmentsAndMergeIntoUserSettings(); + loader.FindFragmentsAndMergeIntoUserSettings(false /*generateExtensionPackages*/); loader.FinalizeLayering(); // DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles. diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 8a699eb865..55af170355 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -92,6 +92,14 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_NewTabMenu->Append(get_self(entry)->Copy()); } } + if (_DisabledProfileSources) + { + globals->_DisabledProfileSources = winrt::single_threaded_vector(); + for (const auto& src : *_DisabledProfileSources) + { + globals->_DisabledProfileSources->Append(src); + } + } for (const auto& parent : _parents) { diff --git a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h index db57944b10..9144bcb325 100644 --- a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h @@ -30,6 +30,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model public: virtual ~IDynamicProfileGenerator() = default; virtual std::wstring_view GetNamespace() const noexcept = 0; + virtual std::wstring_view GetDisplayName() const noexcept = 0; + virtual std::wstring_view GetIcon() const noexcept = 0; virtual void GenerateProfiles(std::vector>& profiles) const = 0; }; }; diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp index 14878c8278..01e4c491bd 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp @@ -15,12 +15,14 @@ #include #include #include +#include static constexpr std::wstring_view POWERSHELL_PFN{ L"Microsoft.PowerShell_8wekyb3d8bbwe" }; static constexpr std::wstring_view POWERSHELL_PREVIEW_PFN{ L"Microsoft.PowerShellPreview_8wekyb3d8bbwe" }; static constexpr std::wstring_view PWSH_EXE{ L"pwsh.exe" }; static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" }; static constexpr std::wstring_view POWERSHELL_PREVIEW_ICON{ L"ms-appx:///ProfileIcons/pwsh-preview.png" }; +static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.png" }; static constexpr std::wstring_view POWERSHELL_PREFERRED_PROFILE_NAME{ L"PowerShell" }; namespace @@ -294,6 +296,16 @@ std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept return PowershellCoreGeneratorNamespace; } +std::wstring_view PowershellCoreProfileGenerator::GetDisplayName() const noexcept +{ + return RS_(L"PowershellCoreProfileGeneratorDisplayName"); +} + +std::wstring_view PowershellCoreProfileGenerator::GetIcon() const noexcept +{ + return GENERATOR_POWERSHELL_ICON; +} + // Method Description: // - Checks if pwsh is installed, and if it is, creates a profile to launch it. // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h index eaec9dacea..d473292e15 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h @@ -26,6 +26,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model static const std::wstring_view GetPreferredPowershellProfileName(); std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; void GenerateProfiles(std::vector>& profiles) const override; }; }; diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index b1c24a54d0..a0126304df 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -1,17 +1,17 @@ - @@ -740,4 +740,24 @@ Open current working directory - + + WSL Distribution Profile Generator + The display name of a dynamic profile generator for WSL distros + + + PowerShell Profile Generator + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell Profile Generator + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio Profile Generator + The display name of a dynamic profile generator for Visual Studio + + + SSH Host Profile Generator + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp index 30dc19039f..45711c711e 100644 --- a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp @@ -7,11 +7,13 @@ #include "../../inc/DefaultSettings.h" #include "DynamicProfileUtils.h" +#include static constexpr std::wstring_view SshHostGeneratorNamespace{ L"Windows.Terminal.SSH" }; static constexpr std::wstring_view PROFILE_TITLE_PREFIX = L"SSH - "; static constexpr std::wstring_view PROFILE_ICON_PATH = L"ms-appx:///ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.png"; +static constexpr std::wstring_view GENERATOR_ICON_PATH = L"ms-appx:///ProfileGeneratorIcons/SSH.png"; // OpenSSH is installed under System32 when installed via Optional Features static constexpr std::wstring_view SSH_EXE_PATH1 = L"%SystemRoot%\\System32\\OpenSSH\\ssh.exe"; @@ -132,6 +134,16 @@ std::wstring_view SshHostGenerator::GetNamespace() const noexcept return SshHostGeneratorNamespace; } +std::wstring_view SshHostGenerator::GetDisplayName() const noexcept +{ + return RS_(L"SshHostGeneratorDisplayName"); +} + +std::wstring_view SshHostGenerator::GetIcon() const noexcept +{ + return GENERATOR_ICON_PATH; +} + // Method Description: // - Generate a list of profiles for each detected OpenSSH host. // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h index a355b0c430..d83e62535b 100644 --- a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h @@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model { public: std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; void GenerateProfiles(std::vector>& profiles) const override; private: diff --git a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp index 2fcfb1cc52..69feb6174e 100644 --- a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp @@ -6,16 +6,28 @@ #include "VisualStudioGenerator.h" #include "VsDevCmdGenerator.h" #include "VsDevShellGenerator.h" +#include using namespace winrt::Microsoft::Terminal::Settings::Model; std::wstring_view VisualStudioGenerator::Namespace{ L"Windows.Terminal.VisualStudio" }; +static constexpr std::wstring_view IconPath{ L"ms-appx:///ProfileGeneratorIcons/VisualStudio.png" }; std::wstring_view VisualStudioGenerator::GetNamespace() const noexcept { return Namespace; } +std::wstring_view VisualStudioGenerator::GetDisplayName() const noexcept +{ + return RS_(L"VisualStudioGeneratorDisplayName"); +} + +std::wstring_view VisualStudioGenerator::GetIcon() const noexcept +{ + return IconPath; +} + void VisualStudioGenerator::GenerateProfiles(std::vector>& profiles) const { const auto instances = VsSetupConfiguration::QueryInstances(); diff --git a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h index ef36284db7..c89e7c6301 100644 --- a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h +++ b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h @@ -28,6 +28,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model public: static std::wstring_view Namespace; std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; void GenerateProfiles(std::vector>& profiles) const override; class IVisualStudioProfileGenerator diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp index cb1d220a90..2e0c81e5a5 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp @@ -8,10 +8,13 @@ #include "../../inc/DefaultSettings.h" #include "DynamicProfileUtils.h" +#include static constexpr std::wstring_view WslHomeDirectory{ L"~" }; static constexpr std::wstring_view DockerDistributionPrefix{ L"docker-desktop" }; static constexpr std::wstring_view RancherDistributionPrefix{ L"rancher-desktop" }; +static constexpr std::wstring_view IconPath{ L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png" }; +static constexpr std::wstring_view GeneratorIconPath{ L"ms-appx:///ProfileGeneratorIcons/WSL.png" }; // The WSL entries are structured as such: // HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss @@ -47,6 +50,16 @@ std::wstring_view WslDistroGenerator::GetNamespace() const noexcept return WslGeneratorNamespace; } +std::wstring_view WslDistroGenerator::GetDisplayName() const noexcept +{ + return RS_(L"WslDistroGeneratorDisplayName"); +} + +std::wstring_view WslDistroGenerator::GetIcon() const noexcept +{ + return GeneratorIconPath; +} + static winrt::com_ptr makeProfile(const std::wstring& distName) { const auto WSLDistro{ CreateDynamicProfile(distName) }; @@ -65,7 +78,7 @@ static winrt::com_ptr makeProfile(const std::wstring& d { WSLDistro->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY }); } - WSLDistro->Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png"); + WSLDistro->Icon(winrt::hstring{ IconPath }); WSLDistro->PathTranslationStyle(winrt::Microsoft::Terminal::Control::PathTranslationStyle::WSL); return WSLDistro; } diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h index b46aac8203..123734523f 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h @@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model { public: std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; void GenerateProfiles(std::vector>& profiles) const override; }; }; From 59590fc6657ece06ef7d2006f5fd5907b1ba1828 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Fri, 30 May 2025 10:06:39 -0700 Subject: [PATCH 065/177] fix: don't render bidi isolates LRI, RLI, FSI, PDI (#18942) Skips rendering LRI, RLI, FSI, and PDI "glyphs" in the terminal. Does not implement BIDI/RTL; that is out of scope, see #538. This is just a hotfix to stop spamming the console with undesired character printouts. Once BIDI support is implemented, this change will (maybe?) no longer be necessary. Fixes #16574. --- src/renderer/atlas/AtlasEngine.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 856b46f0fb..d2e9f8bf26 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -500,8 +500,13 @@ try { for (const auto& cluster : clusters) { - for (const auto& ch : cluster.GetText()) + for (auto ch : cluster.GetText()) { + // Render Unicode directional isolate characters (U+2066..U+2069) as zero-width spaces. + if (ch >= L'\u2066' && ch <= L'\u2069') + { + ch = L'\u200B'; + } _api.bufferLine.emplace_back(ch); _api.bufferLineColumn.emplace_back(columnEnd); } From 36162ae6b110622bfe29770eb2eca5b257e44360 Mon Sep 17 00:00:00 2001 From: Windows Console Service Bot <14666831+consvc@users.noreply.github.com> Date: Fri, 30 May 2025 22:39:32 -0500 Subject: [PATCH 066/177] Localization Updates - main - 05/29/2025 03:05:25 (#18980) --- .../Resources/de-DE/Resources.resw | 47 ++++++++++++ .../Resources/es-ES/Resources.resw | 47 ++++++++++++ .../Resources/fr-FR/Resources.resw | 47 ++++++++++++ .../Resources/it-IT/Resources.resw | 47 ++++++++++++ .../Resources/ja-JP/Resources.resw | 47 ++++++++++++ .../Resources/ko-KR/Resources.resw | 47 ++++++++++++ .../Resources/pt-BR/Resources.resw | 47 ++++++++++++ .../Resources/qps-ploc/Resources.resw | 47 ++++++++++++ .../Resources/qps-ploca/Resources.resw | 47 ++++++++++++ .../Resources/qps-plocm/Resources.resw | 47 ++++++++++++ .../Resources/ru-RU/Resources.resw | 47 ++++++++++++ .../Resources/zh-CN/Resources.resw | 47 ++++++++++++ .../Resources/zh-TW/Resources.resw | 47 ++++++++++++ .../Resources/de-DE/Resources.resw | 74 +++++++++++------- .../Resources/es-ES/Resources.resw | 74 +++++++++++------- .../Resources/fr-FR/Resources.resw | 74 +++++++++++------- .../Resources/it-IT/Resources.resw | 74 +++++++++++------- .../Resources/ja-JP/Resources.resw | 74 +++++++++++------- .../Resources/ko-KR/Resources.resw | 74 +++++++++++------- .../Resources/pt-BR/Resources.resw | 74 +++++++++++------- .../Resources/qps-ploc/Resources.resw | 76 ++++++++++++------- .../Resources/qps-ploca/Resources.resw | 76 ++++++++++++------- .../Resources/qps-plocm/Resources.resw | 76 ++++++++++++------- .../Resources/ru-RU/Resources.resw | 74 +++++++++++------- .../Resources/zh-CN/Resources.resw | 74 +++++++++++------- .../Resources/zh-TW/Resources.resw | 74 +++++++++++------- 26 files changed, 1225 insertions(+), 354 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 4f291d928b..968d594762 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -684,6 +684,10 @@ Aktionen Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Erweiterungen + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Hintergrunddeckkraft Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Diese Option wird durch eine Unternehmensrichtlinie verwaltet und kann hier nicht geändert werden. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Aktive Erweiterungen + + + Geänderte Profile + + + Hinzugefügte Profile + + + Hinzugefügte Farbschemas + + + Weitere Informationen zu Erweiterungen + + + Zum Profil navigieren + + + Zum Profil navigieren + + + Zum Farbschema navigieren + + + Zum Farbschema navigieren + + + Aktueller Benutzer + Label for the installation scope of an extension. + + + Alle Benutzer + Label for the installation scope of an extension + + + Bereich + Header for the installation scope of the extension + + + NEU + Text is used on an info badge for new navigation items. Must be all caps. + Weitere Informationen zu regulären Ausdrücken diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index 35a70e5a4b..f3204f013f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -684,6 +684,10 @@ Acciones Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensiones + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacidad del fondo Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Esta opción está administrada por una directiva de empresa y no se puede cambiar aquí. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Extensiones activas + + + Perfiles modificados + + + Perfiles agregados + + + Combinaciones de colores agregadas + + + Más información acerca de las extensiones + + + Navegar al perfil + + + Navegar al perfil + + + Ir a la combinación de colores + + + Ir a la combinación de colores + + + Usuario actual + Label for the installation scope of an extension. + + + Todos los usuarios + Label for the installation scope of an extension + + + Ámbito + Header for the installation scope of the extension + + + NUEVO + Text is used on an info badge for new navigation items. Must be all caps. + Más información sobre las expresiones regulares diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index 3c5f48dc49..ac90f141ab 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -684,6 +684,10 @@ Actions Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensions + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacité de l’arrière-plan Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Cette option est gérée par la stratégie d’entreprise et ne peut pas être modifiée ici. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Extensions actives + + + Profils modifiés + + + Profils ajoutés + + + Ajout de modèles de couleurs + + + En savoir plus sur les extensions + + + Accéder au profil + + + Accéder au profil + + + Accéder au modèle de couleurs + + + Accéder au modèle de couleurs + + + Utilisateur actuel + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + Étendue + Header for the installation scope of the extension + + + NOUVEAU + Text is used on an info badge for new navigation items. Must be all caps. + En savoir plus sur les expressions régulières diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index 7eb7211d70..b6aebaa918 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -684,6 +684,10 @@ Azioni Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Estensioni + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacità sfondo Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Questa opzione è gestita da criteri aziendali e non può essere modificata qui. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Estensioni attive + + + Profili modificati + + + Profili aggiunti + + + Combinazioni colori aggiunte + + + Altre informazioni sulle estensioni + + + Passa al profilo + + + Passa al profilo + + + Passa alla combinazione colori + + + Passa alla combinazione colori + + + Utente corrente + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + Ambito + Header for the installation scope of the extension + + + NUOVO + Text is used on an info badge for new navigation items. Must be all caps. + Scopri di più sulle espressioni regolari diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index 97a17fa9d7..10514c94e6 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -684,6 +684,10 @@ 操作 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 拡張機能 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 背景の不透明度 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ このオプションはエンタープライズ ポリシーによって管理されているため、ここで変更することはできません。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + アクティブな拡張機能 + + + 変更されたプロファイル + + + 追加されたプロファイル + + + 追加された配色 + + + 拡張機能に関する詳細情報 + + + プロファイルに移動 + + + プロファイルに移動 + + + 配色に移動 + + + 配色に移動 + + + 現在のユーザー + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + スコープ + Header for the installation scope of the extension + + + 新規 + Text is used on an info badge for new navigation items. Must be all caps. + 正規表現について詳細を表示する diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index 6061445a4c..711a533182 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -684,6 +684,10 @@ 작업 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 확장 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 배경 불투명도 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ 이 옵션은 엔터프라이즈 정책에 의해 관리되므로 여기에서 변경할 수 없습니다. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 활성 확장 + + + 수정된 프로필 + + + 추가된 프로필 + + + 추가된 색 구성표 + + + 확장에 대한 자세한 정보 + + + 프로필로 이동 + + + 프로필로 이동 + + + 색 구성표로 이동 + + + 색 구성표로 이동 + + + 현재 사용자 + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + 범위 + Header for the installation scope of the extension + + + 새 기능 + Text is used on an info badge for new navigation items. Must be all caps. + 정규식에 대한 자세한 정보 diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index 522b910a92..faf76cce95 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -684,6 +684,10 @@ Ações Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensões + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacidade da tela de fundo Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Esta opção é gerenciada pela política corporativa e não pode ser alterada aqui. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Extensões Ativas + + + Perfis Modificados + + + Perfis Adicionados + + + Esquemas de Cores Adicionados + + + Saiba mais sobre extensões + + + Navegar para o perfil + + + Navegar para o perfil + + + Navegar para o esquema de cores + + + Navegar para o esquema de cores + + + Usuário Atual + Label for the installation scope of an extension. + + + Todos os Usuários + Label for the installation scope of an extension + + + Escopo + Header for the installation scope of the extension + + + NOVO + Text is used on an info badge for new navigation items. Must be all caps. + Saiba mais sobre expressões regulares diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index 970ddb22f5..800e7b85cd 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -684,6 +684,10 @@ Ăςтιòñš !! Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Ěжτëⁿśïōʼnş !!! + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + βά¢ĸĝŕõúήđ ǿрãĉĩŧÿ !!! !! Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2344,6 +2348,49 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Ãčŧινε ∑хťėŋѕιόйš !!! !! + + + Мőδіƒí℮δ Ρѓòƒίŀέŝ !!! !! + + + Ǻđδєδ Ρѓбƒïℓέŝ !!! ! + + + Áδďéđ Čǿļöŗ Šсħèm℮ŝ !!! !!! + + + Ļ℮ªѓń môг℮ åъоûţ ē×ţепѕîõπѕ !!! !!! !! + + + Ŋãνīġáťё ţσ ргŏƒĭℓз !!! !!! + + + Ŋãνιġǻťě ťǿ рґøƒĩŀę !!! !!! + + + Ňąνїģǻţė ŧŏ ċőľόŗ şċĥêме !!! !!! ! + + + Ñąνīġåŧє ťò ςŏŀόŕ ѕсђεмė !!! !!! ! + + + Ćύřřëńť Ŭѕέѓ !!! + Label for the installation scope of an extension. + + + Ăļł Ůşεŕś !!! + Label for the installation scope of an extension + + + Şĉòрэ ! + Header for the installation scope of the extension + + + ∏ÉŴ + Text is used on an info badge for new navigation items. Must be all caps. + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index 970ddb22f5..800e7b85cd 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -684,6 +684,10 @@ Ăςтιòñš !! Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Ěжτëⁿśïōʼnş !!! + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + βά¢ĸĝŕõúήđ ǿрãĉĩŧÿ !!! !! Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2344,6 +2348,49 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Ãčŧινε ∑хťėŋѕιόйš !!! !! + + + Мőδіƒí℮δ Ρѓòƒίŀέŝ !!! !! + + + Ǻđδєδ Ρѓбƒïℓέŝ !!! ! + + + Áδďéđ Čǿļöŗ Šсħèm℮ŝ !!! !!! + + + Ļ℮ªѓń môг℮ åъоûţ ē×ţепѕîõπѕ !!! !!! !! + + + Ŋãνīġáťё ţσ ргŏƒĭℓз !!! !!! + + + Ŋãνιġǻťě ťǿ рґøƒĩŀę !!! !!! + + + Ňąνїģǻţė ŧŏ ċőľόŗ şċĥêме !!! !!! ! + + + Ñąνīġåŧє ťò ςŏŀόŕ ѕсђεмė !!! !!! ! + + + Ćύřřëńť Ŭѕέѓ !!! + Label for the installation scope of an extension. + + + Ăļł Ůşεŕś !!! + Label for the installation scope of an extension + + + Şĉòрэ ! + Header for the installation scope of the extension + + + ∏ÉŴ + Text is used on an info badge for new navigation items. Must be all caps. + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index 970ddb22f5..800e7b85cd 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -684,6 +684,10 @@ Ăςтιòñš !! Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Ěжτëⁿśïōʼnş !!! + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + βά¢ĸĝŕõúήđ ǿрãĉĩŧÿ !!! !! Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2344,6 +2348,49 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Ãčŧινε ∑хťėŋѕιόйš !!! !! + + + Мőδіƒí℮δ Ρѓòƒίŀέŝ !!! !! + + + Ǻđδєδ Ρѓбƒïℓέŝ !!! ! + + + Áδďéđ Čǿļöŗ Šсħèm℮ŝ !!! !!! + + + Ļ℮ªѓń môг℮ åъоûţ ē×ţепѕîõπѕ !!! !!! !! + + + Ŋãνīġáťё ţσ ргŏƒĭℓз !!! !!! + + + Ŋãνιġǻťě ťǿ рґøƒĩŀę !!! !!! + + + Ňąνїģǻţė ŧŏ ċőľόŗ şċĥêме !!! !!! ! + + + Ñąνīġåŧє ťò ςŏŀόŕ ѕсђεмė !!! !!! ! + + + Ćύřřëńť Ŭѕέѓ !!! + Label for the installation scope of an extension. + + + Ăļł Ůşεŕś !!! + Label for the installation scope of an extension + + + Şĉòрэ ! + Header for the installation scope of the extension + + + ∏ÉŴ + Text is used on an info badge for new navigation items. Must be all caps. + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index ed6f9e8e29..28113389e5 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -684,6 +684,10 @@ Действия Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Расширения + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Прозрачность фона Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Этот параметр управляется политикой предприятия и не может быть изменен здесь. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Активные расширения + + + Измененные профили + + + Добавленные профили + + + Добавленные цветовые схемы + + + Узнайте больше о расширениях + + + Перейти к профилю + + + Перейти к профилю + + + Перейти к цветовой схеме + + + Перейти к цветовой схеме + + + Текущий пользователь + Label for the installation scope of an extension. + + + Все пользователи + Label for the installation scope of an extension + + + Объем + Header for the installation scope of the extension + + + НОВОЕ + Text is used on an info badge for new navigation items. Must be all caps. + Подробнее о регулярных выражениях diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index 096fdca93a..7f46d38030 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -684,6 +684,10 @@ 操作 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 扩展 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 背景不透明度 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ 此选项由企业策略管理,无法在此处更改。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 活动扩展 + + + 已修改配置文件 + + + 已添加配置文件 + + + 已添加配色方案 + + + 了解有关扩展的详细信息 + + + 导航到个人资料 + + + 导航到个人资料 + + + 导航到配色方案 + + + 导航到配色方案 + + + 当前用户 + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + 范围 + Header for the installation scope of the extension + + + 新建 + Text is used on an info badge for new navigation items. Must be all caps. + 了解有关正则表达式的更多信息 diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index 4666ecd933..ed1ee7b18e 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -684,6 +684,10 @@ 動作 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 延伸模組 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 背景不透明度 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ 此選項由企業原則管理,無法在此變更。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 作用中擴充功能 + + + 已修改的設定檔 + + + 已新增設定檔 + + + 已新增色彩配置 + + + 深入了解延伸項目 + + + 瀏覽至設定檔 + + + 瀏覽至設定檔 + + + 瀏覽至色彩配置 + + + 瀏覽至色彩配置 + + + 目前使用者 + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + 範圍 + Header for the installation scope of the extension + + + 新增 + Text is used on an info badge for new navigation items. Must be all caps. + 深入了解規則運算式 diff --git a/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw index 85c516cfee..feea4df3ac 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Aktuelles Arbeitsverzeichnis öffnen + + WSL-Verteilungsprofilgenerator + The display name of a dynamic profile generator for WSL distros + + + PowerShell-Profilgenerator + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell-Profilgenerator + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio-Profilgenerator + The display name of a dynamic profile generator for Visual Studio + + + SSH-Hostprofilgenerator + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw index ec1e158743..479bad532c 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Abrir directorio de trabajo actual + + Generador de perfiles de distribución WSL + The display name of a dynamic profile generator for WSL distros + + + Generador de perfiles de PowerShell + The display name of a dynamic profile generator for PowerShell + + + Generador de perfiles de Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Generador de perfiles de Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Generador de perfiles de host SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw index 6cf1eba684..19dade5fce 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Ouvrir le répertoire de travail actif + + Générateur de profil de distribution WSL + The display name of a dynamic profile generator for WSL distros + + + Générateur de profil PowerShell + The display name of a dynamic profile generator for PowerShell + + + Générateur de profil Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Générateur de profil Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Générateur de profil d’hôte SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw index 1f4a1e65a7..cb79ec9849 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Apri directory di lavoro corrente + + Generatore di profili di distribuzione WSL + The display name of a dynamic profile generator for WSL distros + + + Generatore di profili PowerShell + The display name of a dynamic profile generator for PowerShell + + + Generatore di profili di Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Generatore di profili Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Generatore di profili host SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw index 9743e14162..e8b0d12ca4 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 現在の作業ディレクトリを開く + + WSL ディストリビューション プロファイル ジェネレーター + The display name of a dynamic profile generator for WSL distros + + + PowerShell プロファイル ジェネレーター + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell プロファイル ジェネレーター + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio プロファイル ジェネレーター + The display name of a dynamic profile generator for Visual Studio + + + SSH ホスト プロファイル ジェネレーター + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw index 378d4d04bf..afe633e362 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 현재 작업 디렉터리 열기 + + WSL 배포 프로필 생성기 + The display name of a dynamic profile generator for WSL distros + + + PowerShell 프로필 생성기 + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell 프로필 생성기 + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio 프로필 생성기 + The display name of a dynamic profile generator for Visual Studio + + + SSH 호스트 프로필 생성기 + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw index 4a28f1aa00..205bac0483 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Abrir o diretório de trabalho atual + + Gerador de Perfil de Distribuição WSL + The display name of a dynamic profile generator for WSL distros + + + Gerador de Perfil do PowerShell + The display name of a dynamic profile generator for PowerShell + + + Gerador de Perfil do Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Gerador de Perfil do Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Gerador de Perfil de Host SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw index afcdd02d71..508f979745 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw @@ -1,17 +1,17 @@ - @@ -740,4 +740,24 @@ Öφěп сùяŗĕʼnŧ ώθяќìⁿġ ðїгéćţοŗу !!! !!! !!! - + + ẄŠ₤ Ďΐşţŗĭьúţĭοй Рŗоƒįĺé Ĝèŋëѓάтбŕ !!! !!! !!! ! + The display name of a dynamic profile generator for WSL distros + + + РöшєřŜђěłŀ Ρѓòƒĭļз Ġèпéгâŧθѓ !!! !!! !! + The display name of a dynamic profile generator for PowerShell + + + Δżúѓ℮ Ċℓǿύď Śнéļļ Рŗôƒìŀё Ğēņεřǻŧōґ !!! !!! !!! ! + The display name of a dynamic profile generator for Azure Cloud Shell + + + Vΐšџаℓ Şτϋδίǿ Ρѓőƒϊĺĕ Ğёйěřąтοŕ !!! !!! !!! + The display name of a dynamic profile generator for Visual Studio + + + ŠŚΗ Ħøŝť Рґǿƒιŀέ Ġеπèŕăťθґ !!! !!! ! + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw index afcdd02d71..508f979745 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw @@ -1,17 +1,17 @@ - @@ -740,4 +740,24 @@ Öφěп сùяŗĕʼnŧ ώθяќìⁿġ ðїгéćţοŗу !!! !!! !!! - + + ẄŠ₤ Ďΐşţŗĭьúţĭοй Рŗоƒįĺé Ĝèŋëѓάтбŕ !!! !!! !!! ! + The display name of a dynamic profile generator for WSL distros + + + РöшєřŜђěłŀ Ρѓòƒĭļз Ġèпéгâŧθѓ !!! !!! !! + The display name of a dynamic profile generator for PowerShell + + + Δżúѓ℮ Ċℓǿύď Śнéļļ Рŗôƒìŀё Ğēņεřǻŧōґ !!! !!! !!! ! + The display name of a dynamic profile generator for Azure Cloud Shell + + + Vΐšџаℓ Şτϋδίǿ Ρѓőƒϊĺĕ Ğёйěřąтοŕ !!! !!! !!! + The display name of a dynamic profile generator for Visual Studio + + + ŠŚΗ Ħøŝť Рґǿƒιŀέ Ġеπèŕăťθґ !!! !!! ! + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw index afcdd02d71..508f979745 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw @@ -1,17 +1,17 @@ - @@ -740,4 +740,24 @@ Öφěп сùяŗĕʼnŧ ώθяќìⁿġ ðїгéćţοŗу !!! !!! !!! - + + ẄŠ₤ Ďΐşţŗĭьúţĭοй Рŗоƒįĺé Ĝèŋëѓάтбŕ !!! !!! !!! ! + The display name of a dynamic profile generator for WSL distros + + + РöшєřŜђěłŀ Ρѓòƒĭļз Ġèпéгâŧθѓ !!! !!! !! + The display name of a dynamic profile generator for PowerShell + + + Δżúѓ℮ Ċℓǿύď Śнéļļ Рŗôƒìŀё Ğēņεřǻŧōґ !!! !!! !!! ! + The display name of a dynamic profile generator for Azure Cloud Shell + + + Vΐšџаℓ Şτϋδίǿ Ρѓőƒϊĺĕ Ğёйěřąтοŕ !!! !!! !!! + The display name of a dynamic profile generator for Visual Studio + + + ŠŚΗ Ħøŝť Рґǿƒιŀέ Ġеπèŕăťθґ !!! !!! ! + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw index 147a457dac..bffaaee59c 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Открыть текущую рабочую папку + + Генератор профилей дистрибутивов WSL + The display name of a dynamic profile generator for WSL distros + + + Генератор профилей PowerShell + The display name of a dynamic profile generator for PowerShell + + + Генератор профилей Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Генератор профилей Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Генератор профилей узлов SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw index abef778111..649af7ffbc 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 打开当前工作目录 + + WSL 发行版配置文件生成器 + The display name of a dynamic profile generator for WSL distros + + + PowerShell 配置文件生成器 + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell 配置文件生成器 + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio 配置文件生成器 + The display name of a dynamic profile generator for Visual Studio + + + SSH 主机配置文件生成器 + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw index 75edbf8518..f97fadd75c 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 開啟目前的工作目錄 + + WSL 發佈版設定檔產生器 + The display name of a dynamic profile generator for WSL distros + + + PowerShell 設定檔產生器 + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell 設定檔產生器 + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio 設定檔產生器 + The display name of a dynamic profile generator for Visual Studio + + + SSH 主機設定檔產生器 + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file From 3ae6bbf2df7919071fe8477676d6b7f5d838d558 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Sat, 31 May 2025 12:50:18 -0700 Subject: [PATCH 067/177] Enable SSH Generator Feature Flag and polish UI (#18814) --- ...d500-50ad-8a1a-c400c3262db3}.scale-100.png | Bin 647 -> 0 bytes ...d500-50ad-8a1a-c400c3262db3}.scale-200.png | Bin 787 -> 0 bytes .../TerminalSettingsEditor/Extensions.cpp | 6 ++ .../TerminalSettingsEditor/Extensions.h | 1 + .../TerminalSettingsEditor/Extensions.idl | 1 + .../TerminalSettingsEditor/Extensions.xaml | 55 ++++++++++++++++++ .../SshHostGenerator.cpp | 4 +- src/features.xml | 5 ++ 8 files changed, 70 insertions(+), 2 deletions(-) delete mode 100644 src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-100.png delete mode 100644 src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-200.png diff --git a/src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-100.png b/src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-100.png deleted file mode 100644 index 6d57b166f20df38330a9dc881ac4334922b64b00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 647 zcmV;20(kw2P)EX>4Tx04R}tkv&MmKpe$iQ%j3f9Lyl%kfAzR5EXHhDi*;)X)CnqU~=gfG%+M8 zE{=k0!NHHks)LKOt`4q(Aou~|;_9U6A|?JWDYS_3;J6>}?mh0_0YbCJG^=ME&~)3( zBok7;up)(D5k&|hB8bV%GE&)v9DK*uJpz2ai}Ni1bAOILwP-OQAQ8_p!?cOliKjPh zgY!PI$SSf*d`>)O(glehxvqHp#<}dWz%xZNmzg6LiKTKED_zVgrbawP98)!&@`b#| zD(5ZETCL97_v9~(7WL&U*J%zTiAAK4h6Dw5R8fPCIITJ<7BaM-@bM41{xrE1a#g{| zF^>&skX=9cAN=mtDosv!NznuldU2eO5g@z^v>J}{ee5``6Cn5uT(Ud*lat9cEGGtSBr65hAR07`5=$i__z%9_b>h;#%$LRx*qpp^2fP+I| zv`pD+KJSin_xA6Zc7H!o6mptvbX6w+000JJOGiWi{{a60|De66lK=n!32;bRa{vG? zBLDy{BLR4&KXw2B00(qQO+^Re3e)v3~guj`k$pCPyTlEmg z3gq!a0ZEXsjJY-Vpw_ybND?G_uHD*0kd!1{WREX>4Tx04R}tkv&MmKpe$iQ%j3f9Lyl%kfAzR5EXHhDi*;)X)CnqU~=gfG%+M8 zE{=k0!NHHks)LKOt`4q(Aou~|;_9U6A|?JWDYS_3;J6>}?mh0_0YbCJG^=ME&~)3( zBok7;up)(D5k&|hB8bV%GE&)v9DK*uJpz2ai}Ni1bAOILwP-OQAQ8_p!?cOliKjPh zgY!PI$SSf*d`>)O(glehxvqHp#<}dWz%xZNmzg6LiKTKED_zVgrbawP98)!&@`b#| zD(5ZETCL97_v9~(7WL&U*J%zTiAAK4h6Dw5R8fPCIITJ<7BaM-@bM41{xrE1a#g{| zF^>&skX=9cAN=mtDosv!NznuldU2eO5g@z^v>J}{ee5``6Cn5uT(Ud*lat9cEGGtSBr65hAR07`5=$i__z%9_b>h;#%$LRx*qpp^2fP+I| zv`pD+KJSin_xA6Zc7H!o6mptvbX6w+000JJOGiWi{{a60|De66lK=n!32;bRa{vG? zBLDy{BLR4&KXw2B00(qQO+^Re3Wk>caVsHnX#M&ZX=9kV?EeWm_RDb^43GNX&YFB4EzsN!($xm=8KNC&h> zT$mZmjFb{`&M$Tg0JoJ`d3e9NA%#czjWJf6^bj!cxB9;!WZ;&8D!)B_{$5|uPzqWz zUNbP%90Qrn={YL`>>*8SiFw1U^Qo`9wgZvXI8&>c(YvIFh* RdH4VT002ovPDHLkV1ff0TR{K- diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp index ffaff9a33d..a3aec0ad7c 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -500,6 +500,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { if (!extPkgVM.Package().DisplayName().empty()) { + // Check if the first char of the icon is in the Segoe MDL2 Icons list + const auto ch = til::at(extPkgVM.Package().Icon(), 0); + if (ch >= L'\uE700' && ch <= L'\uF8FF') + { + return ComplexTemplateWithFontIcon(); + } return ComplexTemplate(); } return DefaultTemplate(); diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h index 6b7eafad57..a9379829d2 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.h +++ b/src/cascadia/TerminalSettingsEditor/Extensions.h @@ -183,6 +183,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, DefaultTemplate, nullptr); WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ComplexTemplate, nullptr); + WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ComplexTemplateWithFontIcon, nullptr); }; }; diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.idl b/src/cascadia/TerminalSettingsEditor/Extensions.idl index 4dc3018c36..6093471014 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.idl +++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl @@ -77,5 +77,6 @@ namespace Microsoft.Terminal.Settings.Editor Windows.UI.Xaml.DataTemplate DefaultTemplate; Windows.UI.Xaml.DataTemplate ComplexTemplate; + Windows.UI.Xaml.DataTemplate ComplexTemplateWithFontIcon; } } diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml index eec3e69290..a3f80cb2e7 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -42,6 +42,7 @@ + + + + + + + + + + + + + + + + + + + + + Enables the dynamic profile generator for OpenSSH config files 9031 AlwaysDisabled + + Dev + Canary + Preview + From 9dbcf4b9dd33d36ac0145db195db7563f5a86c05 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 2 Jun 2025 13:27:22 -0500 Subject: [PATCH 068/177] build: try even harder to find a working VC tools version (#18996) I can't explain this, but VS 17.14 ships with VisualCpp.Tools.Core version 14.44.35208 but the files say 14.44.35207. --- build/scripts/Set-LatestVCToolsVersion.ps1 | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/build/scripts/Set-LatestVCToolsVersion.ps1 b/build/scripts/Set-LatestVCToolsVersion.ps1 index 3156fff1bc..4ebf3a3eec 100644 --- a/build/scripts/Set-LatestVCToolsVersion.ps1 +++ b/build/scripts/Set-LatestVCToolsVersion.ps1 @@ -1,8 +1,28 @@ $VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -include packages -format xml)) $VSPackages = $VSInstances.instances.instance.packages.package -$LatestVCPackage = ($VSInstances.instances.instance.packages.package | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" }) +$LatestVCPackage = ($VSPackages | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" }) $LatestVCToolsVersion = $LatestVCPackage.version; +$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property 'resolvedInstallationPath') +$VCToolsRoot = Join-Path $VSRoot "VC\Tools\MSVC" + +# We have observed a few instances where the VC tools package version actually +# differs from the version on the files themselves. We might as well check +# whether the version we just found _actually exists_ before we use it. +# We'll use whichever highest version exists. +$PackageVCToolPath = Join-Path $VCToolsRoot $LatestVCToolsVersion +If ($Null -Eq (Get-Item $PackageVCToolPath -ErrorAction:Ignore)) { + $VCToolsVersions = Get-ChildItem $VCToolsRoot | ForEach-Object { + [Version]$_.Name + } | Sort -Descending + $LatestActualVCToolsVersion = $VCToolsVersions | Select -First 1 + + If ([Version]$LatestVCToolsVersion -Ne $LatestActualVCToolsVersion) { + Write-Output "VC Tools Mismatch: Directory = $LatestActualVCToolsVersion, Package = $LatestVCToolsVersion" + $LatestVCToolsVersion = $LatestActualVCToolsVersion.ToString(3) + } +} + Write-Output "Latest VCToolsVersion: $LatestVCToolsVersion" Write-Output "Updating VCToolsVersion environment variable for job" Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion" From 37d5aec1cff01e27763d15ca2b2f8f587005f849 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 2 Jun 2025 12:12:48 -0700 Subject: [PATCH 069/177] Replace extensions page hyperlink with fwlink (#18997) --- src/cascadia/TerminalSettingsEditor/Extensions.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml index a3f80cb2e7..db1637b42c 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -451,7 +451,7 @@ + NavigateUri="https://go.microsoft.com/fwlink/?linkid=2321753" /> Date: Mon, 2 Jun 2025 17:22:24 -0700 Subject: [PATCH 070/177] Update command palette search to prioritize "longest substring" match. (#18700) It's the fzf algorithm! Repurposed work from #16586 - I think the fzf algo fits here where it optimizes to find the optimal match based on consecutive chars and word boundaries. - There are some edge cases where a match with a small gap could get a higher score than a match of consecutive chars when the match with a gap has other bonuses (FirstChar * Boundary Bonus). This can be adjusted by adjusting the bonuses or removing them if needed. - From reading the thread in #6693 it looked like you guys were leaning towards something like the fzf algo. - License file is now updated in https://github.com/nvim-telescope/telescope-fzf-native.nvim repository - https://github.com/nvim-telescope/telescope-fzf-native.nvim/pull/148 - https://github.com/junegunn/fzf/issues/4310 - Removed the following from the original implementation to minimize complexity and the size of the PR. (Let me know if any of these should be added back). - Query expressions "$:StartsWith ^:EndsWith |:Or !:Not etc" - Slab to avoid allocating the scoring matrix. This felt like overkill for the number of items in the command pallete. - Fallback to V1 algorithm for very long strings. I want to say that the command palette won't have strings this long. - Added the logic from GH#9941 that copies pattern and text chars to string for comparision with lstrcmpi - It does this twice now which isn't great... Closes #6693 --------- Co-authored-by: Leonard Hecker --- .github/actions/spelling/excludes.txt | 1 + .github/actions/spelling/expect/expect.txt | 4 + NOTICE.md | 32 + .../FilteredCommandTests.cpp | 147 ++--- src/cascadia/TerminalApp/CommandPalette.cpp | 5 +- src/cascadia/TerminalApp/FilteredCommand.cpp | 207 ++----- src/cascadia/TerminalApp/FilteredCommand.h | 8 +- src/cascadia/TerminalApp/FilteredCommand.idl | 3 - .../TerminalApp/SnippetsPaneContent.cpp | 3 +- .../TerminalApp/SnippetsPaneContent.h | 10 +- .../TerminalApp/SuggestionsControl.cpp | 5 +- .../TerminalApp/TerminalAppLib.vcxproj | 2 + .../TerminalAppLib.vcxproj.filters | 12 + src/cascadia/TerminalApp/fzf/LICENSE | 22 + src/cascadia/TerminalApp/fzf/fzf.cpp | 432 +++++++++++++ src/cascadia/TerminalApp/fzf/fzf.h | 27 + src/cascadia/ut_app/FzfTests.cpp | 568 ++++++++++++++++++ .../ut_app/TerminalApp.UnitTests.vcxproj | 1 + src/inc/til.h | 4 +- src/inc/til/type_traits.h | 11 + 20 files changed, 1235 insertions(+), 269 deletions(-) create mode 100644 src/cascadia/TerminalApp/fzf/LICENSE create mode 100644 src/cascadia/TerminalApp/fzf/fzf.cpp create mode 100644 src/cascadia/TerminalApp/fzf/fzf.h create mode 100644 src/cascadia/ut_app/FzfTests.cpp diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 896211b8f1..c142f11548 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -133,3 +133,4 @@ Resources/(?!en) ^\Qsrc/terminal/parser/ft_fuzzwrapper/run.bat\E$ ^\Qsrc/tools/lnkd/lnkd.bat\E$ ^\Qsrc/tools/pixels/pixels.bat\E$ +^\Qsrc/cascadia/ut_app/FzfTests.cpp\E$ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index cc53f71e5f..318903fe17 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -651,6 +651,7 @@ FONTSTRING FONTTYPE FONTWIDTH FONTWINDOW +foob FORCEOFFFEEDBACK FORCEONFEEDBACK FRAMECHANGED @@ -668,9 +669,11 @@ fuzzer fuzzmain fuzzmap fuzzwrapper +fuzzyfinder fwdecl fwe fwlink +fzf gci gcx gdi @@ -1248,6 +1251,7 @@ onecoreuuid ONECOREWINDOWS onehalf oneseq +oob openbash opencode opencon diff --git a/NOTICE.md b/NOTICE.md index bd99dd939c..efe962bc0d 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -285,6 +285,8 @@ specific language governing permissions and limitations under the License. **Source**: [https://github.com/commonmark/cmark](https://github.com/commonmark/cmark) ### License + +``` Copyright (c) 2014, John MacFarlane All rights reserved. @@ -455,6 +457,36 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## fzf + +### License + +``` +The MIT License (MIT) + +Copyright (c) 2013-2024 Junegunn Choi +Copyright (c) 2021-2025 Simon Hauser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` # Microsoft Open Source diff --git a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp index e96675fd32..023ac04702 100644 --- a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp @@ -32,37 +32,35 @@ namespace TerminalAppLocalTests { auto result = RunOnUIThread([]() { const auto paletteItem{ winrt::make(L"AAAAAABBBBBBCCC") }; + const auto filteredCommand = winrt::make_self(paletteItem); + { Log::Comment(L"Testing command name segmentation with no filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - auto segments = filteredCommand->_computeHighlightedName().Segments(); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with empty filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L""; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with filter equal to the string"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"AAAAAABBBBBBCCC"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"AAAAAABBBBBBCCC"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with filter with first character matching"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"A"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"A"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 2u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); @@ -71,9 +69,8 @@ namespace TerminalAppLocalTests } { Log::Comment(L"Testing command name segmentation with filter with other case"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"a"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"a"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 2u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); @@ -82,24 +79,20 @@ namespace TerminalAppLocalTests } { Log::Comment(L"Testing command name segmentation with filter matching several characters"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"ab"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 4u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAA"); - VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"B"); - VERIFY_IS_TRUE(segments.GetAt(2).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(3).TextSegment(), L"BBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(3).IsHighlighted()); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"ab"))); + auto segments = filteredCommand->HighlightedName().Segments(); + VERIFY_ARE_EQUAL(segments.Size(), 3u); + VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAA"); + VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AB"); + VERIFY_IS_TRUE(segments.GetAt(1).IsHighlighted()); + VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"BBBBBCCC"); + VERIFY_IS_FALSE(segments.GetAt(2).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with non matching filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"abcd"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"abcd"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); @@ -113,53 +106,37 @@ namespace TerminalAppLocalTests { auto result = RunOnUIThread([]() { const auto paletteItem{ winrt::make(L"AAAAAABBBBBBCCC") }; - { - Log::Comment(L"Testing weight of command with no filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 0); - } - { - Log::Comment(L"Testing weight of command with empty filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L""; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 0); - } - { - Log::Comment(L"Testing weight of command with filter equal to the string"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"AAAAAABBBBBBCCC"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 30); // 1 point for the first char and 2 points for the 14 consequent ones + 1 point for the beginning of the word - } - { - Log::Comment(L"Testing weight of command with filter with first character matching"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"A"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word - } - { - Log::Comment(L"Testing weight of command with filter with other case"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"a"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word - } - { - Log::Comment(L"Testing weight of command with filter matching several characters"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"ab"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 3); // 1 point for the first char match + 1 point for the beginning of the word + 1 point for the match of "b" - } + const auto filteredCommand = winrt::make_self(paletteItem); + + const auto weigh = [&](const wchar_t* str) { + std::shared_ptr pattern; + if (str) + { + pattern = std::make_shared(fzf::matcher::ParsePattern(str)); + } + filteredCommand->UpdateFilter(std::move(pattern)); + return filteredCommand->Weight(); + }; + + const auto null = weigh(nullptr); + const auto empty = weigh(L""); + const auto full = weigh(L"AAAAAABBBBBBCCC"); + const auto firstChar = weigh(L"A"); + const auto otherCase = weigh(L"a"); + const auto severalChars = weigh(L"ab"); + + VERIFY_ARE_EQUAL(null, 0); + VERIFY_ARE_EQUAL(empty, 0); + VERIFY_IS_GREATER_THAN(full, 100); + + VERIFY_IS_GREATER_THAN(firstChar, 0); + VERIFY_IS_LESS_THAN(firstChar, full); + + VERIFY_IS_GREATER_THAN(otherCase, 0); + VERIFY_IS_LESS_THAN(otherCase, full); + + VERIFY_IS_GREATER_THAN(severalChars, otherCase); + VERIFY_IS_LESS_THAN(severalChars, full); }); VERIFY_SUCCEEDED(result); @@ -181,14 +158,10 @@ namespace TerminalAppLocalTests { Log::Comment(L"Testing comparison of commands with empty filter"); const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L""; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - filteredCommand->_Weight = filteredCommand->_computeWeight(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); const auto filteredCommand2 = winrt::make_self(paletteItem2); - filteredCommand2->_Filter = L""; - filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName(); - filteredCommand2->_Weight = filteredCommand2->_computeWeight(); + filteredCommand2->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight()); VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2)); @@ -196,16 +169,12 @@ namespace TerminalAppLocalTests { Log::Comment(L"Testing comparison of commands with different weights"); const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"B"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - filteredCommand->_Weight = filteredCommand->_computeWeight(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"B"))); const auto filteredCommand2 = winrt::make_self(paletteItem2); - filteredCommand2->_Filter = L"B"; - filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName(); - filteredCommand2->_Weight = filteredCommand2->_computeWeight(); + filteredCommand2->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"B"))); - VERIFY_IS_TRUE(filteredCommand->Weight() < filteredCommand2->Weight()); // Second command gets more points due to the beginning of the word + VERIFY_IS_LESS_THAN(filteredCommand->Weight(), filteredCommand2->Weight()); // Second command gets more points due to the beginning of the word VERIFY_IS_FALSE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2)); } }); diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 6d5540703f..e2e7526db8 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -1174,12 +1174,15 @@ namespace winrt::TerminalApp::implementation } else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode) { + auto pattern = std::make_shared(fzf::matcher::ParsePattern(searchText)); + for (const auto& action : commandsToFilter) { // Update filter for all commands // This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting). // Pay attention that it already updates the highlighting in the UI - action.UpdateFilter(searchText); + auto impl = winrt::get_self(action); + impl->UpdateFilter(pattern); // if there is active search we skip commands with 0 weight if (searchText.empty() || action.Weight() > 0) diff --git a/src/cascadia/TerminalApp/FilteredCommand.cpp b/src/cascadia/TerminalApp/FilteredCommand.cpp index d6c6c38a28..9d91ded825 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.cpp +++ b/src/cascadia/TerminalApp/FilteredCommand.cpp @@ -5,6 +5,7 @@ #include "CommandPalette.h" #include "HighlightedText.h" #include +#include "fzf/fzf.h" #include "FilteredCommand.g.cpp" @@ -35,197 +36,75 @@ namespace winrt::TerminalApp::implementation void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item) { _Item = item; - _Filter = L""; _Weight = 0; - _HighlightedName = _computeHighlightedName(); + + _update(); // Recompute the highlighted name if the item name changes _itemChangedRevoker = _Item.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& /*sender*/, auto& e) { auto filteredCommand{ weakThis.get() }; if (filteredCommand && e.PropertyName() == L"Name") { - filteredCommand->HighlightedName(filteredCommand->_computeHighlightedName()); - filteredCommand->Weight(filteredCommand->_computeWeight()); + filteredCommand->_update(); } }); } - void FilteredCommand::UpdateFilter(const winrt::hstring& filter) + void FilteredCommand::UpdateFilter(std::shared_ptr pattern) { // If the filter was not changed we want to prevent the re-computation of matching // that might result in triggering a notification event - if (filter != _Filter) + if (pattern != _pattern) { - Filter(filter); - HighlightedName(_computeHighlightedName()); - Weight(_computeWeight()); + _pattern = pattern; + _update(); } } - // Method Description: - // - Looks up the filter characters within the item name. - // Iterating through the filter and the item name it tries to associate the next filter character - // with the first appearance of this character in the item name suffix. - // - // E.g., for filter="c l t s" and name="close all tabs after this", the match will be "CLose TabS after this". - // - // The item name is then split into segments (groupings of matched and non matched characters). - // - // E.g., the segments were the example above will be "CL", "ose ", "T", "ab", "S", "after this". - // - // The segments matching the filter characters are marked as highlighted. - // - // E.g., ("CL", true) ("ose ", false), ("T", true), ("ab", false), ("S", true), ("after this", false) - // - // TODO: we probably need to merge this logic with _getWeight computation? - // - // Return Value: - // - The HighlightedText object initialized with the segments computed according to the algorithm above. - winrt::TerminalApp::HighlightedText FilteredCommand::_computeHighlightedName() + void FilteredCommand::_update() { - const auto segments = winrt::single_threaded_observable_vector(); - auto commandName = _Item.Name(); - auto isProcessingMatchedSegment = false; - uint32_t nextOffsetToReport = 0; - uint32_t currentOffset = 0; + std::vector segments; + const auto commandName = _Item.Name(); + int32_t weight = 0; - for (const auto searchChar : _Filter) + if (!_pattern || _pattern->terms.empty()) { - const WCHAR searchCharAsString[] = { searchChar, L'\0' }; - while (true) + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(commandName, false)); + } + else if (auto match = fzf::matcher::Match(commandName, *_pattern.get()); !match) + { + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(commandName, false)); + } + else + { + auto& matchResult = *match; + weight = matchResult.Score; + + size_t lastPos = 0; + for (const auto& run : matchResult.Runs) { - if (currentOffset == commandName.size()) + const auto& [start, end] = run; + if (start > lastPos) { - // There are still unmatched filter characters but we finished scanning the name. - // In this case we return the entire item name as unmatched - auto entireNameSegment{ winrt::make(commandName, false) }; - segments.Clear(); - segments.Append(entireNameSegment); - return winrt::make(segments); + hstring nonMatch{ til::safe_slice_abs(commandName, lastPos, start) }; + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(nonMatch, false)); } - // GH#9941: search should be locale-aware as well - // We use the same comparison method as upon sorting to guarantee consistent behavior - const WCHAR currentCharAsString[] = { commandName[currentOffset], L'\0' }; - auto isCurrentCharMatched = lstrcmpi(searchCharAsString, currentCharAsString) == 0; - if (isProcessingMatchedSegment != isCurrentCharMatched) - { - // We reached the end of the region (matched character came after a series of unmatched or vice versa). - // Conclude the segment and add it to the list. - // Skip segment if it is empty (might happen when the first character of the name is matched) - auto sizeToReport = currentOffset - nextOffsetToReport; - if (sizeToReport > 0) - { - winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport }; - auto highlightedSegment{ winrt::make(segment, isProcessingMatchedSegment) }; - segments.Append(highlightedSegment); - nextOffsetToReport = currentOffset; - } - isProcessingMatchedSegment = isCurrentCharMatched; - } + hstring matchSeg{ til::safe_slice_abs(commandName, start, end + 1) }; + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(matchSeg, true)); - currentOffset++; + lastPos = end + 1; + } - if (isCurrentCharMatched) - { - // We have matched this filter character, let's move to matching the next filter char - break; - } + if (lastPos < commandName.size()) + { + hstring tail{ til::safe_slice_abs(commandName, lastPos, SIZE_T_MAX) }; + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(tail, false)); } } - // Either the filter or the item name were fully processed. - // If we were in the middle of the matched segment - add it. - if (isProcessingMatchedSegment) - { - auto sizeToReport = currentOffset - nextOffsetToReport; - if (sizeToReport > 0) - { - winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport }; - auto highlightedSegment{ winrt::make(segment, true) }; - segments.Append(highlightedSegment); - nextOffsetToReport = currentOffset; - } - } - - // Now create a segment for all remaining characters. - // We will have remaining characters as long as the filter is shorter than the item name. - auto sizeToReport = commandName.size() - nextOffsetToReport; - if (sizeToReport > 0) - { - winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport }; - auto highlightedSegment{ winrt::make(segment, false) }; - segments.Append(highlightedSegment); - } - - return winrt::make(segments); - } - - // Function Description: - // - Calculates a "weighting" by which should be used to order a item - // name relative to other names, given a specific search string. - // Currently, this is based off of two factors: - // * The weight is incremented once for each matched character of the - // search text. - // * If a matching character from the search text was found at the start - // of a word in the name, then we increment the weight again. - // * For example, for a search string "sp", we want "Split Pane" to - // appear in the list before "Close Pane" - // * Consecutive matches will be weighted higher than matches with - // characters in between the search characters. - // - This will return 0 if the item should not be shown. If all the - // characters of search text appear in order in `name`, then this function - // will return a positive number. There can be any number of characters - // separating consecutive characters in searchText. - // * For example: - // "name": "New Tab" - // "name": "Close Tab" - // "name": "Close Pane" - // "name": "[-] Split Horizontal" - // "name": "[ | ] Split Vertical" - // "name": "Next Tab" - // "name": "Prev Tab" - // "name": "Open Settings" - // "name": "Open Media Controls" - // * "open" should return both "**Open** Settings" and "**Open** Media Controls". - // * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev - // **Tab**". - // * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ] - // S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media - // Controls". - // * "sv" would return "[ | ] Split Vertical" (by matching the **S** in - // "Split", then the **V** in "Vertical"). - // Arguments: - // - searchText: the string of text to search for in `name` - // - name: the name to check - // Return Value: - // - the relative weight of this match - int FilteredCommand::_computeWeight() - { - auto result = 0; - auto isNextSegmentWordBeginning = true; - - for (const auto& segment : _HighlightedName.Segments()) - { - const auto& segmentText = segment.TextSegment(); - const auto segmentSize = segmentText.size(); - - if (segment.IsHighlighted()) - { - // Give extra point for each consecutive match - result += (segmentSize <= 1) ? segmentSize : 1 + 2 * (segmentSize - 1); - - // Give extra point if this segment is at the beginning of a word - if (isNextSegmentWordBeginning) - { - result++; - } - } - - isNextSegmentWordBeginning = segmentSize > 0 && segmentText[segmentSize - 1] == L' '; - } - - return result; + HighlightedName(winrt::make(winrt::single_threaded_observable_vector(std::move(segments)))); + Weight(weight); } // Function Description: @@ -243,9 +122,9 @@ namespace winrt::TerminalApp::implementation if (firstWeight == secondWeight) { - std::wstring_view firstName{ first.Item().Name() }; - std::wstring_view secondName{ second.Item().Name() }; - return lstrcmpi(firstName.data(), secondName.data()) < 0; + const auto firstName = first.Item().Name(); + const auto secondName = second.Item().Name(); + return til::compare_linguistic_insensitive(firstName, secondName) < 0; } return firstWeight > secondWeight; diff --git a/src/cascadia/TerminalApp/FilteredCommand.h b/src/cascadia/TerminalApp/FilteredCommand.h index f304ad032a..ff6f200c58 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.h +++ b/src/cascadia/TerminalApp/FilteredCommand.h @@ -5,6 +5,7 @@ #include "HighlightedTextControl.h" #include "FilteredCommand.g.h" +#include "fzf/fzf.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -19,13 +20,12 @@ namespace winrt::TerminalApp::implementation FilteredCommand() = default; FilteredCommand(const winrt::TerminalApp::PaletteItem& item); - virtual void UpdateFilter(const winrt::hstring& filter); + virtual void UpdateFilter(std::shared_ptr pattern); static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second); til::property_changed_event PropertyChanged; WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::PaletteItem, Item, PropertyChanged.raise, nullptr); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Filter, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise); @@ -33,8 +33,8 @@ namespace winrt::TerminalApp::implementation void _constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item); private: - winrt::TerminalApp::HighlightedText _computeHighlightedName(); - int _computeWeight(); + std::shared_ptr _pattern; + void _update(); Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _itemChangedRevoker; friend class TerminalAppLocalTests::FilteredCommandTests; diff --git a/src/cascadia/TerminalApp/FilteredCommand.idl b/src/cascadia/TerminalApp/FilteredCommand.idl index a63e6e8110..a5a1e34cf4 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.idl +++ b/src/cascadia/TerminalApp/FilteredCommand.idl @@ -12,10 +12,7 @@ namespace TerminalApp FilteredCommand(PaletteItem item); PaletteItem Item { get; }; - String Filter; HighlightedText HighlightedName { get; }; Int32 Weight; - - void UpdateFilter(String filter); } } diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp index 415e5d8201..d2dd9fb03e 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp @@ -32,6 +32,7 @@ namespace winrt::TerminalApp::implementation void SnippetsPaneContent::_updateFilteredCommands() { const auto& queryString = _filterBox().Text(); + auto pattern = std::make_shared(fzf::matcher::ParsePattern(queryString)); // DON'T replace the itemSource here. If you do, it'll un-expand all the // nested items the user has expanded. Instead, just update the filter. @@ -39,7 +40,7 @@ namespace winrt::TerminalApp::implementation for (const auto& t : _allTasks) { auto impl = winrt::get_self(t); - impl->UpdateFilter(queryString); + impl->UpdateFilter(pattern); } } diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.h b/src/cascadia/TerminalApp/SnippetsPaneContent.h index c469e83fcc..2732522f3e 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.h +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.h @@ -77,13 +77,14 @@ namespace winrt::TerminalApp::implementation } } - void UpdateFilter(const winrt::hstring& filter) + void UpdateFilter(std::shared_ptr pattern) { - _filteredCommand->UpdateFilter(filter); + _pattern = std::move(pattern); + _filteredCommand->UpdateFilter(_pattern); for (const auto& c : _children) { auto impl = winrt::get_self(c); - impl->UpdateFilter(filter); + impl->UpdateFilter(_pattern); } PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Visibility" }); @@ -108,6 +109,7 @@ namespace winrt::TerminalApp::implementation bool HasChildren() { return _children.Size() > 0; } winrt::Microsoft::Terminal::Settings::Model::Command Command() { return _command; } winrt::TerminalApp::FilteredCommand FilteredCommand() { return *_filteredCommand; } + std::shared_ptr _pattern; int32_t Row() { return HasChildren() ? 2 : 1; } // See the BODGY comment in the .XAML for explanation @@ -117,7 +119,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Visibility Visibility() { // Is there no filter, or do we match it? - if (_filteredCommand->Filter().empty() || _filteredCommand->Weight() > 0) + if ((!_pattern || _pattern->terms.empty() || _filteredCommand->Weight() > 0)) { return winrt::Windows::UI::Xaml::Visibility::Visible; } diff --git a/src/cascadia/TerminalApp/SuggestionsControl.cpp b/src/cascadia/TerminalApp/SuggestionsControl.cpp index 442b80243c..3ebc7ca158 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.cpp +++ b/src/cascadia/TerminalApp/SuggestionsControl.cpp @@ -936,12 +936,15 @@ namespace winrt::TerminalApp::implementation auto commandsToFilter = _commandsToFilter(); { + auto pattern = std::make_shared(fzf::matcher::ParsePattern(searchText)); + for (const auto& action : commandsToFilter) { // Update filter for all commands // This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting). // Pay attention that it already updates the highlighting in the UI - action.UpdateFilter(searchText); + auto impl = winrt::get_self(action); + impl->UpdateFilter(pattern); // if there is active search we skip commands with 0 weight if (searchText.empty() || action.Weight() > 0) diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 0896bc114a..ac4357554c 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -138,6 +138,7 @@ + @@ -212,6 +213,7 @@ TabBase.idl + TaskbarState.idl diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 12b3fa2add..6dbf62453d 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -41,6 +41,9 @@ highlightedText + + fzf + @@ -77,6 +80,12 @@ highlightedText + + fzf + + + fzf + @@ -176,6 +185,9 @@ {e490f626-547d-4b5b-b22d-c6d33c9e3210} + + {e4588ff4-c80a-40f7-be57-3e81f570a93d} + diff --git a/src/cascadia/TerminalApp/fzf/LICENSE b/src/cascadia/TerminalApp/fzf/LICENSE new file mode 100644 index 0000000000..04ac2144c3 --- /dev/null +++ b/src/cascadia/TerminalApp/fzf/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2013-2024 Junegunn Choi +Copyright (c) 2021-2025 Simon Hauser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/cascadia/TerminalApp/fzf/fzf.cpp b/src/cascadia/TerminalApp/fzf/fzf.cpp new file mode 100644 index 0000000000..6f2ac764cf --- /dev/null +++ b/src/cascadia/TerminalApp/fzf/fzf.cpp @@ -0,0 +1,432 @@ +#include "pch.h" +#include "fzf.h" + +#undef CharLower +#undef CharUpper + +using namespace fzf::matcher; + +constexpr int16_t ScoreMatch = 16; +constexpr int16_t ScoreGapStart = -3; +constexpr int16_t ScoreGapExtension = -1; +constexpr int16_t BoundaryBonus = ScoreMatch / 2; +constexpr int16_t NonWordBonus = ScoreMatch / 2; +constexpr int16_t CamelCaseBonus = BoundaryBonus + ScoreGapExtension; +constexpr int16_t BonusConsecutive = -(ScoreGapStart + ScoreGapExtension); +constexpr int16_t BonusFirstCharMultiplier = 2; +constexpr size_t npos = std::numeric_limits::max(); + +enum class CharClass : uint8_t +{ + NonWord = 0, + CharLower = 1, + CharUpper = 2, + Digit = 3, +}; + +static std::vector utf16ToUtf32(std::wstring_view text) +{ + const UChar* data = reinterpret_cast(text.data()); + int32_t dataLen = static_cast(text.size()); + int32_t cpCount = u_countChar32(data, dataLen); + + std::vector out(cpCount); + + UErrorCode status = U_ZERO_ERROR; + u_strToUTF32(out.data(), static_cast(out.size()), nullptr, data, dataLen, &status); + THROW_HR_IF(E_UNEXPECTED, status > U_ZERO_ERROR); + + return out; +} + +static void foldStringUtf32(std::vector& str) +{ + for (auto& cp : str) + { + cp = u_foldCase(cp, U_FOLD_CASE_DEFAULT); + } +} + +static size_t trySkip(const std::vector& input, const UChar32 searchChar, size_t startIndex) +{ + for (size_t i = startIndex; i < input.size(); ++i) + { + if (input[i] == searchChar) + { + return i; + } + } + return npos; +} + +// Unlike the equivalent in fzf, this one does more than Unicode. +static size_t asciiFuzzyIndex(const std::vector& input, const std::vector& pattern) +{ + size_t idx = 0; + size_t firstIdx = 0; + for (size_t pi = 0; pi < pattern.size(); ++pi) + { + idx = trySkip(input, pattern[pi], idx); + if (idx == npos) + { + return npos; + } + + if (pi == 0 && idx > 0) + { + firstIdx = idx - 1; + } + + idx++; + } + return firstIdx; +} + +static int16_t calculateBonus(CharClass prevClass, CharClass currentClass) +{ + if (prevClass == CharClass::NonWord && currentClass != CharClass::NonWord) + { + return BoundaryBonus; + } + if ((prevClass == CharClass::CharLower && currentClass == CharClass::CharUpper) || + (prevClass != CharClass::Digit && currentClass == CharClass::Digit)) + { + return CamelCaseBonus; + } + if (currentClass == CharClass::NonWord) + { + return NonWordBonus; + } + return 0; +} + +static constexpr auto s_charClassLut = []() { + std::array lut{}; + lut.fill(CharClass::NonWord); + lut[U_UPPERCASE_LETTER] = CharClass::CharUpper; + lut[U_LOWERCASE_LETTER] = CharClass::CharLower; + lut[U_MODIFIER_LETTER] = CharClass::CharLower; + lut[U_OTHER_LETTER] = CharClass::CharLower; + lut[U_DECIMAL_DIGIT_NUMBER] = CharClass::Digit; + return lut; +}(); + +static CharClass classOf(UChar32 ch) +{ + return s_charClassLut[u_charType(ch)]; +} + +static int32_t fzfFuzzyMatchV2(const std::vector& text, const std::vector& pattern, std::vector* pos) +{ + if (pattern.size() == 0) + { + return 0; + } + + auto foldedText = text; + foldStringUtf32(foldedText); + + size_t firstIndexOf = asciiFuzzyIndex(foldedText, pattern); + if (firstIndexOf == npos) + { + return 0; + } + + auto initialScores = std::vector(text.size()); + auto consecutiveScores = std::vector(text.size()); + auto firstOccurrenceOfEachChar = std::vector(pattern.size()); + auto bonusesSpan = std::vector(text.size()); + + int16_t maxScore = 0; + size_t maxScorePos = 0; + size_t patternIndex = 0; + size_t lastIndex = 0; + UChar32 firstPatternChar = pattern[0]; + UChar32 currentPatternChar = pattern[0]; + int16_t previousInitialScore = 0; + CharClass previousClass = CharClass::NonWord; + bool inGap = false; + + std::span lowerText(foldedText); + auto lowerTextSlice = lowerText.subspan(firstIndexOf); + auto initialScoresSlice = std::span(initialScores).subspan(firstIndexOf); + auto consecutiveScoresSlice = std::span(consecutiveScores).subspan(firstIndexOf); + auto bonusesSlice = std::span(bonusesSpan).subspan(firstIndexOf, text.size() - firstIndexOf); + + for (size_t i = 0; i < lowerTextSlice.size(); i++) + { + const auto currentChar = lowerTextSlice[i]; + const auto currentClass = classOf(text[i + firstIndexOf]); + const auto bonus = calculateBonus(previousClass, currentClass); + bonusesSlice[i] = bonus; + previousClass = currentClass; + + //currentPatternChar was already folded in ParsePattern + if (currentChar == currentPatternChar) + { + if (patternIndex < pattern.size()) + { + firstOccurrenceOfEachChar[patternIndex] = firstIndexOf + i; + patternIndex++; + if (patternIndex < pattern.size()) + { + currentPatternChar = pattern[patternIndex]; + } + } + lastIndex = firstIndexOf + i; + } + if (currentChar == firstPatternChar) + { + int16_t score = ScoreMatch + bonus * BonusFirstCharMultiplier; + initialScoresSlice[i] = score; + consecutiveScoresSlice[i] = 1; + if (pattern.size() == 1 && (score > maxScore)) + { + maxScore = score; + maxScorePos = firstIndexOf + i; + if (bonus == BoundaryBonus) + { + break; + } + } + inGap = false; + } + else + { + initialScoresSlice[i] = std::max(previousInitialScore + (inGap ? ScoreGapExtension : ScoreGapStart), 0); + consecutiveScoresSlice[i] = 0; + inGap = true; + } + previousInitialScore = initialScoresSlice[i]; + } + + if (patternIndex != pattern.size()) + { + return 0; + } + + if (pattern.size() == 1) + { + if (pos) + { + pos->push_back(maxScorePos); + } + return maxScore; + } + + const auto firstOccurrenceOfFirstChar = firstOccurrenceOfEachChar[0]; + const auto width = lastIndex - firstOccurrenceOfFirstChar + 1; + const auto rows = pattern.size(); + auto consecutiveCharMatrixSize = width * pattern.size(); + + std::vector scoreMatrix(width * rows); + std::copy_n(initialScores.begin() + firstOccurrenceOfFirstChar, width, scoreMatrix.begin()); + std::span scoreSpan(scoreMatrix); + + std::vector consecutiveCharMatrix(width * rows); + std::copy_n(consecutiveScores.begin() + firstOccurrenceOfFirstChar, width, consecutiveCharMatrix.begin()); + std::span consecutiveCharMatrixSpan(consecutiveCharMatrix); + + auto patternSliceStr = std::span(pattern).subspan(1); + + for (size_t off = 0; off < pattern.size() - 1; off++) + { + auto patternCharOffset = firstOccurrenceOfEachChar[off + 1]; + auto sliceLen = lastIndex - patternCharOffset + 1; + currentPatternChar = patternSliceStr[off]; + patternIndex = off + 1; + auto row = patternIndex * width; + inGap = false; + std::span textSlice = lowerText.subspan(patternCharOffset, sliceLen); + std::span bonusSlice(bonusesSpan.begin() + patternCharOffset, textSlice.size()); + std::span consecutiveCharMatrixSlice = consecutiveCharMatrixSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar, textSlice.size()); + std::span consecutiveCharMatrixDiagonalSlice = consecutiveCharMatrixSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1 - width, textSlice.size()); + std::span scoreMatrixSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar, textSlice.size()); + std::span scoreMatrixDiagonalSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1 - width, textSlice.size()); + std::span scoreMatrixLeftSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1, textSlice.size()); + + if (!scoreMatrixLeftSlice.empty()) + { + scoreMatrixLeftSlice[0] = 0; + } + + for (size_t j = 0; j < textSlice.size(); j++) + { + const auto currentChar = textSlice[j]; + const auto column = patternCharOffset + j; + const int16_t score = inGap ? scoreMatrixLeftSlice[j] + ScoreGapExtension : scoreMatrixLeftSlice[j] + ScoreGapStart; + int16_t diagonalScore = 0; + int16_t consecutive = 0; + if (currentChar == currentPatternChar) + { + diagonalScore = scoreMatrixDiagonalSlice[j] + ScoreMatch; + int16_t bonus = bonusSlice[j]; + consecutive = consecutiveCharMatrixDiagonalSlice[j] + 1; + if (bonus == BoundaryBonus) + { + consecutive = 1; + } + else if (consecutive > 1) + { + bonus = std::max({ bonus, BonusConsecutive, (bonusesSpan[column - consecutive + 1]) }); + } + if (diagonalScore + bonus < score) + { + diagonalScore += bonusSlice[j]; + consecutive = 0; + } + else + { + diagonalScore += bonus; + } + } + consecutiveCharMatrixSlice[j] = consecutive; + inGap = (diagonalScore < score); + int16_t cellScore = std::max(int16_t{ 0 }, std::max(diagonalScore, score)); + if (off + 2 == pattern.size() && cellScore > maxScore) + { + maxScore = cellScore; + maxScorePos = column; + } + scoreMatrixSlice[j] = cellScore; + } + } + + size_t currentColIndex = maxScorePos; + if (pos) + { + patternIndex = pattern.size() - 1; + bool preferCurrentMatch = true; + while (true) + { + const auto rowStartIndex = patternIndex * width; + const auto colOffset = currentColIndex - firstOccurrenceOfFirstChar; + const auto cellScore = scoreMatrix[rowStartIndex + colOffset]; + int32_t diagonalCellScore = 0; + int32_t leftCellScore = 0; + + if (patternIndex > 0 && currentColIndex >= firstOccurrenceOfEachChar[patternIndex]) + { + diagonalCellScore = scoreMatrix[rowStartIndex - width + colOffset - 1]; + } + if (currentColIndex > firstOccurrenceOfEachChar[patternIndex]) + { + leftCellScore = scoreMatrix[rowStartIndex + colOffset - 1]; + } + + if (cellScore > diagonalCellScore && + (cellScore > leftCellScore || (cellScore == leftCellScore && preferCurrentMatch))) + { + pos->push_back(currentColIndex); + if (patternIndex == 0) + { + break; + } + patternIndex--; + } + + currentColIndex--; + if (rowStartIndex + colOffset >= consecutiveCharMatrixSize) + { + break; + } + + preferCurrentMatch = (consecutiveCharMatrix[rowStartIndex + colOffset] > 1) || + ((rowStartIndex + width + colOffset + 1 < + consecutiveCharMatrixSize) && + (consecutiveCharMatrix[rowStartIndex + width + colOffset + 1] > 0)); + } + } + return maxScore; +} + +Pattern fzf::matcher::ParsePattern(const std::wstring_view patternStr) +{ + Pattern patObj; + size_t pos = 0; + + while (true) + { + const auto beg = patternStr.find_first_not_of(L' ', pos); + if (beg == std::wstring_view::npos) + { + break; // No more non-space characters + } + + const auto end = std::min(patternStr.size(), patternStr.find_first_of(L' ', beg)); + const auto word = patternStr.substr(beg, end - beg); + auto codePoints = utf16ToUtf32(word); + foldStringUtf32(codePoints); + patObj.terms.push_back(std::move(codePoints)); + pos = end; + } + + return patObj; +} + +std::optional fzf::matcher::Match(std::wstring_view text, const Pattern& pattern) +{ + if (pattern.terms.empty()) + { + return MatchResult{}; + } + + const auto textCodePoints = utf16ToUtf32(text); + + int32_t totalScore = 0; + std::vector allUtf32Pos; + + for (const auto& term : pattern.terms) + { + std::vector termPos; + auto score = fzfFuzzyMatchV2(textCodePoints, term, &termPos); + if (score <= 0) + { + return std::nullopt; + } + + totalScore += score; + allUtf32Pos.insert(allUtf32Pos.end(), termPos.begin(), termPos.end()); + } + + std::ranges::sort(allUtf32Pos); + allUtf32Pos.erase(std::ranges::unique(allUtf32Pos).begin(), allUtf32Pos.end()); + + std::vector runs; + std::size_t nextCodePointPos = 0; + size_t utf16Offset = 0; + + bool inRun = false; + size_t runStart = 0; + + for (size_t cpIndex = 0; cpIndex < textCodePoints.size(); cpIndex++) + { + const auto cp = textCodePoints[cpIndex]; + const size_t cpWidth = U16_LENGTH(cp); + + const bool isMatch = (nextCodePointPos < allUtf32Pos.size() && allUtf32Pos[nextCodePointPos] == cpIndex); + if (isMatch) + { + if (!inRun) + { + runStart = utf16Offset; + inRun = true; + } + nextCodePointPos++; + } + else if (inRun) + { + runs.push_back({ runStart, utf16Offset - 1 }); + inRun = false; + } + + utf16Offset += cpWidth; + } + + if (inRun) + { + runs.push_back({ runStart, utf16Offset - 1 }); + } + + return MatchResult{ totalScore, std::move(runs) }; +} diff --git a/src/cascadia/TerminalApp/fzf/fzf.h b/src/cascadia/TerminalApp/fzf/fzf.h new file mode 100644 index 0000000000..2af4b75f91 --- /dev/null +++ b/src/cascadia/TerminalApp/fzf/fzf.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace fzf::matcher +{ + struct TextRun + { + size_t Start; + size_t End; + }; + + struct MatchResult + { + int32_t Score = 0; + std::vector Runs; + }; + + struct Pattern + { + std::vector> terms; + }; + + Pattern ParsePattern(std::wstring_view patternStr); + std::optional Match(std::wstring_view text, const Pattern& pattern); +} diff --git a/src/cascadia/ut_app/FzfTests.cpp b/src/cascadia/ut_app/FzfTests.cpp new file mode 100644 index 0000000000..e4f1fd1903 --- /dev/null +++ b/src/cascadia/ut_app/FzfTests.cpp @@ -0,0 +1,568 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "..\TerminalApp\fzf\fzf.h" + +using namespace Microsoft::Console; +using namespace WEX::Logging; +using namespace WEX::TestExecution; +using namespace WEX::Common; + +namespace TerminalAppUnitTests +{ + typedef enum + { + ScoreMatch = 16, + ScoreGapStart = -3, + ScoreGapExtension = -1, + BonusBoundary = ScoreMatch / 2, + BonusNonWord = ScoreMatch / 2, + BonusCamel123 = BonusBoundary + ScoreGapExtension, + BonusConsecutive = -(ScoreGapStart + ScoreGapExtension), + BonusFirstCharMultiplier = 2, + } score_t; + + class FzfTests + { + BEGIN_TEST_CLASS(FzfTests) + END_TEST_CLASS() + + TEST_METHOD(AllPatternCharsDoNotMatch); + TEST_METHOD(ConsecutiveChars); + TEST_METHOD(ConsecutiveChars_FirstCharBonus); + TEST_METHOD(NonWordBonusBoundary_ConsecutiveChars); + TEST_METHOD(MatchOnNonWordChars_CaseInSensitive); + TEST_METHOD(MatchOnNonWordCharsWithGap); + TEST_METHOD(BonusForCamelCaseMatch); + TEST_METHOD(BonusBoundaryAndFirstCharMultiplier); + TEST_METHOD(MatchesAreCaseInSensitive); + TEST_METHOD(MultipleTerms); + TEST_METHOD(MultipleTerms_AllCharsMatch); + TEST_METHOD(MultipleTerms_NotAllTermsMatch); + TEST_METHOD(MatchesAreCaseInSensitive_BonusBoundary); + TEST_METHOD(TraceBackWillPickTheFirstMatchIfBothHaveTheSameScore); + TEST_METHOD(TraceBackWillPickTheMatchWithTheHighestScore); + TEST_METHOD(TraceBackWillPickTheMatchWithTheHighestScore_Gaps); + TEST_METHOD(TraceBackWillPickEarlierCharsWhenNoBonus); + TEST_METHOD(MatchWithGapCanAHaveHigherScoreThanConsecutiveWhenGapMatchHasBoundaryBonus); + TEST_METHOD(ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothHaveFirstCharBonus); + TEST_METHOD(ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothDontHaveBonus); + TEST_METHOD(MatchWithGapCanHaveHigherScoreThanConsecutiveWhenGapHasFirstCharBonus); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerScoreHigherThanConsecutiveCharsWhenTheGapIs3_NoConsecutiveChar_4CharPattern); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_2CharPattern); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_3CharPattern_1ConsecutiveChar); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs5_NoConsecutiveChars_3CharPattern); + TEST_METHOD(Russian_CaseMisMatch); + TEST_METHOD(Russian_CaseMatch); + TEST_METHOD(English_CaseMatch); + TEST_METHOD(English_CaseMisMatch); + TEST_METHOD(SurrogatePair); + TEST_METHOD(French_CaseMatch); + TEST_METHOD(French_CaseMisMatch); + TEST_METHOD(German_CaseMatch); + TEST_METHOD(German_CaseMisMatch_FoldResultsInMultipleCodePoints); + TEST_METHOD(Greek_CaseMisMatch); + TEST_METHOD(Greek_CaseMatch); + TEST_METHOD(SurrogatePair_ToUtf16Pos_ConsecutiveChars); + TEST_METHOD(SurrogatePair_ToUtf16Pos_PreferConsecutiveChars); + TEST_METHOD(SurrogatePair_ToUtf16Pos_GapAndBoundary); + }; + + void AssertScoreAndRuns(std::wstring_view patternText, std::wstring_view text, int expectedScore, const std::vector& expectedRuns) + { + const auto pattern = fzf::matcher::ParsePattern(patternText); + const auto match = fzf::matcher::Match(text, pattern); + + if (expectedScore == 0 && expectedRuns.empty()) + { + VERIFY_ARE_EQUAL(std::nullopt, match); + return; + } + + VERIFY_IS_TRUE(match.has_value()); + VERIFY_ARE_EQUAL(expectedScore, match->Score); + + const auto& runs = match->Runs; + VERIFY_ARE_EQUAL(expectedRuns.size(), runs.size()); + + for (size_t i = 0; i < expectedRuns.size(); ++i) + { + VERIFY_ARE_EQUAL(expectedRuns[i].Start, runs[i].Start); + VERIFY_ARE_EQUAL(expectedRuns[i].End, runs[i].End); + } + } + + void FzfTests::AllPatternCharsDoNotMatch() + { + AssertScoreAndRuns( + L"fbb", + L"foo bar", + 0, + {}); + } + + void FzfTests::ConsecutiveChars() + { + AssertScoreAndRuns( + L"oba", + L"foobar", + ScoreMatch * 3 + BonusConsecutive * 2, + { { 2, 4 } }); + } + + void FzfTests::ConsecutiveChars_FirstCharBonus() + { + AssertScoreAndRuns( + L"foo", + L"foobar", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::NonWordBonusBoundary_ConsecutiveChars() + { + AssertScoreAndRuns( + L"zshc", + L"/man1/zshcompctl.1", + ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + BonusFirstCharMultiplier * BonusConsecutive * 3, + { { 6, 9 } }); + } + + void FzfTests::Russian_CaseMisMatch() + { + AssertScoreAndRuns( + L"новая", + L"Новая вкладка", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::Russian_CaseMatch() + { + AssertScoreAndRuns( + L"Новая", + L"Новая вкладка", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::German_CaseMatch() + { + AssertScoreAndRuns( + L"fuß", + L"Fußball", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::German_CaseMisMatch_FoldResultsInMultipleCodePoints() + { + //This doesn't currently pass, I think ucase_toFullFolding would give the number of code points that resulted from the fold. + //I wasn't sure how to reference that + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Ignore", L"true") + END_TEST_METHOD_PROPERTIES() + + AssertScoreAndRuns( + L"fuss", + L"Fußball", + //I think ScoreMatch * 4 is correct in this case since it matches 4 codepoints pattern??? fuss + ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 3, + //Only 3 positions in the text were matched + { { 0, 2 } }); + } + + void FzfTests::French_CaseMatch() + { + AssertScoreAndRuns( + L"Éco", + L"École", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::French_CaseMisMatch() + { + AssertScoreAndRuns( + L"Éco", + L"école", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::Greek_CaseMatch() + { + AssertScoreAndRuns( + L"λόγος", + L"λόγος", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::Greek_CaseMisMatch() + { + //I think this tests validates folding (σ, ς) + AssertScoreAndRuns( + L"λόγοσ", + L"λόγος", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::English_CaseMatch() + { + AssertScoreAndRuns( + L"Newer", + L"Newer tab", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::English_CaseMisMatch() + { + AssertScoreAndRuns( + L"newer", + L"Newer tab", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::SurrogatePair() + { + AssertScoreAndRuns( + L"N😀ewer", + L"N😀ewer tab", + ScoreMatch * 6 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 5, + { { 0, 6 } }); + } + + void FzfTests::SurrogatePair_ToUtf16Pos_ConsecutiveChars() + { + AssertScoreAndRuns( + L"N𠀋N😀𝄞e𐐷", + L"N𠀋N😀𝄞e𐐷 tab", + ScoreMatch * 7 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 6, + { { 0, 10 } }); + } + + void FzfTests::SurrogatePair_ToUtf16Pos_PreferConsecutiveChars() + { + AssertScoreAndRuns( + L"𠀋😀", + L"N𠀋😀wer 😀b𐐷 ", + ScoreMatch * 2 + BonusConsecutive * 2, + { { 1, 4 } }); + } + + void FzfTests::SurrogatePair_ToUtf16Pos_GapAndBoundary() + { + AssertScoreAndRuns( + L"𠀋😀", + L"N𠀋wer 😀b𐐷 ", + ScoreMatch * 2 + ScoreGapStart + ScoreGapExtension * 3 + BonusBoundary, + { { 1, 2 }, { 7, 8 } }); + } + + void FzfTests::MatchOnNonWordChars_CaseInSensitive() + { + AssertScoreAndRuns( + L"foo-b", + L"xFoo-Bar Baz", + (ScoreMatch + BonusCamel123 * BonusFirstCharMultiplier) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusBoundary) + + (ScoreMatch + BonusNonWord), + { { 1, 5 } }); + } + + void FzfTests::MatchOnNonWordCharsWithGap() + { + AssertScoreAndRuns( + L"12356", + L"abc123 456", + (ScoreMatch + BonusCamel123 * BonusFirstCharMultiplier) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusCamel123) + + ScoreGapStart + + ScoreGapExtension + + ScoreMatch + + ScoreMatch + BonusConsecutive, + { { 3, 5 }, { 8, 9 } }); + } + + void FzfTests::BonusForCamelCaseMatch() + { + AssertScoreAndRuns( + L"def56", + L"abcDEF 456", + (ScoreMatch + BonusCamel123 * BonusFirstCharMultiplier) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusCamel123) + + ScoreGapStart + + ScoreGapExtension + + ScoreMatch + + (ScoreMatch + BonusConsecutive), + { { 3, 5 }, { 8, 9 } }); + } + + void FzfTests::BonusBoundaryAndFirstCharMultiplier() + { + AssertScoreAndRuns( + L"fbb", + L"foo bar baz", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusBoundary * 2 + 2 * ScoreGapStart + 4 * ScoreGapExtension, + { { 0, 0 }, { 4, 4 }, { 8, 8 } }); + } + + void FzfTests::MatchesAreCaseInSensitive() + { + AssertScoreAndRuns( + L"FBB", + L"foo bar baz", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusBoundary * 2 + 2 * ScoreGapStart + 4 * ScoreGapExtension, + { { 0, 0 }, { 4, 4 }, { 8, 8 } }); + } + + void FzfTests::MultipleTerms() + { + auto term1Score = ScoreMatch * 2 + BonusBoundary * BonusFirstCharMultiplier + (BonusFirstCharMultiplier * BonusConsecutive); + auto term2Score = ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + (BonusFirstCharMultiplier * BonusConsecutive) * 3; + + AssertScoreAndRuns( + L"sp anta", + L"Split Pane, split: horizontal, profile: SSH: Antares", + term1Score + term2Score, + { { 0, 1 }, { 45, 48 } }); + } + + void FzfTests::MultipleTerms_AllCharsMatch() + { + auto term1Score = ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + (BonusFirstCharMultiplier * BonusConsecutive * 2); + auto term2Score = term1Score; + + AssertScoreAndRuns( + L"foo bar", + L"foo bar", + term1Score + term2Score, + { { 0, 2 }, { 4, 6 } }); + } + + void FzfTests::MultipleTerms_NotAllTermsMatch() + { + AssertScoreAndRuns( + L"sp anta zz", + L"Split Pane, split: horizontal, profile: SSH: Antares", + 0, + {}); + } + + void FzfTests::MatchesAreCaseInSensitive_BonusBoundary() + { + AssertScoreAndRuns( + L"fbb", + L"Foo Bar Baz", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusBoundary * 2 + 2 * ScoreGapStart + 4 * ScoreGapExtension, + { { 0, 0 }, { 4, 4 }, { 8, 8 } }); + } + + void FzfTests::TraceBackWillPickTheFirstMatchIfBothHaveTheSameScore() + { + AssertScoreAndRuns( + L"bar", + L"Foo Bar Bar", + (ScoreMatch + BonusBoundary * BonusFirstCharMultiplier) + + (ScoreMatch + BonusBoundary) + + (ScoreMatch + BonusBoundary), + //ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier * 2, + { { 4, 6 } }); + } + + void FzfTests::TraceBackWillPickTheMatchWithTheHighestScore() + { + AssertScoreAndRuns( + L"bar", + L"Foo aBar Bar", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier * 2, + { { 9, 11 } }); + } + + void FzfTests::TraceBackWillPickTheMatchWithTheHighestScore_Gaps() + { + AssertScoreAndRuns( + L"bar", + L"Boo Author Raz Bar", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 15, 17 } }); + } + + void FzfTests::TraceBackWillPickEarlierCharsWhenNoBonus() + { + AssertScoreAndRuns( + L"clts", + L"close all tabs after this", + ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + BonusFirstCharMultiplier * BonusConsecutive + ScoreGapStart + ScoreGapExtension * 7 + BonusBoundary + ScoreGapStart + ScoreGapExtension, + { { 0, 1 }, { 10, 10 }, { 13, 13 } }); + } + + void FzfTests::ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothDontHaveBonus() + { + auto consecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto gapScore = (ScoreMatch * 3) + ScoreGapStart + ScoreGapStart; + + AssertScoreAndRuns( + L"oob", + L"aoobar", + consecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oob", + L"aoaoabound", + gapScore, + { { 1, 1 }, { 3, 3 }, { 5, 5 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothHaveFirstCharBonus() + { + auto consecutiveScore = ScoreMatch * 3 + BonusFirstCharMultiplier * BonusBoundary + BonusFirstCharMultiplier * BonusConsecutive * 2; + auto gapScore = (ScoreMatch * 3) + (BonusBoundary * BonusFirstCharMultiplier) + ScoreGapStart + ScoreGapStart; + + AssertScoreAndRuns( + L"oob", + L"oobar", + consecutiveScore, + { { 0, 2 } }); + + AssertScoreAndRuns( + L"oob", + L"oaoabound", + gapScore, + { { 0, 0 }, { 2, 2 }, { 4, 4 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::MatchWithGapCanAHaveHigherScoreThanConsecutiveWhenGapMatchHasBoundaryBonus() + { + auto consecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto gapScore = (ScoreMatch * 3) + (BonusBoundary * BonusFirstCharMultiplier) + (BonusBoundary * 2) + ScoreGapStart + (ScoreGapExtension * 2) + ScoreGapStart + ScoreGapExtension; + + AssertScoreAndRuns( + L"oob", + L"foobar", + consecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oob", + L"out-of-bound", + gapScore, + { { 0, 0 }, { 4, 4 }, { 7, 7 } }); + + VERIFY_IS_GREATER_THAN(gapScore, consecutiveScore); + } + + void FzfTests::MatchWithGapCanHaveHigherScoreThanConsecutiveWhenGapHasFirstCharBonus() + { + auto consecutiveScore = ScoreMatch * 2 + BonusConsecutive; + auto gapScore = ScoreMatch * 2 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart; + + AssertScoreAndRuns( + L"ob", + L"aobar", + consecutiveScore, + { { 1, 2 } }); + + AssertScoreAndRuns( + L"ob", + L"oabar", + gapScore, + { { 0, 0 }, { 2, 2 } }); + + VERIFY_IS_GREATER_THAN(gapScore, consecutiveScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_2CharPattern() + { + auto consecutiveScore = ScoreMatch * 2 + BonusConsecutive; + auto gapScore = ScoreMatch * 2 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart + ScoreGapExtension * 10; + + AssertScoreAndRuns( + L"ob", + L"aobar", + consecutiveScore, + { { 1, 2 } }); + + AssertScoreAndRuns( + L"ob", + L"oaaaaaaaaaaabar", + gapScore, + { { 0, 0 }, { 12, 12 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_3CharPattern_1ConsecutiveChar() + { + auto consecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto gapScore = ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive + ScoreGapStart + ScoreGapExtension * 10; + + AssertScoreAndRuns( + L"oba", + L"aobar", + consecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oba", + L"oaaaaaaaaaaabar", + gapScore, + { { 0, 0 }, { 12, 13 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs5_NoConsecutiveChars_3CharPattern() + { + auto allConsecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto allBoundaryWithGapScore = ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart + ScoreGapExtension + ScoreGapExtension + ScoreGapStart + ScoreGapExtension; + + AssertScoreAndRuns( + L"oba", + L"aobar", + allConsecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oba", + L"oaaabzzar", + allBoundaryWithGapScore, + { { 0, 0 }, { 4, 4 }, { 7, 7 } }); + + VERIFY_IS_GREATER_THAN(allConsecutiveScore, allBoundaryWithGapScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerScoreHigherThanConsecutiveCharsWhenTheGapIs3_NoConsecutiveChar_4CharPattern() + { + auto consecutiveScore = ScoreMatch * 4 + BonusConsecutive * 3; + auto gapScore = ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart * 3; + + AssertScoreAndRuns( + L"obar", + L"aobar", + consecutiveScore, + { { 1, 4 } }); + + AssertScoreAndRuns( + L"obar", + L"oabzazr", + gapScore, + { { 0, 0 }, { 2, 2 }, { 4, 4 }, { 6, 6 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } +} diff --git a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj index 705d5e3592..608bce64b6 100644 --- a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj +++ b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj @@ -24,6 +24,7 @@ + Create diff --git a/src/inc/til.h b/src/inc/til.h index c79b0debdc..531e571f75 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -57,7 +57,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" template as_view_t safe_slice_abs(const T& view, size_t beg, size_t end) { - const auto len = view.size(); + const size_t len = view.size(); end = std::min(end, len); beg = std::min(beg, end); return { view.data() + beg, end - beg }; @@ -66,7 +66,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" template as_view_t safe_slice_len(const T& view, size_t start, size_t count) { - const auto len = view.size(); + const size_t len = view.size(); start = std::min(start, len); count = std::min(count, len - start); return { view.data() + start, count }; diff --git a/src/inc/til/type_traits.h b/src/inc/til/type_traits.h index 1f426155eb..f38380ada9 100644 --- a/src/inc/til/type_traits.h +++ b/src/inc/til/type_traits.h @@ -3,6 +3,11 @@ #pragma once +namespace winrt +{ + struct hstring; +} + namespace til { namespace details @@ -50,6 +55,12 @@ namespace til { using type = std::basic_string_view; }; + + template<> + struct as_view + { + using type = std::wstring_view; + }; } template From 155d8a9ab264742d4bbaf243b922835417fe9901 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 3 Jun 2025 17:38:15 +0200 Subject: [PATCH 071/177] Tweak spacing on about dialog (#18993) Some minor styling tweaks to the about page. - Tweaking the spacing of the version-checking / update available text. - Wrapping the hyperlinkbuttons in a StackPanel so it's easier to set a negative margin so they are visually aligned with the version / title text. Closes #18994 --- src/cascadia/TerminalApp/AboutDialog.xaml | 31 +++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/cascadia/TerminalApp/AboutDialog.xaml b/src/cascadia/TerminalApp/AboutDialog.xaml index b9aebf9a9b..ff075efb9a 100644 --- a/src/cascadia/TerminalApp/AboutDialog.xaml +++ b/src/cascadia/TerminalApp/AboutDialog.xaml @@ -28,18 +28,18 @@ - - + - @@ -51,15 +51,18 @@ - - - - - + + + + + + + From 4abc041eb78902708e1844902e40a88d0bc3d50a Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 10 Jun 2025 21:09:51 +0100 Subject: [PATCH 072/177] Add support for OSC 52 clipboard copy in conhost (#18949) This adds support for copying to the clipboard in conhost using the OSC 52 escape sequence, extending the original implementation which was for Windows Terminal only. The Windows Terminal implementation was added in PR #5823. Because the clipboard can't be accessed from a background thread, this works by saving the content in a global variable, and then posting a custom message to the main GUI thread, which takes care of the actual copy operation. Validation: I've manually confirmed that tmux copy mode is now able to copy to the system clipboard. Closes #18943 --- src/host/consoleInformation.cpp | 27 +++++++++++++++++++ src/host/outputStream.cpp | 4 +-- src/host/server.h | 3 +++ src/interactivity/win32/Clipboard.cpp | 16 +++++++++++ .../win32/CustomWindowMessages.h | 2 ++ src/interactivity/win32/clipboard.hpp | 1 + src/interactivity/win32/windowproc.cpp | 9 +++++++ 7 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index d4e5926f6c..7146bee438 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -13,6 +13,7 @@ #include "srvinit.h" #include "../interactivity/inc/ServiceLocator.hpp" +#include "../interactivity/win32/CustomWindowMessages.h" #include "../types/inc/convert.hpp" using Microsoft::Console::Interactivity::ServiceLocator; @@ -179,6 +180,32 @@ void CONSOLE_INFORMATION::SetBracketedPasteMode(const bool enabled) noexcept _bracketedPasteMode = enabled; } +void CONSOLE_INFORMATION::CopyTextToClipboard(const std::wstring_view text) +{ + const auto window = ServiceLocator::LocateConsoleWindow(); + if (window) + { + // The clipboard can only be updated from the main GUI thread, so we + // need to post a message to trigger the actual copy operation. But if + // the pending clipboard content is already set, a message would have + // already been posted, so there's no need to post another one. + const auto clipboardMessageSent = _pendingClipboardText.has_value(); + _pendingClipboardText = text; + if (!clipboardMessageSent) + { + PostMessageW(window->GetWindowHandle(), CM_UPDATE_CLIPBOARD, 0, 0); + } + } +} + +std::optional CONSOLE_INFORMATION::UsePendingClipboardText() +{ + // Once the pending text has been used, we clear the variable to let the + // CopyTextToClipboard method know that the last CM_UPDATE_CLIPBOARD message + // has been processed, and future updates will require another message. + return std::exchange(_pendingClipboardText, {}); +} + // Method Description: // - Return the active screen buffer of the console. // Arguments: diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 3e8219a9bb..9107413e7e 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -280,9 +280,9 @@ unsigned int ConhostInternalGetSet::GetInputCodePage() const // - content - the text to be copied. // Return Value: // - -void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view /*content*/) +void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view content) { - // TODO + ServiceLocator::LocateGlobals().getConsoleInformation().CopyTextToClipboard(content); } // Routine Description: diff --git a/src/host/server.h b/src/host/server.h index d62484b3eb..d318808a8c 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -126,6 +126,8 @@ public: bool GetBracketedPasteMode() const noexcept; void SetBracketedPasteMode(const bool enabled) noexcept; + void CopyTextToClipboard(const std::wstring_view text); + std::optional UsePendingClipboardText(); void SetTitle(const std::wstring_view newTitle); void SetTitlePrefix(const std::wstring_view newTitlePrefix); @@ -160,6 +162,7 @@ private: SCREEN_INFORMATION* pCurrentScreenBuffer = nullptr; COOKED_READ_DATA* _cookedReadData = nullptr; // non-ownership pointer bool _bracketedPasteMode = false; + std::optional _pendingClipboardText; Microsoft::Console::VirtualTerminal::VtIo _vtIo; Microsoft::Console::CursorBlinker _blinker; diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 8c42e91af9..b022947bbd 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -24,6 +24,22 @@ using namespace Microsoft::Console::Types; #pragma region Public Methods +void Clipboard::CopyText(const std::wstring& text) +{ + const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle()); + if (!clipboard) + { + LOG_LAST_ERROR(); + return; + } + + EmptyClipboard(); + // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + // CF_UNICODETEXT: [...] A null character signals the end of the data. + // --> We add +1 to the length. This works because .c_str() is null-terminated. + _copyToClipboard(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t)); +} + // Arguments: // - fAlsoCopyFormatting - Place colored HTML & RTF text onto the clipboard as well as the usual plain text. // Return Value: diff --git a/src/interactivity/win32/CustomWindowMessages.h b/src/interactivity/win32/CustomWindowMessages.h index bf36660249..bcc43fdb7a 100644 --- a/src/interactivity/win32/CustomWindowMessages.h +++ b/src/interactivity/win32/CustomWindowMessages.h @@ -29,4 +29,6 @@ #define CM_SET_KEYBOARD_LAYOUT (WM_USER+19) #endif +#define CM_UPDATE_CLIPBOARD (WM_USER+20) + // clang-format on diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp index 09990ab514..54851fa55b 100644 --- a/src/interactivity/win32/clipboard.hpp +++ b/src/interactivity/win32/clipboard.hpp @@ -29,6 +29,7 @@ namespace Microsoft::Console::Interactivity::Win32 public: static Clipboard& Instance(); + void CopyText(const std::wstring& text); void Copy(_In_ const bool fAlsoCopyFormatting = false); void Paste(); void PasteDrop(HDROP drop); diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index c3f1e68c29..3517ddd839 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -773,6 +773,15 @@ static constexpr TsfDataProvider s_tsfDataProvider; } #endif // DBG + case CM_UPDATE_CLIPBOARD: + { + if (const auto clipboardText = gci.UsePendingClipboardText()) + { + Clipboard::Instance().CopyText(clipboardText.value()); + } + break; + } + case EVENT_CONSOLE_CARET: case EVENT_CONSOLE_UPDATE_REGION: case EVENT_CONSOLE_UPDATE_SIMPLE: From 557a193cb7affac7b1564c5fcd6d3a2ddc7e5fd6 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 11 Jun 2025 01:04:56 +0100 Subject: [PATCH 073/177] Improve correctness of Sixel background fill (#18260) This is an attempt to improve the correctness of the background fill that the Sixel parser performs when an image is scrolled because it doesn't fit on the screen. This new behavior may not be exactly correct, but does at least match the VT330 and VT340 hardware more closely than it did before. The initial Sixel parser implementation was in PR #17421. When a Sixel image has the background select parameter set to 0 or 2, it fills the background up to the active raster attribute dimensions prior to writing out any actual pixel data. But this fill operation is clamped at the boundaries of the screen, so if the image doesn't fit, and needs to scroll, we have to perform an additional fill at that point to cover the background of the newly revealed rows (this is something we weren't doing before). This later fill uses the width of the most recent raster attributes command, which is not necessarily the same as the initial background fill, and fills the entire height of the new rows, regardless of the height specified in the raster attributes command. ## Validation Steps Performed Thanks to @al20878 and @hackerb9, we've been able to test on both a VT330 and a VT340, and I've confirmed that we're matching the behavior of those terminals as closely as possible. There are some edge cases where they don't agree with each other, so we can't always match both. I've also confirmed that the test case in issue #17946 now matches what the OP was expecting, and the test case in #17887 at least works more consistently now when scrolling (this is not what the OP was expecting though). Closes #17887 Closes #17946 --- src/terminal/adapter/SixelParser.cpp | 80 +++++++++++++++++++++++----- src/terminal/adapter/SixelParser.hpp | 4 ++ 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/terminal/adapter/SixelParser.cpp b/src/terminal/adapter/SixelParser.cpp index 60e7fecaee..15b9499d2a 100644 --- a/src/terminal/adapter/SixelParser.cpp +++ b/src/terminal/adapter/SixelParser.cpp @@ -237,6 +237,7 @@ void SixelParser::_executeNextLine() _imageCursor.y += _sixelHeight; _availablePixelHeight -= _sixelHeight; _resizeImageBuffer(_sixelHeight); + _fillImageBackgroundWhenScrolled(); } void SixelParser::_executeMoveToHome() @@ -341,6 +342,12 @@ void SixelParser::_initRasterAttributes(const VTInt macroParameter, const Dispat // By default, the filled area will cover the maximum extent allowed. _backgroundSize = { til::CoordTypeMax, til::CoordTypeMax }; + + // If the image ends up extending beyond the bottom of the page, we may need + // to perform additional background filling as the screen is scrolled, which + // requires us to track the area filled so far. This will be initialized, if + // necessary, in the _fillImageBackground method below. + _filledBackgroundHeight = std::nullopt; } void SixelParser::_updateRasterAttributes(const VTParameters& rasterAttributes) @@ -373,6 +380,15 @@ void SixelParser::_updateRasterAttributes(const VTParameters& rasterAttributes) // back to the dimensions from an earlier raster attributes command. _backgroundSize.width = width > 0 ? width : _backgroundSize.width; _backgroundSize.height = height > 0 ? height : _backgroundSize.height; + + // If the aspect ratio has changed, the image height may increase, and that + // could potentially trigger a scroll requiring the background to be filled. + _fillImageBackgroundWhenScrolled(); + + // And while not documented, we know from testing on a VT330 that the raster + // attributes command should also trigger a carriage return. This applies + // regardless of whether the requested aspect ratio is valid or not. + _executeCarriageReturn(); } void SixelParser::_scrollTextBuffer(Page& page, const int scrollAmount) @@ -656,24 +672,60 @@ void SixelParser::_fillImageBackground() { _backgroundFillRequired = false; - const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight); - const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth); - _resizeImageBuffer(backgroundHeight); - // When a background fill is requested, we prefill the buffer with the 0 // color index, up to the boundaries set by the raster attributes (or if // none were given, up to the page boundaries). The actual image output // isn't limited by the background dimensions though. - static constexpr auto backgroundPixel = IndexedPixel{}; - const auto backgroundOffset = _imageCursor.y * _imageMaxWidth; - auto dst = std::next(_imageBuffer.begin(), backgroundOffset); - for (auto i = 0; i < backgroundHeight; i++) - { - std::fill_n(dst, backgroundWidth, backgroundPixel); - std::advance(dst, _imageMaxWidth); - } + const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight); + _resizeImageBuffer(backgroundHeight); + _fillImageBackground(backgroundHeight); + // When the image extends beyond the page boundaries, and the screen is + // scrolled, we also need to fill the newly exposed lines, so we keep a + // record of the area filled so far. Initially this is considered to be + // the available height, even if it wasn't all filled to start with. + _filledBackgroundHeight = _imageCursor.y + _availablePixelHeight; + _fillImageBackgroundWhenScrolled(); + } +} - _imageWidth = std::max(_imageWidth, backgroundWidth); +void SixelParser::_fillImageBackground(const int backgroundHeight) +{ + static constexpr auto backgroundPixel = IndexedPixel{}; + const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth); + const auto backgroundOffset = _imageCursor.y * _imageMaxWidth; + auto dst = std::next(_imageBuffer.begin(), backgroundOffset); + for (auto i = 0; i < backgroundHeight; i++) + { + std::fill_n(dst, backgroundWidth, backgroundPixel); + std::advance(dst, _imageMaxWidth); + } + _imageWidth = std::max(_imageWidth, backgroundWidth); +} + +void SixelParser::_fillImageBackgroundWhenScrolled() +{ + // If _filledBackgroundHeight is set, that means a background fill has been + // requested, and we need to extend that area whenever the image is about to + // overrun it. The newly filled area is a multiple of the cell height (this + // is to match the behavior of the original hardware terminals). + const auto imageHeight = _imageCursor.y + _sixelHeight; + if (_filledBackgroundHeight && imageHeight > _filledBackgroundHeight) [[unlikely]] + { + _filledBackgroundHeight = (imageHeight + _cellSize.height - 1) / _cellSize.height * _cellSize.height; + const auto additionalFillHeight = *_filledBackgroundHeight - _imageCursor.y; + _resizeImageBuffer(additionalFillHeight); + _fillImageBackground(additionalFillHeight); + } +} + +void SixelParser::_decreaseFilledBackgroundHeight(const int decreasedHeight) noexcept +{ + // Sometimes the top of the image buffer may be clipped (e.g. when the image + // scrolls off the top of a margin area). When that occurs, our record of + // the filled height will need to be decreased to account for the new start. + if (_filledBackgroundHeight) [[unlikely]] + { + _filledBackgroundHeight = *_filledBackgroundHeight - decreasedHeight; } } @@ -717,11 +769,13 @@ void SixelParser::_eraseImageBufferRows(const int rowCount, const til::CoordType const auto bufferOffsetEnd = bufferOffset + pixelCount * _imageMaxWidth; if (static_cast(bufferOffsetEnd) >= _imageBuffer.size()) [[unlikely]] { + _decreaseFilledBackgroundHeight(_imageCursor.y); _imageBuffer.clear(); _imageCursor.y = 0; } else { + _decreaseFilledBackgroundHeight(pixelCount); _imageBuffer.erase(_imageBuffer.begin() + bufferOffset, _imageBuffer.begin() + bufferOffsetEnd); _imageCursor.y -= pixelCount; } diff --git a/src/terminal/adapter/SixelParser.hpp b/src/terminal/adapter/SixelParser.hpp index ea97783fec..62a7f7eae7 100644 --- a/src/terminal/adapter/SixelParser.hpp +++ b/src/terminal/adapter/SixelParser.hpp @@ -89,6 +89,7 @@ namespace Microsoft::Console::VirtualTerminal til::CoordType _pendingTextScrollCount = 0; til::size _backgroundSize; bool _backgroundFillRequired = false; + std::optional _filledBackgroundHeight; void _initColorMap(const VTParameter backgroundColor); void _defineColor(const VTParameters& colorParameters); @@ -109,6 +110,9 @@ namespace Microsoft::Console::VirtualTerminal void _initImageBuffer(); void _resizeImageBuffer(const til::CoordType requiredHeight); void _fillImageBackground(); + void _fillImageBackground(const int backgroundHeight); + void _fillImageBackgroundWhenScrolled(); + void _decreaseFilledBackgroundHeight(const int decreasedHeight) noexcept; void _writeToImageBuffer(const int sixelValue, const int repeatCount); void _eraseImageBufferRows(const int rowCount, const til::CoordType startRow = 0) noexcept; void _maybeFlushImageBuffer(const bool endOfSequence = false); From ad149228744ca1b2ad1ee50bfbe6eb474b7141a4 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Thu, 12 Jun 2025 02:56:57 +0500 Subject: [PATCH 074/177] build: update WinGet publish script to use env var instead of --token (#19021) With the latest winget-create release, the preferred method for providing the GitHub token in CI/CD environment is via the environment variable `WINGET_CREATE_GITHUB_TOKEN`. Removed use of `--token` and switched to environment variable. See https://aka.ms/winget-create-token for details. --- .github/workflows/winget.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 63464ea4fe..5dda1dc89c 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -6,6 +6,9 @@ on: env: REGEX: 'Microsoft\.WindowsTerminal(?:Preview)?_([\d.]+)_8wekyb3d8bbwe\.msixbundle$' + # winget-create will read the following environment variable to access the GitHub token needed for submitting a PR + # See https://aka.ms/winget-create-token + WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }} jobs: publish: @@ -21,4 +24,4 @@ jobs: $wingetPackage = "Microsoft.WindowsTerminal${{ github.event.release.prerelease && '.Preview' || '' }}" & curl.exe -JLO https://aka.ms/wingetcreate/latest - & .\wingetcreate.exe update $wingetPackage -s -v $version -u $wingetRelevantAsset.browser_download_url -t "${{ secrets.WINGET_TOKEN }}" + & .\wingetcreate.exe update $wingetPackage -s -v $version -u $wingetRelevantAsset.browser_download_url From 7a9fb769558693d982660a88640bea7ba1bf7eb2 Mon Sep 17 00:00:00 2001 From: James Pack Date: Thu, 12 Jun 2025 20:34:51 -0400 Subject: [PATCH 075/177] Ensure items is not null before checking its size so we dont crash (#19026) Prevents a crash when no storage items are returned from a dropped path. Terminal no longer crashes when a relative path is dragged and dropped into the tool. Closes #19015 --- src/cascadia/TerminalControl/TermControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 7118c7a868..b38c651e3d 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -3180,7 +3180,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } CATCH_LOG(); - if (items.Size() > 0) + if (items && items.Size() > 0) { std::vector fullPaths; From 685499df3a5bc1e7f1d9e373062c66cb2d694259 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 13 Jun 2025 15:38:40 -0700 Subject: [PATCH 076/177] Automatically enable AdjustIndistinguishableColors if High Contrast mode enabled (#17346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If High Contrast mode is enabled in the OS settings, we now automatically enable `adjustIndistinguishableColors`. To accomplish this, a new `Automatic` value is added to `adjustIndistinguishableColors`. When it's chosen, color nudging doesn't occur in regular contrast sessions, but we interpret the value as `Indexed` respectively. The new default value is `AutomaticIndexed`. Meaning that regular contrast sessions will see no difference in behavior. However, if they switch to high contrast mode, Windows Terminal will interpret the value as `Indexed` at runtime. This was chosen because `Always` is more performance intensive.    ## References and Relevant Issues #12999 ## Validation Steps Performed ✅ Toggling High Contrast mode immediately triggers an updated terminal instance with `adjustIndistinguishableColors` --- src/cascadia/TerminalControl/ControlCore.cpp | 5 +++++ src/cascadia/TerminalControl/ControlCore.h | 1 + src/cascadia/TerminalControl/ControlCore.idl | 1 + src/cascadia/TerminalControl/TermControl.cpp | 16 ++++++++++++++++ src/cascadia/TerminalControl/TermControl.h | 4 +--- src/cascadia/TerminalCore/ICoreAppearance.idl | 3 ++- src/cascadia/TerminalCore/Terminal.cpp | 15 ++++++++++++++- src/cascadia/TerminalCore/Terminal.hpp | 2 ++ .../Resources/en-US/Resources.resw | 4 ++++ .../TerminalSettingsModel/MTSMSettings.h | 2 +- .../TerminalSettingsSerializationHelpers.h | 3 ++- src/cascadia/inc/ControlProperties.h | 2 +- 12 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index a96adb7a1d..61d699a6cc 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -955,6 +955,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + void ControlCore::SetHighContrastMode(const bool enabled) + { + _terminal->SetHighContrastMode(enabled); + } + Control::IControlSettings ControlCore::Settings() { return *_settings; diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 8e99e26155..2453670d3f 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -94,6 +94,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void UpdateSettings(const Control::IControlSettings& settings, const IControlAppearance& newAppearance); void ApplyAppearance(const bool focused); + void SetHighContrastMode(const bool enabled); Control::IControlSettings Settings(); Control::IControlAppearance FocusedAppearance() const; Control::IControlAppearance UnfocusedAppearance() const; diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 528bc11458..bc704a1342 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -102,6 +102,7 @@ namespace Microsoft.Terminal.Control IControlAppearance FocusedAppearance { get; }; IControlAppearance UnfocusedAppearance { get; }; Boolean HasUnfocusedAppearance(); + void SetHighContrastMode(Boolean enabled); UInt64 SwapChainHandle { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index b38c651e3d..67809d4cea 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -257,6 +257,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation return get_self(_termControl->_core); } + static Windows::UI::ViewManagement::AccessibilitySettings& _GetAccessibilitySettings() + { + static Windows::UI::ViewManagement::AccessibilitySettings accessibilitySettings; + return accessibilitySettings; + } + TermControl::TermControl(IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection) : @@ -276,6 +282,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core = _interactivity.Core(); + // If high contrast mode was changed, update the appearance appropriately. + _core.SetHighContrastMode(_GetAccessibilitySettings().HighContrast()); + _revokers.HighContrastChanged = _GetAccessibilitySettings().HighContrastChanged(winrt::auto_revoke, [weakThis{ get_weak() }](const Windows::UI::ViewManagement::AccessibilitySettings& a11ySettings, auto&&) { + if (auto termControl = weakThis.get()) + { + termControl->_core.SetHighContrastMode(a11ySettings.HighContrast()); + termControl->_core.ApplyAppearance(termControl->_focused); + } + }); + // This event is specifically triggered by the renderer thread, a BG thread. Use a weak ref here. _revokers.RendererEnteredErrorState = _core.RendererEnteredErrorState(winrt::auto_revoke, { get_weak(), &TermControl::_RendererEnteredErrorState }); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 2e8fadd850..7fe3db1be0 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -362,7 +362,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation }; bool _InitializeTerminal(const InitializeReason reason); safe_void_coroutine _restoreInBackground(); - void _SetFontSize(int fontSize); void _TappedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& e); void _KeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e); void _KeyUpHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e); @@ -394,8 +393,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _SwapChainSizeChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::SizeChangedEventArgs& e); void _SwapChainScaleChanged(const Windows::UI::Xaml::Controls::SwapChainPanel& sender, const Windows::Foundation::IInspectable& args); - void _TerminalTabColorChanged(const std::optional color); - void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args); bool _CapturePointer(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e); @@ -480,6 +477,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // These are set up in _InitializeTerminal Control::ControlCore::RendererWarning_revoker RendererWarning; Control::ControlCore::SwapChainChanged_revoker SwapChainChanged; + Windows::UI::ViewManagement::AccessibilitySettings::HighContrastChanged_revoker HighContrastChanged; Control::ControlInteractivity::OpenHyperlink_revoker interactivityOpenHyperlink; Control::ControlInteractivity::ScrollPositionChanged_revoker interactivityScrollPositionChanged; diff --git a/src/cascadia/TerminalCore/ICoreAppearance.idl b/src/cascadia/TerminalCore/ICoreAppearance.idl index fa0364a322..d86fdd4262 100644 --- a/src/cascadia/TerminalCore/ICoreAppearance.idl +++ b/src/cascadia/TerminalCore/ICoreAppearance.idl @@ -23,7 +23,8 @@ namespace Microsoft.Terminal.Core { Never, Indexed, - Always + Always, + Automatic }; // TerminalCore declares its own Color struct to avoid depending diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 68d7178187..039b251144 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -143,7 +143,15 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) renderSettings.SetRenderMode(RenderSettings::Mode::IntenseIsBold, appearance.IntenseIsBold()); renderSettings.SetRenderMode(RenderSettings::Mode::IntenseIsBright, appearance.IntenseIsBright()); - switch (appearance.AdjustIndistinguishableColors()) + // If AIC is set to Automatic, + // update the value based on if high contrast mode is enabled. + AdjustTextMode deducedAIC = appearance.AdjustIndistinguishableColors(); + if (deducedAIC == AdjustTextMode::Automatic) + { + deducedAIC = _highContrastMode ? AdjustTextMode::Indexed : AdjustTextMode::Never; + } + + switch (deducedAIC) { case AdjustTextMode::Always: renderSettings.SetRenderMode(RenderSettings::Mode::IndexedDistinguishableColors, false); @@ -213,6 +221,11 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) _NotifyScrollEvent(); } +void Terminal::SetHighContrastMode(bool hc) noexcept +{ + _highContrastMode = hc; +} + void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) { auto& engine = reinterpret_cast(_stateMachine->Engine()); diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 1459c39fc3..afbf873da9 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -92,6 +92,7 @@ public: void UpdateSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings); void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance); + void SetHighContrastMode(bool hc) noexcept; void SetFontInfo(const FontInfo& fontInfo); void SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle); void SetVtChecksumReportSupport(const bool enabled); @@ -382,6 +383,7 @@ private: std::wstring _answerbackMessage; std::wstring _workingDirectory; + bool _highContrastMode = false; // This default fake font value is only used to check if the font is a raster font. // Otherwise, the font is changed to a real value with the renderer via TriggerFontChange. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 7949d017ca..7ed6a84252 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -946,6 +946,10 @@ Always An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automatic + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Bar ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index eb195f2695..01bd5ce5c8 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -136,7 +136,7 @@ Author(s): X(ConvergedAlignment, BackgroundImageAlignment, "backgroundImageAlignment", ConvergedAlignment::Horizontal_Center | ConvergedAlignment::Vertical_Center) \ X(hstring, BackgroundImagePath, "backgroundImage") \ X(Model::IntenseStyle, IntenseTextStyle, "intenseTextStyle", Model::IntenseStyle::Bright) \ - X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Never) \ + X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Automatic) \ X(bool, UseAcrylic, "useAcrylic", false) // Intentionally omitted Appearance settings: diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 448eae5e43..32a24b11f7 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -35,10 +35,11 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::CursorStyle) // - Helper for converting a user-specified adjustTextMode value to its corresponding enum JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::AdjustTextMode) { - JSON_MAPPINGS(3) = { + JSON_MAPPINGS(4) = { pair_type{ "never", ValueType::Never }, pair_type{ "indexed", ValueType::Indexed }, pair_type{ "always", ValueType::Always }, + pair_type{ "automatic", ValueType::Automatic }, }; // Override mapping parser to add boolean parsing diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 42c3b1b9d3..107773da06 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -14,7 +14,7 @@ X(til::color, SelectionBackground, DEFAULT_FOREGROUND) \ X(bool, IntenseIsBold) \ X(bool, IntenseIsBright, true) \ - X(winrt::Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors, winrt::Microsoft::Terminal::Core::AdjustTextMode::Never) + X(winrt::Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors, winrt::Microsoft::Terminal::Core::AdjustTextMode::Automatic) // --------------------------- Control Appearance --------------------------- // All of these settings are defined in IControlAppearance. From bb62ce93459feb9bbe0a90ddb6c444bc23817d20 Mon Sep 17 00:00:00 2001 From: Katherine Reynolds <7054971+reynoldskr@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:41:10 -0700 Subject: [PATCH 077/177] Improve VS profile generation (#19025) * Make PowerShell profile generation try to find `pwsh.exe` before falling back to legacy powershell * Make profiles generated on an `arm64` host work properly ## Validation Steps Performed * Local build ran * Verified the new `arm64` profile works * Verified `pwsh.exe` is used if present * Verified `powershell.exe` is used if `pwsh` is not present in path * Verified we don't attempt to create `arm64` host cmd/pwsh profiles if VS is not >= 17.4 --- .../VsDevCmdGenerator.cpp | 10 +++++- .../VsDevShellGenerator.cpp | 36 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp index 19d6f50cc8..c4baf36a39 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp @@ -45,7 +45,15 @@ std::wstring VsDevCmdGenerator::GetProfileCommandLine(const VsSetupConfiguration // The "-startdir" parameter will prevent "vsdevcmd" from automatically // setting the shell path so the path in the profile will be used instead. #if defined(_M_ARM64) - commandLine.append(LR"(" -startdir=none -arch=arm64 -host_arch=x64)"); + commandLine.append(LR"(" -startdir=none -arch=arm64 -host_arch=)"); + if (instance.VersionInRange(L"[17.4,")) + { + commandLine.append(LR"(arm64)"); + } + else + { + commandLine.append(LR"(x64)"); + } #elif defined(_M_AMD64) commandLine.append(LR"(" -startdir=none -arch=x64 -host_arch=x64)"); #else diff --git a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp index 22c17cd98a..ce14532107 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp @@ -39,17 +39,43 @@ std::wstring VsDevShellGenerator::GetProfileName(const VsSetupConfiguration::VsS std::wstring VsDevShellGenerator::GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const { - // The triple-quotes are a PowerShell path escape sequence that can safely be stored in a JSON object. - // The "SkipAutomaticLocation" parameter will prevent "Enter-VsDevShell" from automatically setting the shell path - // so the path in the profile will be used instead. + // Build this in stages, so reserve space now std::wstring commandLine; commandLine.reserve(256); - commandLine.append(LR"(powershell.exe -NoExit -Command "&{Import-Module """)"); + + // Try to detect if `pwsh.exe` is available in the PATH, if so we want to use that + // Allow some extra space in case user put it somewhere odd + // We do need to allocate space for the full path even if we don't want to paste the whole thing in + wchar_t pwshPath[MAX_PATH] = { 0 }; + const auto pwshExeName = L"pwsh.exe"; + if (SearchPathW(nullptr, pwshExeName, nullptr, MAX_PATH, pwshPath, nullptr)) + { + commandLine.append(pwshExeName); + } + else + { + commandLine.append(L"powershell.exe"); + } + + // The triple-quotes are a PowerShell path escape sequence that can safely be stored in a JSON object. + // The "SkipAutomaticLocation" parameter will prevent "Enter-VsDevShell" from automatically setting the shell path + // so the path in the profile will be used instead + commandLine.append(LR"( -NoExit -Command "&{Import-Module """)"); commandLine.append(GetDevShellModulePath(instance)); commandLine.append(LR"("""; Enter-VsDevShell )"); commandLine.append(instance.GetInstanceId()); #if defined(_M_ARM64) - commandLine.append(LR"( -SkipAutomaticLocation -DevCmdArguments """-arch=arm64 -host_arch=x64"""}")"); + // This part stays constant no matter what + commandLine.append(LR"( -SkipAutomaticLocation -DevCmdArguments """-arch=arm64 -host_arch=)"); + if (instance.VersionInRange(L"[17.4,")) + { + commandLine.append(LR"("arm64 """}")"); + } + // If an old version of VS is installed without ARM64 host support + else + { + commandLine.append(LR"("x64 """}")"); + } #elif defined(_M_AMD64) commandLine.append(LR"( -SkipAutomaticLocation -DevCmdArguments """-arch=x64 -host_arch=x64"""}")"); #else From bd7e3179ff1c41e9147d99253a9ab150cf9e942a Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 13 Jun 2025 15:43:13 -0700 Subject: [PATCH 078/177] Manually focus panes after swapping them (#19024) --- src/cascadia/TerminalApp/Pane.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index dc235b0602..3255c5fba3 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -697,12 +697,24 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) // Refocus the last pane if there was a pane focused if (const auto focus = first->GetActivePane()) { - focus->_Focus(); + // GH#18184: manually focus the pane and content. + // _Focus() results in no-op because the pane was _lastActive + focus->GotFocus.raise(focus, FocusState::Programmatic); + if (const auto& lastContent{ focus->GetLastFocusedContent() }) + { + lastContent.Focus(FocusState::Programmatic); + } } if (const auto focus = second->GetActivePane()) { - focus->_Focus(); + // GH#18184: manually focus the pane and content. + // _Focus() results in no-op because the pane was _lastActive + focus->GotFocus.raise(focus, FocusState::Programmatic); + if (const auto& lastContent{ focus->GetLastFocusedContent() }) + { + lastContent.Focus(FocusState::Programmatic); + } } return true; From 098da6ce1c8fa439cc814931e64c2b443e16783b Mon Sep 17 00:00:00 2001 From: James Pack Date: Mon, 16 Jun 2025 09:37:45 -0400 Subject: [PATCH 079/177] Replace newline literal in AtlasEngine Readme with
tag (#19041) --- src/renderer/atlas/README.md | 42 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/renderer/atlas/README.md b/src/renderer/atlas/README.md index c72e77f51e..a1619e9b4b 100644 --- a/src/renderer/atlas/README.md +++ b/src/renderer/atlas/README.md @@ -4,18 +4,18 @@ ```mermaid graph TD - RenderThread["RenderThread (base/thread.cpp)\ncalls Renderer::PaintFrame() x times per sec"] - Renderer["Renderer (base/renderer.cpp)\nbreaks the text buffer down into GDI-oriented graphics\nprimitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)"] - RenderEngineBase[/"RenderEngineBase\n(base/RenderEngineBase.cpp)\nabstracts 24 LOC 👻"\] + RenderThread["RenderThread (base/thread.cpp)
calls Renderer::PaintFrame() x times per sec"] + Renderer["Renderer (base/renderer.cpp)
breaks the text buffer down into GDI-oriented graphics
primitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)
"] + RenderEngineBase[/"RenderEngineBase
(base/RenderEngineBase.cpp)
abstracts 24 LOC 👻"\] GdiEngine["GdiEngine (gdi/...)"] subgraph AtlasEngine["AtlasEngine (atlas/...)"] - AtlasEngine.cpp["AtlasEngine.cpp\nImplements IRenderEngine text rendering API\nbreaks GDI graphics primitives down into DWRITE_GLYPH_RUNs"] - AtlasEngine.api.cpp["AtlasEngine.api.cpp\nImplements the parts run inside the console\nlock (many IRenderEngine setters)"] - AtlasEngine.r.cpp["AtlasEngine.r.cpp\nImplements the parts run\noutside of the console lock"] - Backend.cpp["Backend.cpp\nImplements common functionality/helpers"] - BackendD2D.cpp["BackendD2D.cpp\nPure Direct2D text renderer (for low latency\nremote desktop and older/no GPUs)"] - BackendD3D.cpp["BackendD3D.cpp\nCustom, performant text renderer\nwith our own glyph cache"] + AtlasEngine.cpp["AtlasEngine.cpp
Implements IRenderEngine text rendering API
breaks GDI graphics primitives down into DWRITE_GLYPH_RUNs
"] + AtlasEngine.api.cpp["AtlasEngine.api.cpp
Implements the parts run inside the console
lock (many IRenderEngine setters)"] + AtlasEngine.r.cpp["AtlasEngine.r.cpp
Implements the parts run
outside of the console lock"] + Backend.cpp["Backend.cpp
Implements common functionality/helpers"] + BackendD2D.cpp["BackendD2D.cpp
Pure Direct2D text renderer (for low latency
remote desktop and older/no GPUs)
"] + BackendD3D.cpp["BackendD3D.cpp
Custom, performant text renderer
with our own glyph cache
"] end RenderThread --> Renderer @@ -63,8 +63,8 @@ graph TD ```mermaid graph TD - Render --> _drawCursorPart1["_drawCursorPart1\nruns before _drawText\ndraws cursors that are behind the text"] - Render --> _drawCursorPart2["_drawCursorPart2\nruns after _drawText\ndraws inverted cursors"] + Render --> _drawCursorPart1["_drawCursorPart1
runs before _drawText
draws cursors that are behind the text"] + Render --> _drawCursorPart2["_drawCursorPart2
runs after _drawText
draws inverted cursors"] _drawCursorPart1 -.->|_cursorRects| _drawCursorPart2 ``` @@ -81,26 +81,26 @@ graph TD foreachFont --> foreachGlyph(("for each glyph")) foreachGlyph --> foreachGlyph - foreachGlyph --> _glyphAtlasMap[("font/glyph-pair lookup in\nglyph cache hashmap")] + foreachGlyph --> _glyphAtlasMap[("font/glyph-pair lookup in
glyph cache hashmap")] _glyphAtlasMap --> drawGlyph - drawGlyph --> _appendQuad["_appendQuad\nstages the glyph for later drawing"] + drawGlyph --> _appendQuad["_appendQuad
stages the glyph for later drawing"] _glyphAtlasMap --> _appendQuad subgraph drawGlyph["if glyph is missing"] - _drawGlyph["_drawGlyph\n(defers to _drawSoftFontGlyph for soft fonts)"] + _drawGlyph["_drawGlyph
(defers to _drawSoftFontGlyph for soft fonts)"] _drawGlyph -.->|if glpyh cache is full| _drawGlyphPrepareRetry - _drawGlyphPrepareRetry --> _flushQuads["_flushQuads\ndraws the current state\ninto the render target"] - _flushQuads --> _recreateInstanceBuffers["_recreateInstanceBuffers\nallocates a GPU buffer\nfor our glyph instances"] - _drawGlyphPrepareRetry --> _resetGlyphAtlas["_resetGlyphAtlas\nclears the glyph texture"] - _resetGlyphAtlas --> _resizeGlyphAtlas["_resizeGlyphAtlas\nresizes the glyph texture if it's still small"] + _drawGlyphPrepareRetry --> _flushQuads["_flushQuads
draws the current state
into the render target
"] + _flushQuads --> _recreateInstanceBuffers["_recreateInstanceBuffers
allocates a GPU buffer
for our glyph instances
"] + _drawGlyphPrepareRetry --> _resetGlyphAtlas["_resetGlyphAtlas
clears the glyph texture"] + _resetGlyphAtlas --> _resizeGlyphAtlas["_resizeGlyphAtlas
resizes the glyph texture if it's still small"] - _drawGlyph -.->|if it's a DECDHL glyph| _splitDoubleHeightGlyph["_splitDoubleHeightGlyph\nDECDHL glyphs are split up into their\ntop/bottom halves to emulate clip rects"] + _drawGlyph -.->|if it's a DECDHL glyph| _splitDoubleHeightGlyph["_splitDoubleHeightGlyph
DECDHL glyphs are split up into their
top/bottom halves to emulate clip rects
"] end - foreachGlyph -.-> _drawTextOverlapSplit["_drawTextOverlapSplit\nsplits overly wide glyphs up into smaller chunks to support\nforeground color changes within the ligature"] + foreachGlyph -.-> _drawTextOverlapSplit["_drawTextOverlapSplit
splits overly wide glyphs up into smaller chunks to support
foreground color changes within the ligature
"] - foreachRow -.->|if gridlines exist| _drawGridlineRow["_drawGridlineRow\ndraws underlines, etc."] + foreachRow -.->|if gridlines exist| _drawGridlineRow["_drawGridlineRow
draws underlines, etc."] ``` ### `_drawSelection` From f28bb42979756318073a9d42b0209e6f9ebd77c7 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 16 Jun 2025 18:01:35 -0500 Subject: [PATCH 080/177] Gently rework icon and background image validation (#19044) Right now, image validation accepts web-sourced icons (boo) and rejects images whose paths begin with `\\?\`. In addition, it will warn the user for things out of their control like images set by fragments. This pull request adds a filesystem path validator (which accepts images with fully-qualified paths and UNC paths), makes the URI validator reject any web-origin URIs (only `file` and `ms-*` are allowable), and suppresses warnings for any images that were not _directly_ set by the user. Since we want to avoid using fragment images that fail validation, we no longer `Clear` each image property but rather set it to the blank or fallback value. This does **not** actually add support for images at absolute paths beginning with `\\?\`. Such images are still rejected by `Image` and the other XAML fixtures we use for images. It's better than a warning, though. Closes #18703 Closes #14143 Refs #18710 Refs #5204 Related to #18922 (http-origin icons will be blank everywhere and not just the jump list ;)) --- .../CascadiaSettings.cpp | 101 ++++++++++++------ 1 file changed, 70 insertions(+), 31 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index a252695108..9dc2c156f2 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -488,6 +488,39 @@ void CascadiaSettings::_validateAllSchemesExist() } } +static bool _validateSingleMediaResource(std::wstring_view resource) +{ + // URI + try + { + winrt::Windows::Foundation::Uri resourceUri{ resource }; + if (!resourceUri) + { + return false; + } + + const auto scheme{ resourceUri.SchemeName() }; + // Only file: URIs and ms-* URIs are permissible. http, https, ftp, gopher, etc. are not. + return til::equals_insensitive_ascii(scheme, L"file") || til::starts_with_insensitive_ascii(scheme, L"ms-"); + } + catch (...) + { + // fall through + } + + // Not a URI? Try a path. + try + { + std::filesystem::path resourcePath{ resource }; + return std::filesystem::exists(resourcePath); + } + catch (...) + { + // fall through + } + return false; +} + // Method Description: // - Ensures that all specified images resources (icons and background images) are valid URIs. // This does not verify that the icon or background image files are encoded as an image. @@ -501,24 +534,26 @@ void CascadiaSettings::_validateAllSchemesExist() // we find any invalid icon images. void CascadiaSettings::_validateMediaResources() { - auto invalidBackground{ false }; - auto invalidIcon{ false }; + auto warnInvalidBackground{ false }; + auto warnInvalidIcon{ false }; for (auto profile : _allProfiles) { if (const auto path = profile.DefaultAppearance().ExpandedBackgroundImagePath(); !path.empty()) { - // Attempt to convert the path to a URI, the ctor will throw if it's invalid/unparseable. - // This covers file paths on the machine, app data, URLs, and other resource paths. - try + if (!_validateSingleMediaResource(path)) { - winrt::Windows::Foundation::Uri imagePath{ path }; - } - catch (...) - { - // reset background image path - profile.DefaultAppearance().ClearBackgroundImagePath(); - invalidBackground = true; + if (profile.DefaultAppearance().HasBackgroundImagePath()) + { + // Only warn and delete if the user set this at the top level (do not warn for fragments, just clear it) + warnInvalidBackground = true; + profile.DefaultAppearance().ClearBackgroundImagePath(); + } + else + { + // reset background image path (set it to blank as an override for any fragment value) + profile.DefaultAppearance().BackgroundImagePath({}); + } } } @@ -526,17 +561,18 @@ void CascadiaSettings::_validateMediaResources() { if (const auto path = profile.UnfocusedAppearance().ExpandedBackgroundImagePath(); !path.empty()) { - // Attempt to convert the path to a URI, the ctor will throw if it's invalid/unparseable. - // This covers file paths on the machine, app data, URLs, and other resource paths. - try + if (!_validateSingleMediaResource(path)) { - winrt::Windows::Foundation::Uri imagePath{ path }; - } - catch (...) - { - // reset background image path - profile.UnfocusedAppearance().ClearBackgroundImagePath(); - invalidBackground = true; + if (profile.UnfocusedAppearance().HasBackgroundImagePath()) + { + warnInvalidBackground = true; + profile.UnfocusedAppearance().ClearBackgroundImagePath(); + } + else + { + // reset background image path (set it to blank as an override for any fragment value) + profile.UnfocusedAppearance().BackgroundImagePath({}); + } } } } @@ -552,24 +588,27 @@ void CascadiaSettings::_validateMediaResources() if (const auto icon = profile.Icon(); icon.size() > 2 && icon != HideIconValue) { const auto iconPath{ wil::ExpandEnvironmentStringsW(icon.c_str()) }; - try + if (!_validateSingleMediaResource(iconPath)) { - winrt::Windows::Foundation::Uri imagePath{ iconPath }; - } - catch (...) - { - profile.ClearIcon(); - invalidIcon = true; + if (profile.HasIcon()) + { + warnInvalidIcon = true; + profile.ClearIcon(); + } + else + { + profile.Icon({}); + } } } } - if (invalidBackground) + if (warnInvalidBackground) { _warnings.Append(SettingsLoadWarnings::InvalidBackgroundImage); } - if (invalidIcon) + if (warnInvalidIcon) { _warnings.Append(SettingsLoadWarnings::InvalidIcon); } From b47fdfc7e63fd7c2f8c060a1abc930b3fc40fc19 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 17 Jun 2025 01:52:26 +0200 Subject: [PATCH 081/177] Fix support for the Tencent QQPinyin IME (#19046) --- .github/actions/spelling/allow/allow.txt | 5 ++-- src/tsf/Implementation.cpp | 31 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index f9a16af64d..b347ec85b9 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -24,8 +24,8 @@ dze dzhe Emacspeak Fitt -FTCS flac +FTCS gantt gfm ghe @@ -61,8 +61,8 @@ Powerline ptys pwn pwshw -QOL qof +QOL qps quickfix rclt @@ -82,6 +82,7 @@ stakeholders subpage sustainability sxn +Tencent TLDR tonos toolset diff --git a/src/tsf/Implementation.cpp b/src/tsf/Implementation.cpp index f2cd958933..6f2e249755 100644 --- a/src/tsf/Implementation.cpp +++ b/src/tsf/Implementation.cpp @@ -711,13 +711,28 @@ TextAttribute Implementation::_textAttributeFromAtom(TfGuidAtom atom) const TF_DISPLAYATTRIBUTE da; THROW_IF_FAILED(dai->GetAttributeInfo(&da)); - if (da.crText.type != TF_CT_NONE) + // The Tencent QQPinyin IME creates TF_CT_COLORREF attributes with a color of 0x000000 (black). + // We respect their wish, which results in the preview text being invisible. + // (Note that sending this COLORREF is incorrect, and not a bug in our handling.) + // + // After some discussion, we realized that an IME which sets only one color but not + // the others is likely not properly tested anyway, so we reject those cases. + // After all, what behavior do we expect, if the IME sends e.g. foreground=blue, + // without knowing whether our terminal theme already uses a blue background? + if (da.crText.type == da.crBk.type && da.crText.type == da.crLine.type) { - attr.SetForeground(_colorFromDisplayAttribute(da.crText)); - } - if (da.crBk.type != TF_CT_NONE) - { - attr.SetBackground(_colorFromDisplayAttribute(da.crBk)); + if (da.crText.type != TF_CT_NONE) + { + attr.SetForeground(_colorFromDisplayAttribute(da.crText)); + } + if (da.crBk.type != TF_CT_NONE) + { + attr.SetBackground(_colorFromDisplayAttribute(da.crBk)); + } + if (da.crLine.type != TF_CT_NONE) + { + attr.SetUnderlineColor(_colorFromDisplayAttribute(da.crLine)); + } } if (da.lsStyle >= TF_LS_NONE && da.lsStyle <= TF_LS_SQUIGGLE) { @@ -737,10 +752,6 @@ TextAttribute Implementation::_textAttributeFromAtom(TfGuidAtom atom) const { attr.SetUnderlineStyle(UnderlineStyle::DoublyUnderlined); } - if (da.crLine.type != TF_CT_NONE) - { - attr.SetUnderlineColor(_colorFromDisplayAttribute(da.crLine)); - } return attr; } From 8a018334890b05080888f7bdb91a561661faf5d3 Mon Sep 17 00:00:00 2001 From: Windows Console Service Bot <14666831+consvc@users.noreply.github.com> Date: Mon, 16 Jun 2025 22:42:04 -0500 Subject: [PATCH 082/177] Localization Updates - main - 06/14/2025 03:04:55 (#19035) --- .../TerminalSettingsEditor/Resources/de-DE/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/es-ES/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/fr-FR/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/it-IT/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ja-JP/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ko-KR/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/pt-BR/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/qps-ploc/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/qps-ploca/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/qps-plocm/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ru-RU/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/zh-CN/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/zh-TW/Resources.resw | 4 ++++ 13 files changed, 52 insertions(+) diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 968d594762..cf7198582d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -946,6 +946,10 @@ Immer An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility.
+ + Automatisch + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Balken ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index f3204f013f..3e5d705e1a 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -946,6 +946,10 @@ Siempre An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automático + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Barra ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index ac90f141ab..884781e9c9 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -946,6 +946,10 @@ Toujours An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automatique + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Barre ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index b6aebaa918..b4ce453594 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -946,6 +946,10 @@ Sempre An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automatico + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Bar ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index 10514c94e6..80dd6d9cfe 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -946,6 +946,10 @@ 常時 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 自動 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + バー ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index 711a533182..6c668fd91a 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -946,6 +946,10 @@ 항상 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 자동 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + 막대( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index faf76cce95..3e0b684fbf 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -946,6 +946,10 @@ Sempre An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automático + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Barra ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index 800e7b85cd..c9e208ee3f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -946,6 +946,10 @@ Åļωάỳš ! An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Дüтôмαťί¢ !!! + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Ъâг ( ┃ ) !!! {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index 800e7b85cd..c9e208ee3f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -946,6 +946,10 @@ Åļωάỳš ! An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Дüтôмαťί¢ !!! + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Ъâг ( ┃ ) !!! {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index 800e7b85cd..c9e208ee3f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -946,6 +946,10 @@ Åļωάỳš ! An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Дüтôмαťί¢ !!! + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Ъâг ( ┃ ) !!! {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index 28113389e5..c70359f395 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -946,6 +946,10 @@ Всегда An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Автоматически + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Вертикальная линия (┃) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index 7f46d38030..a93980ce3b 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -946,6 +946,10 @@ 始终 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 自动 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + 条形 ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index ed1ee7b18e..2ada7577e3 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -946,6 +946,10 @@ 一律 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 自動 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + 橫條圖 ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. From 4cf492e36bbb4f26769b71c586d402957310d764 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 18 Jun 2025 18:04:45 -0500 Subject: [PATCH 083/177] build: adjust for changes in the Az.Accounts module (#19020) --- .../job-publish-symbols-using-symbolrequestprod-api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml b/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml index a5ed41f737..1b34002c50 100644 --- a/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml +++ b/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml @@ -61,7 +61,7 @@ jobs: pwsh: true ScriptType: InlineScript Inline: |- - $AzToken = (Get-AzAccessToken -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token + $AzToken = (Get-AzAccessToken -AsSecureString -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token | ConvertFrom-SecureString -AsPlainText Write-Host "##vso[task.setvariable variable=SymbolAccessToken;issecret=true]$AzToken" From 00ee88400aab0cd94409204ce0c6d20854ba7eff Mon Sep 17 00:00:00 2001 From: James Holderness Date: Thu, 19 Jun 2025 02:13:00 +0100 Subject: [PATCH 084/177] Indicate support for OSC 52 in the DA1 report (#19034) ## Summary of the Pull Request Some applications that make use of the `OSC 52` clipboard sequence will only do so if they can be certain that the terminal actually has that functionality. Indicating our support for `OSC 52` in the `DA1` report will give them an easy way to detect that. ## References and Relevant Issues `OSC 52` support was added to Windows Terminal in issue #5823, and to ConHost in issue #18949. ## Detailed Description of the Pull Request / Additional comments Support for writing to the clipboard is indicated in the primary device attributes report by the extension parameter `52`. This is obviously not a standard DEC extension, but it's one that's been agreed upon by a number of modern terminals. The extension is only reported when writing to the clipboard is actually permitted (Windows Terminal has an option to disable that). ## Validation Steps Performed I've updated the Device Attributes unit test to check that we're reporting extension `52` when clipboard access is enabled, and not reporting it when disabled. ## PR Checklist - [x] Closes #19017 - [x] Tests added/passed --- src/cascadia/TerminalCore/Terminal.cpp | 9 +++++--- src/cascadia/TerminalCore/Terminal.hpp | 2 +- src/terminal/adapter/ITermDispatch.hpp | 9 +++++++- src/terminal/adapter/adaptDispatch.cpp | 22 +++++++++++++------ src/terminal/adapter/adaptDispatch.hpp | 4 ++-- src/terminal/adapter/termDispatch.hpp | 2 +- .../adapter/ut_adapter/adapterTest.cpp | 13 +++++++---- 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 039b251144..005916822d 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -94,7 +94,7 @@ void Terminal::UpdateSettings(ICoreSettings settings) if (_stateMachine) { - SetVtChecksumReportSupport(settings.AllowVtChecksumReport()); + SetOptionalFeatures(settings); } _getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput()); @@ -232,10 +232,13 @@ void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) engine.Dispatch().SetCursorStyle(cursorStyle); } -void Terminal::SetVtChecksumReportSupport(const bool enabled) +void Terminal::SetOptionalFeatures(winrt::Microsoft::Terminal::Core::ICoreSettings settings) { auto& engine = reinterpret_cast(_stateMachine->Engine()); - engine.Dispatch().SetVtChecksumReportSupport(enabled); + auto features = til::enumset{}; + features.set(ITermDispatch::OptionalFeature::ChecksumReport, settings.AllowVtChecksumReport()); + features.set(ITermDispatch::OptionalFeature::ClipboardWrite, settings.AllowVtClipboardWrite()); + engine.Dispatch().SetOptionalFeatures(features); } bool Terminal::IsXtermBracketedPasteModeEnabled() const noexcept diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index afbf873da9..e38841e3bc 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -95,7 +95,7 @@ public: void SetHighContrastMode(bool hc) noexcept; void SetFontInfo(const FontInfo& fontInfo); void SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle); - void SetVtChecksumReportSupport(const bool enabled); + void SetOptionalFeatures(winrt::Microsoft::Terminal::Core::ICoreSettings settings); bool IsXtermBracketedPasteModeEnabled() const noexcept; std::wstring_view GetWorkingDirectory() noexcept; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index da24f71b6f..5ebb2ff31c 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -25,6 +25,12 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch public: using StringHandler = std::function; + enum class OptionalFeature + { + ChecksumReport, + ClipboardWrite, + }; + #pragma warning(push) #pragma warning(disable : 26432) // suppress rule of 5 violation on interface because tampering with this is fraught with peril virtual ~ITermDispatch() = 0; @@ -133,7 +139,6 @@ public: virtual void ScreenAlignmentPattern() = 0; // DECALN virtual void SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR - virtual void SetVtChecksumReportSupport(const bool enabled) = 0; virtual void SetClipboard(wil::zwstring_view content) = 0; // OSCSetClipboard @@ -185,6 +190,8 @@ public: virtual StringHandler RestorePresentationState(const DispatchTypes::PresentationReportFormat format) = 0; // DECRSPS virtual void PlaySounds(const VTParameters parameters) = 0; // DECPS + + virtual void SetOptionalFeatures(const til::enumset features) = 0; }; inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() = default; #pragma warning(pop) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index bc03ae8331..cb8e4feb5a 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1297,11 +1297,6 @@ void AdaptDispatch::SelectAttributeChangeExtent(const DispatchTypes::ChangeExten } } -void AdaptDispatch::SetVtChecksumReportSupport(const bool enabled) noexcept -{ - _vtChecksumReportEnabled = enabled; -} - // Routine Description: // - DECRQCRA - Computes and reports a checksum of the specified area of // the buffer memory. @@ -1318,7 +1313,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p // If this feature is not enabled, we'll just report a zero checksum. if constexpr (Feature_VtChecksumReport::IsEnabled()) { - if (_vtChecksumReportEnabled) + if (_optionalFeatures.test(OptionalFeature::ChecksumReport)) { // If the page number is 0, then we're meant to return a checksum of all // of the pages, but we have no need for that, so we'll just return 0. @@ -1483,8 +1478,16 @@ void AdaptDispatch::DeviceAttributes() // 28 = Rectangular area operations // 32 = Text macros // 42 = ISO Latin-2 character set + // 52 = Clipboard access - _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c"); + if (_optionalFeatures.test(OptionalFeature::ClipboardWrite)) + { + _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42;52c"); + } + else + { + _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c"); + } } // Routine Description: @@ -4790,3 +4793,8 @@ void AdaptDispatch::PlaySounds(const VTParameters parameters) _api.PlayMidiNote(noteNumber, noteNumber == 71 ? 0 : velocity, duration); }); } + +void AdaptDispatch::SetOptionalFeatures(const til::enumset features) noexcept +{ + _optionalFeatures = features; +} diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 313611a732..af726c0e01 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -188,7 +188,7 @@ namespace Microsoft::Console::VirtualTerminal void PlaySounds(const VTParameters parameters) override; // DECPS - void SetVtChecksumReportSupport(const bool enabled) noexcept override; + void SetOptionalFeatures(const til::enumset features) noexcept override; private: enum class Mode @@ -313,7 +313,7 @@ namespace Microsoft::Console::VirtualTerminal std::unique_ptr _fontBuffer; std::shared_ptr _macroBuffer; std::optional _initialCodePage; - bool _vtChecksumReportEnabled = false; + til::enumset _optionalFeatures = { OptionalFeature::ClipboardWrite }; // We have two instances of the saved cursor state, because we need // one for the main buffer (at index 0), and another for the alt buffer diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 0f3908331b..99c9033fee 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -178,7 +178,7 @@ public: void PlaySounds(const VTParameters /*parameters*/) override{}; // DECPS - void SetVtChecksumReportSupport(const bool /*enabled*/) override{}; + void SetOptionalFeatures(const til::enumset /*features*/) override{}; }; #pragma warning(default : 26440) // Restore "can be declared noexcept" warning diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index b288561359..a6eecb6b37 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -415,7 +415,6 @@ public: auto& renderer = _testGetSet->_renderer; auto& renderSettings = renderer._renderSettings; auto adapter = std::make_unique(*_testGetSet, &renderer, renderSettings, _terminalInput); - adapter->SetVtChecksumReportSupport(true); fSuccess = adapter.get() != nullptr; if (fSuccess) @@ -1737,11 +1736,15 @@ public: Log::Comment(L"Test 1: Verify normal response."); _testGetSet->PrepData(); _pDispatch->DeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42;52c"); - auto pwszExpectedResponse = L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"; - _testGetSet->ValidateInputEvent(pwszExpectedResponse); + Log::Comment(L"Test 2: Verify response with clipboard disabled."); + _testGetSet->PrepData(); + _pDispatch->SetOptionalFeatures({}); + _pDispatch->DeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"); - Log::Comment(L"Test 2: Verify failure when ReturnResponse doesn't work."); + Log::Comment(L"Test 3: Verify failure when ReturnResponse doesn't work."); _testGetSet->PrepData(); _testGetSet->_returnResponseResult = FALSE; @@ -2187,6 +2190,8 @@ public: using namespace std::string_view_literals; + _pDispatch->SetOptionalFeatures(ITermDispatch::OptionalFeature::ChecksumReport); + Log::Comment(L"Test 1: ASCII characters"); outputText(L"A"sv); verifyChecksumReport(L"FF4F"); From 1980e725ab085acdeda96b7b0fa2526996279456 Mon Sep 17 00:00:00 2001 From: Chawye Hsu Date: Tue, 24 Jun 2025 07:55:46 +0800 Subject: [PATCH 085/177] schema: add Microsoft.WSL profile source to schema, width its type (#19047) WSL now generates profiles with the source named `Microsoft.WSL`, this PR adds the value to the profile schema. Refs #18231 --- doc/cascadia/profiles.schema.json | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index d60db96aac..a5011bb980 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -32,13 +32,21 @@ ] }, "DynamicProfileSource": { - "enum": [ - "Windows.Terminal.Wsl", - "Windows.Terminal.Azure", - "Windows.Terminal.PowershellCore", - "Windows.Terminal.VisualStudio" - ], - "type": "string" + "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "Microsoft.WSL", + "Windows.Terminal.Wsl", + "Windows.Terminal.Azure", + "Windows.Terminal.PowershellCore", + "Windows.Terminal.VisualStudio" + ] + } + ] }, "BellStyle": { "oneOf": [ From 3680e13bc0bbd0a68b69858623f738f1d8a6ead4 Mon Sep 17 00:00:00 2001 From: "S. M. Mohiuddin Khan Shiam" <147746955+mohiuddin-khan-shiam@users.noreply.github.com> Date: Wed, 25 Jun 2025 01:19:22 +0600 Subject: [PATCH 086/177] hygiene: fix mutable default argument in `vttests/common.py::sgr_n` (#19028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sgr_n previously used a mutable list (`seq=[]`) as its default parameter. In Python, default arguments are instantiated once at function‐definition time and then shared across all calls. If any caller mutated that list (e.g., `seq.append(...)`), later invocations would inherit the mutated state, producing unpredictable or corrupted VT-test sequences. --- src/tools/vttests/common.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tools/vttests/common.py b/src/tools/vttests/common.py index 2ecd854145..b08d23618b 100644 --- a/src/tools/vttests/common.py +++ b/src/tools/vttests/common.py @@ -59,7 +59,18 @@ def clear_all(): def sgr(code=0): csi('{}m'.format(code)) -def sgr_n(seq=[]): +def sgr_n(seq=None): + """Apply multiple SGR (Select Graphic Rendition) parameters. + + Parameters + ---------- + seq : Iterable of int | None + Sequence of numeric SGR codes to emit. If ``None`` (default) an empty + sequence is assumed. A mutable default argument must be avoided to + prevent unwanted state sharing between calls. + """ + if seq is None: + seq = [] csi('{}m'.format(';'.join(str(code) for code in seq))) def tbc(): From 218c9fbe3ecd79cc15d9cab267753b1b68ead250 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Jun 2025 13:06:03 -0700 Subject: [PATCH 087/177] Display a warning if SUI is unable to write to the settings file (#19027) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds logic to display a warning popup if the settings.json is marked as read-only and we try to write to the settings.json file. Previously, this scenario would crash, which definitely isn't right. However, a simple fix of "not-crashing" wouldn't feel right either. This leverages the existing infrastructure to display a warning dialog when we failed to write to the settings file. The main annoyance here is that that popup dialog is located in `TerminalWindow` and is normally triggered from a failed `SettingsLoadEventArgs`. To get around this, `CascadiaSettings::WriteSettingsToDisk()` now returns a boolean to signal if the write was successful; whereas if it fails, a warning is added to the settings object. If we fail to write to disk, the function will return false and we'll raise an event with the settings' warnings to `TerminalPage` which passes it along to `TerminalWindow`. Additionally, this uses `IVectorView` as opposed to `IVector` throughout the relevant code. It's more correct as the list of warnings shouldn't be mutable and the warnings from the `CascadiaSettings` object are retrieved in that format. ## Validation Steps Performed - ✅ Using SUI, save settings when the settings.json is set to read-only Closes #18913 --- src/cascadia/TerminalApp/AppLogic.cpp | 6 ++--- .../TerminalApp/SettingsLoadEventArgs.h | 4 ++-- src/cascadia/TerminalApp/TerminalPage.cpp | 7 ++++++ src/cascadia/TerminalApp/TerminalPage.h | 1 + src/cascadia/TerminalApp/TerminalPage.idl | 1 + src/cascadia/TerminalApp/TerminalWindow.cpp | 7 +++--- src/cascadia/TerminalApp/TerminalWindow.h | 2 +- src/cascadia/TerminalApp/TerminalWindow.idl | 2 +- .../TerminalSettingsEditor/MainPage.cpp | 5 +++- .../TerminalSettingsEditor/MainPage.h | 1 + .../TerminalSettingsEditor/MainPage.idl | 1 + .../TerminalSettingsModel/CascadiaSettings.h | 2 +- .../CascadiaSettings.idl | 2 +- .../CascadiaSettingsSerialization.cpp | 24 ++++++++++--------- src/inc/til/io.h | 6 ++++- 15 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index b7e0eae91a..6a0d456142 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -392,7 +392,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(true, static_cast(_settingsLoadedResult), _settingsLoadExceptionText, - warnings, + warnings.GetView(), _settings); SettingsChanged.raise(*this, *ev); return; @@ -424,7 +424,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(!initialLoad, _settingsLoadedResult, _settingsLoadExceptionText, - warnings, + warnings.GetView(), _settings); SettingsChanged.raise(*this, *ev); } @@ -491,7 +491,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(false, _settingsLoadedResult, _settingsLoadExceptionText, - warnings, + warnings.GetView(), _settings); auto window = winrt::make_self(*ev, _contentManager); diff --git a/src/cascadia/TerminalApp/SettingsLoadEventArgs.h b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h index e094810784..f9436f95ae 100644 --- a/src/cascadia/TerminalApp/SettingsLoadEventArgs.h +++ b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h @@ -12,14 +12,14 @@ namespace winrt::TerminalApp::implementation WINRT_PROPERTY(bool, Reload, false); WINRT_PROPERTY(uint64_t, Result, S_OK); WINRT_PROPERTY(winrt::hstring, ExceptionText, L""); - WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVector, Warnings, nullptr); + WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVectorView, Warnings, nullptr); WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::CascadiaSettings, NewSettings, nullptr); public: SettingsLoadEventArgs(bool reload, uint64_t result, winrt::hstring exceptionText, - winrt::Windows::Foundation::Collections::IVector warnings, + winrt::Windows::Foundation::Collections::IVectorView warnings, Microsoft::Terminal::Settings::Model::CascadiaSettings newSettings) : _Reload{ reload }, _Result{ result }, diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 702b4a36ee..810ce5272b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -4165,6 +4165,13 @@ namespace winrt::TerminalApp::implementation } }); + sui.ShowLoadWarningsDialog([weakThis{ get_weak() }](auto&& /*s*/, const Windows::Foundation::Collections::IVectorView& warnings) { + if (auto page{ weakThis.get() }) + { + page->ShowLoadWarningsDialog.raise(*page, warnings); + } + }); + return *settingsContent; } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index b369bd920e..1fd773fb3b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -187,6 +187,7 @@ namespace winrt::TerminalApp::implementation til::typed_event OpenSystemMenu; til::typed_event QuitRequested; til::typed_event ShowWindowChanged; + til::typed_event> ShowLoadWarningsDialog; til::typed_event RequestMoveContent; til::typed_event RequestReceiveContent; diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 2788cedd10..f1bd4fbc66 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -97,6 +97,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler OpenSystemMenu; event Windows.Foundation.TypedEventHandler ShowWindowChanged; + event Windows.Foundation.TypedEventHandler > ShowLoadWarningsDialog; event Windows.Foundation.TypedEventHandler RequestMoveContent; event Windows.Foundation.TypedEventHandler RequestReceiveContent; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 65518b5fdc..88a839949c 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -220,6 +220,7 @@ namespace winrt::TerminalApp::implementation _root->Initialized({ get_weak(), &TerminalWindow::_pageInitialized }); _root->WindowSizeChanged({ get_weak(), &TerminalWindow::_WindowSizeChanged }); _root->RenameWindowRequested({ get_weak(), &TerminalWindow::_RenameWindowRequested }); + _root->ShowLoadWarningsDialog({ get_weak(), &TerminalWindow::_ShowLoadWarningsDialog }); _root->Create(); AppLogic::Current()->SettingsChanged({ get_weak(), &TerminalWindow::UpdateSettingsHandler }); @@ -478,7 +479,7 @@ namespace winrt::TerminalApp::implementation // validating the settings. // - Only one dialog can be visible at a time. If another dialog is visible // when this is called, nothing happens. See ShowDialog for details - void TerminalWindow::_ShowLoadWarningsDialog(const Windows::Foundation::Collections::IVector& warnings) + void TerminalWindow::_ShowLoadWarningsDialog(const IInspectable&, const Windows::Foundation::Collections::IVectorView& warnings) { auto title = RS_(L"SettingsValidateErrorTitle"); auto buttonText = RS_(L"Ok"); @@ -539,7 +540,7 @@ namespace winrt::TerminalApp::implementation } else if (settingsLoadedResult == S_FALSE) { - _ShowLoadWarningsDialog(_initialLoadResult.Warnings()); + _ShowLoadWarningsDialog(nullptr, _initialLoadResult.Warnings()); } } @@ -825,7 +826,7 @@ namespace winrt::TerminalApp::implementation } else if (args.Result() == S_FALSE) { - _ShowLoadWarningsDialog(args.Warnings()); + _ShowLoadWarningsDialog(nullptr, args.Warnings()); } else if (args.Result() == S_OK) { diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index fd27617846..a7db7a8034 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -189,7 +189,7 @@ namespace winrt::TerminalApp::implementation const winrt::hstring& contentKey, HRESULT settingsLoadedResult, const winrt::hstring& exceptionText); - void _ShowLoadWarningsDialog(const Windows::Foundation::Collections::IVector& warnings); + void _ShowLoadWarningsDialog(const IInspectable& sender, const Windows::Foundation::Collections::IVectorView& warnings); bool _IsKeyboardServiceEnabled(); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index baf58317df..35ccfe9340 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -30,7 +30,7 @@ namespace TerminalApp { Boolean Reload { get; }; UInt64 Result { get; }; - IVector Warnings { get; }; + IVectorView Warnings { get; }; String ExceptionText { get; }; Microsoft.Terminal.Settings.Model.CascadiaSettings NewSettings { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 098827d9a9..b5fdc867b6 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -680,7 +680,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void MainPage::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) { _settingsClone.LogSettingChanges(false); - _settingsClone.WriteSettingsToDisk(); + if (!_settingsClone.WriteSettingsToDisk()) + { + ShowLoadWarningsDialog.raise(*this, _settingsClone.Warnings()); + } } void MainPage::ResetButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index aebeb516e3..ff87f4910b 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -46,6 +46,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::ExtensionsViewModel ExtensionsVM() const noexcept { return _extensionsVM; } til::typed_event OpenJson; + til::typed_event> ShowLoadWarningsDialog; private: Windows::Foundation::Collections::IObservableVector _breadcrumbs; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.idl b/src/cascadia/TerminalSettingsEditor/MainPage.idl index 390ae5cb80..20f3bab869 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -39,6 +39,7 @@ namespace Microsoft.Terminal.Settings.Editor void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); event Windows.Foundation.TypedEventHandler OpenJson; + event Windows.Foundation.TypedEventHandler > ShowLoadWarningsDialog; // Due to the aforementioned bug, we can't use IInitializeWithWindow _here_ either. // Let's just smuggle the HWND in as a UInt64 :| diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index a4fd16bd43..defd04af69 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -167,7 +167,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IVectorView Extensions(); void ResetApplicationState() const; void ResetToDefaultSettings(); - void WriteSettingsToDisk(); + bool WriteSettingsToDisk(); Json::Value ToJson() const; Model::Profile ProfileDefaults() const; Model::Profile CreateNewProfile(); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 95165bcc80..d1f4304ba9 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -31,7 +31,7 @@ namespace Microsoft.Terminal.Settings.Model CascadiaSettings Copy(); void ResetApplicationState(); void ResetToDefaultSettings(); - void WriteSettingsToDisk(); + Boolean WriteSettingsToDisk(); void LogSettingChanges(Boolean isJsonLoad); String Hash { get; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 6e03785dec..bbbb9172c7 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -1226,15 +1226,7 @@ try // settings string back to the file. if (mustWriteToDisk) { - try - { - settings->WriteSettingsToDisk(); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - settings->_warnings.Append(SettingsLoadWarnings::FailedToWriteToSettings); - } + settings->WriteSettingsToDisk(); } else { @@ -1539,7 +1531,7 @@ void CascadiaSettings::ResetToDefaultSettings() // - // Return Value: // - -void CascadiaSettings::WriteSettingsToDisk() +bool CascadiaSettings::WriteSettingsToDisk() { // write current settings to current settings file Json::StreamWriterBuilder wbuilder; @@ -1547,7 +1539,17 @@ void CascadiaSettings::WriteSettingsToDisk() wbuilder.settings_["indentation"] = " "; wbuilder.settings_["precision"] = 6; // prevent values like 1.1000000000000001 - _writeSettingsToDisk(Json::writeString(wbuilder, ToJson())); + try + { + _writeSettingsToDisk(Json::writeString(wbuilder, ToJson())); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + _warnings.Append(SettingsLoadWarnings::FailedToWriteToSettings); + return false; + } + return true; } void CascadiaSettings::_writeSettingsToDisk(std::string_view contents) diff --git a/src/inc/til/io.h b/src/inc/til/io.h index 6f917f0d0b..9e28b99630 100644 --- a/src/inc/til/io.h +++ b/src/inc/til/io.h @@ -256,7 +256,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // renaming one is (supposed to be) atomic. // Wait... "supposed to be"!? Well it's technically not always atomic, // but it's pretty darn close to it, so... better than nothing. - std::filesystem::rename(tmpPath, resolvedPath); + std::filesystem::rename(tmpPath, resolvedPath, ec); + if (ec) + { + THROW_WIN32_MSG(ec.value(), "failed to write to file"); + } } } // io } // til From 9c452cd985bb0c602bb17b1465cd6b66a1ae272e Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:54:04 -0400 Subject: [PATCH 088/177] Upgrade to check-spelling v0.0.25 (#18940) - Various spelling fixes - Refresh metadata (including dictionaries) - Upgrade to v0.0.25 ## Validation Steps Performed - check-spelling has been automatically testing this repository for a while now on a daily basis to ensure that it works fairly reliably: https://github.com/check-spelling-sandbox/autotest-check-spelling/actions/workflows/microsoft-terminal-spelling2.yml Specific in-code fixes: - winget - whereas - tl;dr - set up - otherwise, - more, - macbook - its - invalid - in order to - if - if the - for this tab,... - fall back - course, - cch - aspect - archaeologists - an - all at once - a - `...` - ; otherwise, Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- .github/actions/spelling/allow/allow.txt | 19 -- .github/actions/spelling/allow/apis.txt | 72 ------ .github/actions/spelling/allow/chinese.txt | 5 +- .github/actions/spelling/allow/colors.txt | 9 - .github/actions/spelling/allow/fonts.txt | 2 +- .github/actions/spelling/allow/japanese.txt | 3 - .github/actions/spelling/allow/math.txt | 9 - .github/actions/spelling/allow/microsoft.txt | 29 +-- .github/actions/spelling/allow/names.txt | 34 +-- .github/actions/spelling/candidate.patterns | 89 +++++-- .github/actions/spelling/excludes.txt | 4 +- .github/actions/spelling/expect/alphabet.txt | 41 +-- .github/actions/spelling/expect/expect.txt | 184 ++------------ .../actions/spelling/line_forbidden.patterns | 232 ++++++++++++++++- .../actions/spelling/patterns/patterns.txt | 239 ++++++++++-------- .github/actions/spelling/reject.txt | 12 +- .github/workflows/spelling2.yml | 39 ++- .github/workflows/winget.yml | 2 +- build/Helix/HelixTestHelpers.cs | 92 +++---- .../job-pgo-build-nuget-and-publish.yml | 2 +- doc/Niksa.md | 40 +-- doc/WindowsTestPasses.md | 6 +- doc/creating_a_new_project.md | 6 +- .../#1564 - Settings UI/cascading-settings.md | 2 +- doc/specs/#1595 - Suggestions UI/Snippets.md | 2 +- .../#1790 - Font features and axes-spec.md | 6 +- ... - Theme-controlled color scheme switch.md | 2 +- ...2 - Windows Terminal Session Management.md | 2 +- .../#5000 - Process Model 2.0.md | 4 +- doc/specs/#605 - Search/spec.md | 2 +- .../#7335 - Console Allocation Policy.md | 2 +- .../#754 - Cascading Default Settings.md | 2 +- .../#885 - Terminal Settings Model.md | 6 +- .../#2634 - Broadcast Input.md | 6 +- doc/terminal-a11y-2023.md | 12 +- policies/en-US/WindowsTerminal.adml | 2 +- .../GUIConsole/GUIConsole.WPF/MainWindow.xaml | 2 +- src/buffer/out/textBuffer.cpp | 2 +- src/buffer/out/ut_textbuffer/ReflowTests.cpp | 2 +- .../ShellExtension/OpenTerminalHere.cpp | 2 +- .../TerminalApp/AppActionHandlers.cpp | 2 +- src/cascadia/TerminalApp/AppLogic.cpp | 2 +- src/cascadia/TerminalApp/ColorHelper.cpp | 2 +- src/cascadia/TerminalApp/CommandPalette.cpp | 2 +- src/cascadia/TerminalApp/Pane.cpp | 24 +- .../TerminalApp/ShortcutActionDispatch.cpp | 2 +- .../TerminalApp/SuggestionsControl.cpp | 2 +- src/cascadia/TerminalApp/TerminalPage.cpp | 4 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 4 +- .../TerminalControl/ControlInteractivity.h | 2 +- .../HwndTerminalAutomationPeer.cpp | 2 +- src/cascadia/TerminalControl/TermControl.cpp | 6 +- src/cascadia/TerminalControl/TermControl.h | 2 +- .../TermControlAutomationPeer.cpp | 4 +- src/cascadia/TerminalCore/Terminal.cpp | 8 +- src/cascadia/TerminalCore/TerminalApi.cpp | 2 +- .../TerminalCore/TerminalSelection.cpp | 2 +- .../TerminalSettingsEditor/MainPage.cpp | 2 +- .../TerminalSettingsModel/ActionMap.cpp | 4 +- .../CascadiaSettingsSerialization.cpp | 2 +- .../TerminalSettingsModel/Command.cpp | 4 +- .../TerminalSettingsModel/JsonUtils.h | 2 +- .../TerminalSettingsModel/Profile.cpp | 2 +- .../TerminalSettings.cpp | 2 +- src/cascadia/TerminalSettingsModel/Theme.cpp | 2 +- src/cascadia/WindowsTerminal/AppHost.cpp | 2 +- .../WindowsTerminal/NonClientIslandWindow.cpp | 2 +- .../TerminalControl.xaml.cs | 2 +- src/host/ConsoleArguments.cpp | 10 +- src/host/VtIo.cpp | 6 +- src/host/exe/exemain.cpp | 2 +- src/host/ft_host/API_AliasTestsHelpers.hpp | 2 +- src/host/outputStream.cpp | 2 +- src/host/readDataCooked.cpp | 2 +- src/host/readDataDirect.cpp | 2 +- src/host/readDataRaw.cpp | 2 +- src/host/screenInfo.cpp | 4 +- src/host/selection.cpp | 2 +- src/host/ut_host/ConsoleArgumentsTests.cpp | 6 +- src/host/ut_host/ScreenBufferTests.cpp | 2 +- src/host/utils.cpp | 2 +- src/host/writeData.cpp | 2 +- src/inc/til/io.h | 2 +- src/inc/til/small_vector.h | 2 +- .../base/InteractivityFactory.cpp | 2 +- src/interactivity/win32/WindowMetrics.cpp | 2 +- src/propsheet/preview.cpp | 2 +- src/propslib/DelegationConfig.cpp | 2 +- src/renderer/atlas/AtlasEngine.cpp | 2 +- src/renderer/atlas/BackendD3D.cpp | 2 +- src/renderer/atlas/common.h | 2 +- src/renderer/base/renderer.cpp | 2 +- src/renderer/gdi/state.cpp | 2 +- src/server/IoDispatchers.cpp | 2 +- src/terminal/adapter/FontBuffer.cpp | 6 +- src/terminal/adapter/MacroBuffer.hpp | 2 +- src/terminal/adapter/adaptDispatch.cpp | 16 +- src/terminal/adapter/ut_adapter/inputTest.cpp | 2 +- src/terminal/input/mouseInput.cpp | 2 +- src/terminal/input/terminalInput.cpp | 2 +- src/terminal/parser/stateMachine.cpp | 2 +- src/tools/U8U16Test/U8U16Test.cpp | 2 +- src/types/UiaTextRangeBase.cpp | 6 +- src/winconpty/winconpty.cpp | 2 +- tools/GenerateAppxFromManifest.ps1 | 4 +- tools/README.md | 2 +- 107 files changed, 702 insertions(+), 744 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index b347ec85b9..a99422e8b0 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -11,17 +11,11 @@ colorbrewer commandlines consvc copyable -CText -dalet dcs deselection -dialytika diffing Dimidium -dje downsides -dze -dzhe Emacspeak Fitt flac @@ -29,16 +23,13 @@ FTCS gantt gfm ghe -gje godbolt hstrings hyperlinking hyperlinks Kbds -kje libfuzzer liga -lje Llast Lmid locl @@ -50,10 +41,8 @@ minimalistic mkmk mnt mru -nje notwrapped NTMTo -ogonek overlined perlw postmodern @@ -75,18 +64,13 @@ rlig rubyw runtimes servicebus -shcha -similaritytolerance slnt stakeholders subpage sustainability sxn Tencent -TLDR -tonos toolset -tshe UEFI uiatextrange und @@ -94,8 +78,5 @@ vsdevcmd westus workarounds WSLs -wtconfig XBox YBox -yeru -zhe diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 6670909587..336e62ff2a 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -2,11 +2,8 @@ aalt abvm ACCEPTFILES ACCESSDENIED -acl -aclapi alignas alignof -allocconsolewithoptions APPLYTOSUBMENUS appxrecipe bitfield @@ -15,29 +12,20 @@ BUILDBRANCH BUILDMSG BUILDNUMBER BYCOMMAND -BYPOSITION charconv -CLASSNOTAVAILABLE CLOSEAPP cmdletbinding -COLORPROPERTY colspan COMDLG commandlinetoargv -commoncontrols -comparand COPYFROMRESOURCE cstdint CXICON CYICON -Dacl dataobject -dcomp debugbreak delayimp -DERR dlldata -DNE dnom DONTADDTORECENT DWMSBT @@ -64,81 +52,42 @@ GETHIGHCONTRAST GETMOUSEHOVERTIME GETTEXTLENGTH HARDBREAKS -Hashtable HIGHCONTRASTON HIGHCONTRASTW HIGHQUALITYSCALE hinternet -HINTERNET hotkeys href hrgn HTCLOSE hwinsta -HWINSTA -IActivation -IApp IAppearance -IAsync -IBind -IBox -IClass -IComparable -IComparer ICONINFO -IConnection -ICustom -IDialog IDirect -Idn -IExplorer -IFACEMETHOD -IFile -IGraphics -IImage IInheritable -IMap imm -IObject iosfwd -IPackage isa -ISetup isspace -IStorage istream -IStringable -ITab -ITaskbar -itow -IUri -IVirtual KEYSELECT LCID LINEBREAK -llabs -llu -localtime lround Lsa lsass LSHIFT LTGRAY MAINWINDOW -MAXIMIZEBOX medi -memchr memicmp MENUCOMMAND MENUDATA MENUINFO MENUITEMINFOW MINIMIZEBOX -mmeapi MOUSELEAVE mov -mptt -msappx MULTIPLEUSE NCHITTEST NCLBUTTONDBLCLK @@ -148,7 +97,6 @@ NCPOINTERUPDATE NCRBUTTONDBLCLK NIF NIN -NOAGGREGATION NOASYNC NOBREAKS NOCHANGEDIR @@ -161,8 +109,6 @@ NOTIFYICONDATA ntprivapi NTSYSCALLAPI numr -oaidl -ocidl ODR offsetof ofstream @@ -172,22 +118,17 @@ OSVERSIONINFOEXW otms OUTLINETEXTMETRICW overridable -PACL PAGESCROLL PALLOC PATINVERT -PEXPLICIT PICKFOLDERS PINPUT pmr -ptstr QUERYENDSESSION rcx REGCLS RETURNCMD rfind -RLO -rnrn ROOTOWNER roundf RSHIFT @@ -206,22 +147,17 @@ SHOWTIP SINGLEUSE SIZENS smoothstep -snprintf SOFTBREAK spsc -sregex SRWLOC srwlock SRWLOCK STDCPP STDMETHOD -strchr strcpy streambuf strtoul Stubless -Subheader -Subpage syscall syscolors SYSTEMBACKDROP @@ -238,23 +174,17 @@ tokeninfo tolower toupper TRACKMOUSEEVENT -TTask -TVal UChar UFIELD ULARGE UOI UPDATEINIFILE urlmon -userenv USEROBJECTFLAGS Vcpp Viewbox virtualalloc -vsnwprintf wcsnlen -wcsstr -wcstoui WDJ winhttp wininet @@ -264,10 +194,8 @@ winstamin wmemcmp wpc WSF -wsregex WWH wwinmain -xchg XDocument XElement xfacet diff --git a/.github/actions/spelling/allow/chinese.txt b/.github/actions/spelling/allow/chinese.txt index 6b7ca2a917..02ed0e4bae 100644 --- a/.github/actions/spelling/allow/chinese.txt +++ b/.github/actions/spelling/allow/chinese.txt @@ -1,5 +1,4 @@ CHINESEBIG choseong -Jongseong -Jungseong -ssangtikeut +Choseong +CHOSEONG diff --git a/.github/actions/spelling/allow/colors.txt b/.github/actions/spelling/allow/colors.txt index b56723b368..2b7192cf5b 100644 --- a/.github/actions/spelling/allow/colors.txt +++ b/.github/actions/spelling/allow/colors.txt @@ -1,4 +1,3 @@ -alice aliceblue antiquewhite blanchedalmond @@ -39,7 +38,6 @@ gainsboro ghostwhite greenyellow hotpink -indian indianred lavenderblush lawngreen @@ -74,7 +72,6 @@ mediumvioletred midnightblue mintcream mistyrose -navajo navajowhite navyblue oldlace @@ -88,7 +85,6 @@ papayawhip peachpuff peru powderblue -rebecca rebeccapurple rosybrown royalblue @@ -109,9 +105,4 @@ webgrey webmaroon webpurple whitesmoke -xaroon -xray -xreen -xrey -xurple yellowgreen diff --git a/.github/actions/spelling/allow/fonts.txt b/.github/actions/spelling/allow/fonts.txt index c9931b2fc0..a0bacd0600 100644 --- a/.github/actions/spelling/allow/fonts.txt +++ b/.github/actions/spelling/allow/fonts.txt @@ -1,8 +1,8 @@ Consolas emoji emojis +Emojis Extralight -Gabriola Iosevka MDL Monofur diff --git a/.github/actions/spelling/allow/japanese.txt b/.github/actions/spelling/allow/japanese.txt index 261f45dc2a..6d941c08a5 100644 --- a/.github/actions/spelling/allow/japanese.txt +++ b/.github/actions/spelling/allow/japanese.txt @@ -1,4 +1 @@ -arigatoo -doomo -Kaomojis TATEGAKI diff --git a/.github/actions/spelling/allow/math.txt b/.github/actions/spelling/allow/math.txt index bf8960f008..8c6041d465 100644 --- a/.github/actions/spelling/allow/math.txt +++ b/.github/actions/spelling/allow/math.txt @@ -1,11 +1,2 @@ -atan -CPrime -HBar -HPrime isnan -LPrime -LStep -powf -RSub -sqrtf ULP diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index 940b0fc29d..3f639c4746 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -4,12 +4,11 @@ advapi akv AKV altform -altforms +Altforms appendwttlogging appinstaller appx appxbundle -appxerror appxmanifest ATL autoexec @@ -25,66 +24,43 @@ CPRs cryptbase cscript DACL -DACLs defaultlib diffs disposables -dotnetfeed -DTDs -DWINRT enablewttlogging HOMESHARE Intelli issecret -IVisual libucrt libucrtd -LKG LOCKFILE Lxss makepri -mfcribbon microsoft -microsoftonline MSAA msixbundle MSVC MSVCP mtu muxc -netcore -Onefuzz -osgvsowi -PFILETIME pgc pgo pgosweep -powerrename powershell priconfig PRIINFO propkey pscustomobject QWORD -rdpclip regedit resfiles -robocopy SACLs -sdkddkver segoe -Shobjidl sid -Skype SRW sxs symbolrequestprod Sysinternals -sysnative -systemroot -taskkill -tasklist -tdbuildteamid ucrt ucrtd unvirtualized @@ -92,12 +68,9 @@ USERDNSDOMAIN VCRT vcruntime Virtualization -visualstudio vscode VSTHRD WINBASEAPI -winsdkver -wlk wscript wslpath wtl diff --git a/.github/actions/spelling/allow/names.txt b/.github/actions/spelling/allow/names.txt index 11a1f95486..a97ec94ac9 100644 --- a/.github/actions/spelling/allow/names.txt +++ b/.github/actions/spelling/allow/names.txt @@ -1,4 +1,3 @@ -Anup arkthur austdi Ballmer @@ -6,13 +5,11 @@ bhoj Bhojwani Bluloco carlos -craigloewen dhowett Diviness dsafa duhowett DXP -ekg eryksun ethanschoonover Firefox @@ -25,70 +22,42 @@ Hernan Howett Illhardt Imms -iquilezles italo jantari jerrysh Kaiyu -kimwalisch -KMehrain -Kodelife -KODELIFE -Kourosh -kowalczyk leonardder -leonmsft -Lepilleur lhecker -lukesampson -Macbook -Manandhar masserano -mbadolato -Mehrain menger -mgravell -michaelniksa -michkap migrie mikegr mikemaccana -miloush miniksa nguyen niksa nvaccess nvda -oising -oldnewthing -opengl osgwiki Ottosson pabhojwa -panos +Panos paulcam -pauldotknopf PGP Pham Rincewind -rprichard Schoonover shadertoy Shomnipotence simioni -Somuah sonph sonpham stakx -talo thereses Thysell -Walisch WDX Wellons Westerman -Wirt -Wojciech zadjii Zamor zamora @@ -96,4 +65,3 @@ Zamora zljubisic Zoey zorio -Zverovich diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns index 5d93035f91..d405383514 100644 --- a/.github/actions/spelling/candidate.patterns +++ b/.github/actions/spelling/candidate.patterns @@ -1,3 +1,6 @@ +# Repeated letters +\b([a-z])\g{-1}{2,}\b + # marker to ignore all code on line ^.*/\* #no-spell-check-line \*/.*$ # marker to ignore all code on line @@ -7,6 +10,9 @@ # cspell inline ^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b +# copyright +Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+ + # patch hunk comments ^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .* # git index header @@ -15,6 +21,9 @@ index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40} # file permissions ['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s] +# css fonts +\bfont(?:-family|):[^;}]+ + # css url wrappings \burl\([^)]+\) @@ -32,9 +41,6 @@ index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40} # https/http/file urls (?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/*%?=~_|!:,.;]+[-A-Za-z0-9+&@#/*%=~_|] -# https/http/file urls -(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] - # mailto urls mailto:[-a-zA-Z=;:/?%&0-9+@._]{3,} @@ -69,6 +75,8 @@ magnet:[?=:\w]+ # Amazon \bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|) +# AWS ARN +arn:aws:[-/:\w]+ # AWS S3 \b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]* # AWS execute-api @@ -95,6 +103,8 @@ vpc-\w+ \bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]* # Google APIs \bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+ +# Google Artifact Registry +\.pkg\.dev(?:/[-\w]+)+(?::[-\w]+|) # Google Storage \b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|) # Google Calendar @@ -130,6 +140,8 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. \bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+ # Google Colab Research Drive \bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]* +# Google Cloud regions +(?:us|(?:north|south)america|europe|asia|australia|me|africa)-(?:north|south|east|west|central){1,2}\d+ # GitHub SHAs (api) \bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b @@ -168,6 +180,12 @@ GHSA(?:-[0-9a-z]{4}){3} # GitLab commits \bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b +# #includes +^\s*#include\s*(?:<.*?>|".*?") + +# #pragma lib +^\s*#pragma comment\(lib, ".*?"\) + # binance accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* @@ -220,7 +238,7 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* \bmedium\.com/@?[^/\s"]+/[-\w]+ # microsoft -\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]* +\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]* # powerbi \bapp\.powerbi\.com/reportEmbed/[^"' ]* # vs devops @@ -394,7 +412,7 @@ ipfs://[0-9a-zA-Z]{3,} \bgetopts\s+(?:"[^"]+"|'[^']+') # ANSI color codes -(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m +(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m # URL escaped characters %[0-9A-F][A-F](?=[A-Za-z]) @@ -431,10 +449,14 @@ sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]* # pki (base64) LS0tLS1CRUdJT.* +# C# includes +^\s*using [^;]+; + # uuid: \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b # hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#x?|%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b +(?:[\\0][xX]|\\u|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b + # integrity integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} @@ -452,7 +474,10 @@ integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} Name\[[^\]]+\]=.* # IServiceProvider / isAThing -(?:\b|_)(?:(?:ns|)I|isA)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) +(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) + +# python +\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,}) # crypt (['"])\$2[ayb]\$.{56}\g{-1} @@ -466,17 +491,14 @@ Name\[[^\]]+\]=.* # machine learning (?) \b(?i)ml(?=[a-z]{2,}) -# python -\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,}) - # scrypt / argon \$(?:scrypt|argon\d+[di]*)\$\S+ # go.sum \bh1:\S+ -# scala imports -^import (?:[\w.]|\{\w*?(?:,\s*(?:\w*|\*))+\})+ +# imports +^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+ # scala modules ("[^"]+"\s*%%?\s*){2,3}"[^"]+" @@ -485,7 +507,7 @@ Name\[[^\]]+\]=.* image: [-\w./:@]+ # Docker images -^\s*FROM\s+\S+:\S+(?:\s+AS\s+\S+|) +^\s*(?i)FROM\s+\S+:\S+(?:\s+AS\s+\S+|) # `docker images` REPOSITORY TAG IMAGE ID CREATED SIZE \s*\S+/\S+\s+\S+\s+[0-9a-f]{8,}\s+\d+\s+(?:hour|day|week)s ago\s+[\d.]+[KMGT]B @@ -501,6 +523,7 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} # The `(?=.*?")` suffix should limit the false positives rate # printf #%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA])(?=[a-zA-Z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"]) + # Alternative printf # %s %(?:s(?=[a-z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%[^s])\b)(?=.*?['"]) @@ -524,7 +547,7 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} # javascript replace regex \.replace\(/[^/\s"]{3,}/[gim]*\s*, # assign regex -= /[^*].*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/[gi]?(?=\W|$) += /[^*].*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/[gim]*(?=\W|$) # perl regex test [!=]~ (?:/.*/|m\{.*?\}|m<.*?>|m([|!/@#,;']).*?\g{-1}) @@ -538,7 +561,7 @@ perl(?:\s+-[a-zA-Z]\w*)+ (?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\() # Go regular expressions -regexp?\.MustCompile\(`[^`]*`\) +regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\) # regex choice \(\?:[^)]+\|[^)]+\) @@ -586,7 +609,7 @@ urn:shemas-jetbrains-com # xcode # xcodeproject scenes -(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}" +(?:Controller|destination|(?:first|second)Item|ID|id)="\w{3}-\w{2}-\w{3}" # xcode api botches customObjectInstantitationMethod @@ -601,14 +624,17 @@ PrependWithABINamepsace \.fa-[-a-z0-9]+ # bearer auth -(['"])[Bb]ear[e][r] .*?\g{-1} +(['"])[Bb]ear[e][r] .{3,}?\g{-1} # bearer auth -\b[Bb]ear[e][r]:? [-a-zA-Z=;:/0-9+.]+ +\b[Bb]ear[e][r]:? [-a-zA-Z=;:/0-9+.]{3,} # basic auth (['"])[Bb]asic [-a-zA-Z=;:/0-9+]{3,}\g{-1} +# basic auth +: [Bb]asic [-a-zA-Z=;:/0-9+.]{3,} + # base64 encoded content #([`'"])[-a-zA-Z=;:/0-9+]{3,}=\g{-1} # base64 encoded content in xml/sgml @@ -620,6 +646,9 @@ PrependWithABINamepsace # base64 encoded pkcs #\bMII[-a-zA-Z=;:/0-9+]+ +# uuencoded +#[!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_]{40,} + # DNS rr data #(?:\d+\s+){3}(?:[-+/=.\w]{2,}\s*){1,2} @@ -630,7 +659,7 @@ PrependWithABINamepsace \bnumer\b(?=.*denom) # Time Zones -\b(?:Africa|Atlantic|America|Antarctica|Asia|Australia|Europe|Indian|Pacific)(?:/\w+)+ +\b(?:Africa|Atlantic|America|Antarctica|Arctic|Asia|Australia|Europe|Indian|Pacific)(?:/[-\w]+)+ # linux kernel info ^(?:bugs|flags|Features)\s+:.* @@ -676,11 +705,17 @@ TeX/AMS "varsIgnorePattern": ".+" # nolint -nolint:\w+ +nolint:\s*[\w,]+ # Windows short paths [/\\][^/\\]{5,6}~\d{1,2}(?=[/\\]) +# Windows Resources with accelerators +\b[A-Z]&[a-z]+\b(?!;) + +# signed off by +(?i)Signed-off-by: .* + # cygwin paths /cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+ @@ -715,29 +750,29 @@ W/"[^"]+" # Compiler flags (Unix, Java/Scala) # Use if you have things like `-Pdocker` and want to treat them as `docker` -#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) +#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) # Compiler flags (Windows / PowerShell) # This is a subset of the more general compiler flags pattern. # It avoids matching `-Path` to prevent it from being treated as `ath` -#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})) +#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})) # Compiler flags (linker) ,-B # libraries -#(?:\b|_)lib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z]) - -# WWNN/WWPN (NAA identifiers) -\b(?:0x)?10[0-9a-f]{14}\b|\b(?:0x|3)?[25][0-9a-f]{15}\b|\b(?:0x|3)?6[0-9a-f]{31}\b +#(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z]) # iSCSI iqn (approximate regex) \biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b +# WWNN/WWPN (NAA identifiers) +\b(?:0x)?10[0-9a-f]{14}\b|\b(?:0x|3)?[25][0-9a-f]{15}\b|\b(?:0x|3)?6[0-9a-f]{31}\b + # curl arguments \b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)* # set arguments -\b(?:bash|sh|set)(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)* +\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)* # tar arguments \b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ # tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index c142f11548..23511535fb 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -97,7 +97,7 @@ Resources/(?!en) ^doc/reference/UTF8-torture-test\.txt$ ^doc/reference/windows-terminal-logo\.ans$ ^NOTICE.md -^oss/ +^oss/.*?/ ^samples/PixelShaders/Screenshots/ ^src/cascadia/TerminalSettingsEditor/SegoeFluentIconList.h$ ^src/interactivity/onecore/BgfxEngine\. @@ -121,8 +121,8 @@ Resources/(?!en) ^tools/ReleaseEngineering/ServicingPipeline\.ps1$ ^XamlStyler\.json$ ^\.github/actions/spelling/ +^\.github/workflows/spelling\d*\.yml$ ^\.vsconfig$ -^\Q.github/workflows/spelling.yml\E$ ^\Qbuild/config/release.gdnbaselines\E$ ^\Qdep/WinAppDriver/EULA.rtf\E$ ^\Qdoc/reference/windows-terminal-logo.ans\E$ diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt index 1ff5611c38..73bbdffb3c 100644 --- a/.github/actions/spelling/expect/alphabet.txt +++ b/.github/actions/spelling/expect/alphabet.txt @@ -1,38 +1,19 @@ -AAAAAABBBBBBCCC -AAAAABBBBBBCCC -abcd -ABCDEFGHIJ -abcdefghijk -ABCDEFGHIJKLMNOPQRS -ABCDEFGHIJKLMNOPQRST -ABCDEFGHIJKLMNOPQRSTUVWXY ABCG ABE -abf -BBBBB -BBBBBCCC -BBBBCCCCC +AZZ +BBDM BBGGRR -EFG +CBN +cbt +Ccc +cch efg -EFGh efgh -KLMNOQQQQQQQQQQ -QQQQQQQQQQABCDEFGHIJ -QQQQQQQQQQABCDEFGHIJKLMNOPQRS -QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ -QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ -QQQQQQQQQQABCDEFGHIJPQRST -QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ +fdw +fesb +ffd +FFFD qwerty qwertyuiopasdfg -ZAAZZ -ZABBZ -ZBAZZ -ZBBBZ -ZBBZZ ZYXWVUT -ZZBBZ -ZZZBB -ZZZBZ -ZZZZZ +zzf diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 318903fe17..3507bf387b 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -2,7 +2,6 @@ aaaaabbb aabbcc ABANDONFONT abbcc -ABCF abgr ABORTIFHUNG ACCESSTOKEN @@ -26,13 +25,11 @@ AImpl AInplace ALIGNRIGHT allocing -allocs alpc ALTERNATENAME ALTF ALTNUMPAD ALWAYSTIP -aml ansicpg ANSISYS ANSISYSRC @@ -49,22 +46,17 @@ APIENTRY apiset APPBARDATA appcontainer -appium appletname -applicationmodel APPLMODAL Applocal appmodel +appshellintegration APPWINDOW APPXMANIFESTVERSION APrep -apsect APSTUDIO -archeologists -Argb ARRAYSIZE ARROWKEYS -asan ASBSET ASetting ASingle @@ -72,11 +64,11 @@ ASYNCDONTCARE ASYNCWINDOWPOS atch ATest +atg aumid Authenticode AUTOBUDDY AUTOCHECKBOX -autocrlf autohide AUTOHSCROLL automagically @@ -92,10 +84,8 @@ AZZ backgrounded Backgrounder backgrounding -backported backstory Bazz -bbb bbccb BBDM bbwe @@ -123,13 +113,13 @@ bitmasks BITOPERATION BKCOLOR BKGND +BKMK Bksp Blt blu BLUESCROLL bmi bodgy -BODGY BOLDFONT Borland boutput @@ -148,9 +138,7 @@ buflen buildsystems buildtransitive BValue -bytebuffer -cac -cacafire +Cacafire CALLCONV CANDRABINDU capslock @@ -161,12 +149,6 @@ catid cazamor CBash cbiex -CBN -cbt -Ccc -CCCBB -CCCDDD -cch CCHAR CCmd ccolor @@ -181,15 +163,14 @@ cfie cfiex cfte CFuzz +cgmanifest cgscrn chafa changelists CHARSETINFO -chh chshdng CHT CLASSSTRING -CLE cleartype CLICKACTIVE clickdown @@ -212,9 +193,7 @@ cmw CNL cnn Codeflow -codenav codepages -codepath coinit colorizing COLORONCOLOR @@ -226,13 +205,8 @@ colortbl colortest colortool COLORVALUE -combaseapi comctl -commandline -commctrl commdlg -COMMITID -componentization conapi conattrs conbufferout @@ -250,7 +224,6 @@ conintegrityuwp coninteractivitybase coninteractivityonecore coninteractivitywin -conio coniosrv CONKBD conlibk @@ -263,13 +236,13 @@ conpropsp conpty conptylib conserv +consoleaccessibility consoleapi CONSOLECONTROL CONSOLEENDTASK consolegit consolehost CONSOLEIME -consoleinternal CONSOLESETFOREGROUND consoletaeftemplates consoleuwp @@ -277,7 +250,6 @@ Consolewait CONSOLEWINDOWOWNER consrv constexprable -constness contentfiles conterm contsf @@ -311,7 +283,6 @@ csbi csbiex CSHORT Cspace -csrmsg CSRSS csrutil CSTYLE @@ -368,16 +339,14 @@ DBGFONTS DBGOUTPUT dbh dblclk +DBUILD Dcd DColor -dcommon +DCOMMON DComposition -DDDCCC -dde DDESHARE DDevice DEADCHAR -dealloc Debian debugtype DECAC @@ -482,18 +451,16 @@ DELAYLOAD DELETEONRELEASE depersist deprioritized -deserializers -desktopwindowxamlsource devicecode Dext DFactory DFF dialogbox +DINLINE directio DIRECTX DISABLEDELAYEDEXPANSION DISABLENOSCROLL -DISPATCHNOTIFY DISPLAYATTRIBUTE DISPLAYCHANGE distros @@ -530,9 +497,10 @@ dsm dsound DSSCL DSwap -DTest DTo DTTERM +DUNICODE +DUNIT dup'ed dvi dwl @@ -542,8 +510,6 @@ dwmapi DWORDs dwrite dxgi -dxgidwm -dxinterop dxsm dxttbmp Dyreen @@ -557,7 +523,6 @@ EDITKEYS EDITTEXT EDITUPDATE Efast -efghijklmn EHsc EINS ELEMENTNOTAVAILABLE @@ -566,7 +531,6 @@ enabledelayedexpansion ENDCAP endptr ENTIREBUFFER -entrypoints ENU ENUMLOGFONT ENUMLOGFONTEX @@ -581,7 +545,6 @@ esrp ESV ETW EUDC -EVENTID eventing evflags evt @@ -603,16 +566,6 @@ FACESIZE FAILIFTHERE fastlink fcharset -FDEA -fdw -FECF -FEEF -fesb -FFAF -ffd -FFDE -FFFD -FFFDb fgbg FGCOLOR FGHIJ @@ -630,7 +583,6 @@ FINDDLG FINDDOWN FINDREGEX FINDSTRINGEXACT -FINDUP FITZPATRICK FIXEDFILEINFO Flg @@ -665,7 +617,6 @@ Ftm Fullscreens Fullwidth FUNCTIONCALL -fuzzer fuzzmain fuzzmap fuzzwrapper @@ -729,6 +680,7 @@ GETWHEELSCROLLCHARS GETWHEELSCROLLLINES Gfun gfx +gfycat GGI GHgh GHIJK @@ -749,6 +701,7 @@ Greyscale gridline gset gsl +Guake guc GUIDATOM GValue @@ -778,7 +731,6 @@ hfind hfont hfontresource hglobal -hhh hhook hhx HIBYTE @@ -794,7 +746,6 @@ HKCU hkey hkl HKLM -hlocal hlsl HMB HMK @@ -813,7 +764,6 @@ HREDRAW hresult hscroll hstr -hstring HTBOTTOMLEFT HTBOTTOMRIGHT HTCAPTION @@ -842,14 +792,12 @@ idl idllib IDOK IDR -idth IDTo IDXGI IFACEMETHODIMP ification IGNORELANGUAGE iid -IInput IIo ILC ILCo @@ -863,20 +811,18 @@ INFOEX inheritcursor INITCOMMONCONTROLSEX INITDIALOG -initguid +INITGUID INITMENU inkscape INLINEPREFIX inproc Inputkeyinfo -inputpaneinterop Inputreadhandledata INPUTSCOPE INSERTMODE INTERACTIVITYBASE INTERCEPTCOPYPASTE INTERNALNAME -Interner intsafe INVALIDARG INVALIDATERECT @@ -888,8 +834,6 @@ itermcolors itf Ith IUI -IUnknown -ivalid IWIC IXP jconcpp @@ -913,7 +857,6 @@ keydowns KEYFIRST KEYLAST Keymapping -keyscan keystate keyups Kickstart @@ -923,10 +866,6 @@ kinda KIYEOK KLF KLMNO -KLMNOPQRST -KLMNOPQRSTQQQQQ -KLMNOPQRSTUVWXY -KLMNOPQRSTY KOK KPRIORITY KVM @@ -948,6 +887,8 @@ LCONTROL LCTRL lcx LEFTALIGN +libsancov +libtickit LIMITTEXT LINEDOWN LINESELECTION @@ -1052,6 +993,7 @@ MBUTTONDOWN MBUTTONUP mdmerge MDs +mdtauk MEASUREITEM megamix memallocator @@ -1061,7 +1003,6 @@ MENUCONTROL MENUDROPALIGNMENT MENUITEMINFO MENUSELECT -messageext metaproj Mgrs microsoftpublicsymbols @@ -1078,11 +1019,9 @@ minwindef MMBB mmcc MMCPL -mmsystem MNC MNOPQ MNOPQR -MNOPQRSTUVWXY MODALFRAME MODERNCORE MONITORINFO @@ -1094,7 +1033,6 @@ MOUSEHWHEEL MOVESTART msb msbuildcache -msctf msctls msdata MSDL @@ -1109,10 +1047,9 @@ MSGSELECTMODE msiexec MSIL msix -msrc +MSRC MSVCRTD MTSM -Munged murmurhash muxes myapplet @@ -1151,7 +1088,6 @@ Newtonsoft NEXTLINE nfe NLSMODE -nnn NOACTIVATE NOAPPLYNOW NOCLIP @@ -1204,40 +1140,31 @@ NPFS nrcs NSTATUS ntapi -ntcon -ntcsrdll ntdef NTDEV ntdll ntifs -ntlpcapi ntm -ntrtl ntstatus nttree -nturtl ntuser NTVDM -ntverp nugetversions NUKTA nullness nullonfailure nullopts -numlock NUMSCROLL NUnit nupkg NVIDIA NVT OACR -objbase ocolor oemcp OEMFONT OEMFORMAT OEMs -offboarded OLEAUT OLECHAR onebranch @@ -1287,7 +1214,6 @@ PALPC pankaj parentable PATCOPY -pathcch PATTERNID pbstr pcb @@ -1342,7 +1268,6 @@ phicon phwnd pidl PIDLIST -pids pii piml pimpl @@ -1367,11 +1292,9 @@ POINTERUPDATE POINTSLIST policheck POLYTEXTW -poppack POPUPATTR popups PORFLG -positionals POSTCHARBREAKS POSX POSXSCROLL @@ -1404,11 +1327,9 @@ PREVLINE prg pri prioritization -processenv processhost PROCESSINFOCLASS PRODEXT -Productize PROPERTYID PROPERTYKEY propertyval @@ -1420,16 +1341,13 @@ propsys PROPTITLE propvar propvariant -propvarutil psa PSCRED PSECURITY pseudoconsole -pseudoterminal psh pshn PSHNOTIFY -pshpack PSINGLE psl psldl @@ -1501,8 +1419,6 @@ REGISTERVDM regkey REGSTR RELBINPATH -remoting -renamer rendersize reparented reparenting @@ -1537,19 +1453,16 @@ RIGHTALIGN RIGHTBUTTON riid ris -roadmap robomac rodata rosetta RRF -rrr RRRGGGBB rsas rtcore RTEXT RTLREADING Rtn -ruleset runas RUNDLL runformat @@ -1567,7 +1480,6 @@ rvpa RWIN rxvt safemath -sancov sba SBCS SBCSDBCS @@ -1595,10 +1507,8 @@ SCROLLSCREENBUFFER scursor sddl SDKDDK -securityappcontainer segfault SELCHANGE -SELECTALL SELECTEDFONT SELECTSTRING Selfhosters @@ -1642,14 +1552,10 @@ SFUI sgr SHCo shcore -shellapi shellex -shellscalingapi SHFILEINFO SHGFI SHIFTJIS -shlguid -shlobj shlwapi SHORTPATH SHOWCURSOR @@ -1687,7 +1593,6 @@ snapcy snk SOLIDBOX Solutiondir -somefile sourced SRCAND SRCCODEPAGE @@ -1719,9 +1624,8 @@ STDEXT STDMETHODCALLTYPE STDMETHODIMP STGM -Stringable STRINGTABLE -strsafe +STRSAFE STUBHEAD STUVWX stylecop @@ -1747,32 +1651,27 @@ SYSLIB SYSLINK SYSMENU sysparams -sysparamsext SYSTEMHAND SYSTEMMENU SYSTEMTIME tabview -TAdd taef TARG targetentrypoint TARGETLIBS TARGETNAME targetver -TBase tbc tbi Tbl TBM -tchar +TCHAR TCHFORMAT TCI tcommands tdbuild Tdd -TDelegated TDP -tearoff Teb Techo tellp @@ -1782,7 +1681,6 @@ terminalinput terminalrenderdata TERMINALSCROLLING terminfo -TEs testcon testd testenvs @@ -1794,7 +1692,6 @@ TESTNULL testpass testpasses TEXCOORD -TExpected textattribute TEXTATTRIBUTEID textboxes @@ -1805,39 +1702,28 @@ TEXTMETRIC TEXTMETRICW textmode texttests -TFunction THUMBPOSITION THUMBTRACK -tickit -TIcon tilunittests titlebars TITLEISLINKNAME -TJson -TLambda TLDP TLEN TMAE TMPF -TMult tmultiple -TODOs tofrom -tokenhelpers toolbars TOOLINFO TOOLWINDOW TOPDOWNDIB -TOpt tosign -touchpad tracelogging traceviewpp trackbar trackpad transitioning Trd -TREX triaged triaging TRIMZEROHEADINGS @@ -1845,9 +1731,7 @@ trx tsa tsgr tsm -TStr TSTRFORMAT -TSub TTBITMAP TTFONT TTFONTLIST @@ -1856,7 +1740,6 @@ TTo tvpp tvtseq TYUI -UAC uap uapadmin UAX @@ -1892,7 +1775,6 @@ unk unknwn UNORM unparseable -Unregistering untextured UPDATEDISPLAY UPDOWN @@ -1910,7 +1792,6 @@ USEFILLATTRIBUTE USEGLYPHCHARS USEHICON USEPOSITION -USERDATA userdpiapi Userp userprivapi @@ -1923,7 +1804,6 @@ USRDLL utext utr UVWXY -UVWXYZ uwa uwp uwu @@ -1932,17 +1812,16 @@ Vanara vararg vclib vcxitems -vectorize VERCTRL VERTBAR VFT vga vgaoem viewkind -viewports VIRAMA Virt VIRTTERM +visualstudiosdk vkey VKKEYSCAN VMs @@ -2001,7 +1880,6 @@ wekyb wewoad wex wextest -wextestclass WFill wfopen WHelper @@ -2012,9 +1890,7 @@ Wiggum wil WImpl WINAPI -winbase winbasep -wincodec wincon winconp winconpty @@ -2029,10 +1905,8 @@ windll WINDOWALPHA windowdpiapi WINDOWEDGE -windowext WINDOWINFO windowio -windowmetrics WINDOWPLACEMENT windowpos WINDOWPOSCHANGED @@ -2040,20 +1914,15 @@ WINDOWPOSCHANGING windowproc windowrect windowsapp -windowsinternalstring WINDOWSIZE windowsshell windowsterminal -windowsx windowtheme winevent -wingdi winget wingetcreate WINIDE -winioctl winmd -winmeta winmgr winmm WINMSAPP @@ -2063,8 +1932,8 @@ WInplace winres winrt winternl +winui winuser -winuserp WINVER wistd wmain @@ -2101,10 +1970,9 @@ WRITECONSOLEINPUT WRITECONSOLEOUTPUT WRITECONSOLEOUTPUTSTRING wrkstr -wrl +WRL wrp WRunoff -wsl WSLENV wstr wstrings @@ -2117,7 +1985,7 @@ wtof WTs WTSOFTFONT wtw -wtypes +Wtypes WUX WVerify WWith @@ -2136,7 +2004,6 @@ XBUTTONDOWN XBUTTONUP XCast XCENTER -xchar xcopy XCount xdy @@ -2146,6 +2013,7 @@ XFG XFile XFORM XIn +xkcd XManifest XMath XNamespace @@ -2172,7 +2040,6 @@ YLimit YPan YSubstantial YVIRTUALSCREEN -Zab zabcd Zabcdefghijklmn Zabcdefghijklmnopqrstuvwxyz @@ -2181,4 +2048,3 @@ ZCtrl ZWJs ZYXWVU ZYXWVUTd -zzf diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns index 5617a8af5f..ecfead2f0c 100644 --- a/.github/actions/spelling/line_forbidden.patterns +++ b/.github/actions/spelling/line_forbidden.patterns @@ -8,6 +8,24 @@ # you might not want to check in code where you skip all the other tests. #\bfit\( +# English does not use a hyphen between adverbs and nouns +# https://twitter.com/nyttypos/status/1894815686192685239 +(?:^|\s)[A-Z]?[a-z]+ly-(?=[a-z]{3,})(?:[.,?!]?\s|$) + +# Don't use `requires that` + `to be` +# https://twitter.com/nyttypos/status/1894816551435641027 +\brequires that \w+\b[^.]+to be\b + +# A fully parenthetical sentence’s period goes inside the parentheses, not outside. +# https://twitter.com/nyttypos/status/1898844061873639490 +\([A-Z][a-z]{2,}(?: [a-z]+){3,}\)\.\s + +# Complete sentences shouldn't be in the middle of another sentence as a parenthetical. +(? In formal writing and where contractions are frowned upon, use `cannot`. # > It is possible to write `can not`, but you generally find it only as part of some other construction, such as `not only . . . but also.` # - if you encounter such a case, add a pattern for that case to patterns.txt. -\b[Cc]an not\b +\b[Cc]an not\b(?! only\b) + +# Should be `chart` +(?i)\bhelm\b.*\bchard\b # Do not use `(click) here` links # For more information, see: @@ -56,9 +111,27 @@ # * https://heyoka.medium.com/dont-use-click-here-f32f445d1021 (?i)(?:>|\[)(?:(?:click |)here|link|(?:read |)more)(?:> /etc/apt/sources.list.d/something-distro.list +# ```` +\bapt-key add\b + +# Should be `nearby` +\bnear by\b + # Should probably be a person named `Nick` or the abbreviation `NIC` \bNic\b @@ -153,7 +260,7 @@ \bperform it's\b # Should be `opt-in` -#(? below for the` +(?i)\bfind below the\b + +# Should be `then any` unless there's a comparison before the `,` +, than any\b + # Should be `did not exist` \bwere not existent\b @@ -197,9 +357,18 @@ # Should be `nonexistent` \b[Nn]o[nt][- ]existent\b +# Should be `our` +\bspending out time\b + # Should be `@brief` / `@details` / `@param` / `@return` / `@retval` (?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b +# Should be `more than` or `more, then` +\bmore then\b + +# Should be `Pipeline`/`pipeline` +(?:(?<=\b|[A-Z])p|P)ipeLine(?:\b|(?=[A-Z])) + # Should be `preexisting` [Pp]re[- ]existing @@ -224,14 +393,27 @@ # Should be `reentrant` [Rr]e[- ]entrant +# Should be `room for` +\brooms for (?!lease|rent|sale) + +# Should be `socioeconomic` +# https://dictionary.cambridge.org/us/dictionary/english/socioeconomic +socio-economic + # Should be `strong suit` \b(?:my|his|her|their) strong suite\b +# Should probably be `temperatures` unless actually talking about thermal drafts (things birds may fly on) +\bthermals\b + +# Should be `there are` or `they are` (or `they're`) +(?i)\btheir are\b + # Should be `understand` \bunder stand\b -# Should be `URI` or `uri` unless it refers to a person named `Uri` -#(?|".*?") + +# hit-count: 1131 file-count: 326 +# C# includes +^\s*using [^;]+; + +# hit-count: 128 file-count: 47 # C network byte conversions (?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\() -# hit-count: 59 file-count: 36 -# IServiceProvider / isAThing -(?:\b|_)(?:(?:ns|)I|isA)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) +# hit-count: 53 file-count: 10 +# ANSI color codes +(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m -# hit-count: 9 file-count: 6 +# hit-count: 45 file-count: 29 +# version suffix v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) + +# hit-count: 30 file-count: 19 +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b + +# hit-count: 17 file-count: 7 +# File extensions +\*\.[+\w]+, + +# hit-count: 15 file-count: 9 # Markdown anchor links \(#\S*?[a-zA-Z]\S*?\) -# hit-count: 5 file-count: 5 -# libraries -(?:\b|_)lib(?:re(?=office)|)(?!era[lt]|ero|ert(?:ies|y)|rar(?:i(?:an|es)|y))(?=[a-z]) +# hit-count: 14 file-count: 8 +# hex runs +\b[0-9a-fA-F]{16,}\b + +# hit-count: 12 file-count: 8 +# hex digits including css/html color classes: +(?:[\\0][xX]|\\u|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b + +# hit-count: 12 file-count: 7 +# tar arguments +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ + +# hit-count: 9 file-count: 6 +# Repeated letters +\b([a-z])\g{-1}{2,}\b + +# hit-count: 8 file-count: 2 +# regex choice +\(\?:[^)]+\|[^)]+\) + +# hit-count: 8 file-count: 1 +# latex (check-spelling >= 0.0.22) +\\\w{2,}\{ + +# hit-count: 7 file-count: 4 +# Python string prefix / binary prefix +# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings +(?)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|"'`=(])-(?:D(?=[A-Z])|W(?!ork)|X|f(?=[ms]))(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) - -# hit-count: 60 file-count: 35 -# version suffix v# -(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) - -# hit-count: 2 file-count: 2 -# This does not cover multiline strings, if your repository has them, -# you'll want to remove the `(?=.*?")` suffix. -# The `(?=.*?")` suffix should limit the false positives rate -# printf -%(?:s)(?!ize)(?=[a-z]{2,}) - -# hit-count: 16 file-count: 10 -# uuid: -\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b - -# hit-count: 13 file-count: 4 -# Non-English -[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,} - -# hit-count: 7 file-count: 5 -# hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b - -# hit-count: 7 file-count: 1 -# regex choice -\(\?:[^)]+\|[^)]+\) - -# hit-count: 4 file-count: 4 -# tar arguments -\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ - -# hit-count: 4 file-count: 1 -# ANSI color codes -(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m - -# hit-count: 4 file-count: 1 -# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally ) -# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review - -# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into: -## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary). -## You could manually change `(?i)X...` to use `[Xx]...` -## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path) -# Lorem -(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])* - -# hit-count: 3 file-count: 3 -# mailto urls -mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} - -# hit-count: 2 file-count: 1 -# Python string prefix / binary prefix -# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings -(?= 0.0.22) -\\\w{2,}\{ - -# hit-count: 1 file-count: 1 -# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... -\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b - -# Questionably acceptable forms of `in to` -# Personally, I prefer `log into`, but people object -# https://www.tprteaching.com/log-into-log-in-to-login/ -\b(?:[Ll]og|[Ss]ign) in to\b - -# to opt in -\bto opt in\b # Questionably acceptable forms of `in to` # Personally, I prefer `log into`, but people object @@ -183,6 +210,10 @@ mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} # to opt in \bto opt in\b + +# pass(ed|ing) in +\bpass(?:ed|ing) in\b + # acceptable duplicates # ls directory listings [-bcdlpsw](?:[-r][-w][-SsTtx]){3}[\.+*]?\s+\d+\s+\S+\s+\S+\s+[.\d]+(?:[KMGT]|)\s+ diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt index d98038f96f..a4022cd4b4 100644 --- a/.github/actions/spelling/reject.txt +++ b/.github/actions/spelling/reject.txt @@ -1,10 +1,19 @@ ^attache$ ^attacher$ ^attachers$ -^bellow$ +^bellow?$ benefitting occurences? ^dependan.* +^develope$ +^developement$ +^developpe +^Devers?$ +^devex +^devide +^Devinn?[ae] +^devisal +^devisor ^diables?$ ^oer$ Sorce @@ -12,4 +21,5 @@ Sorce ^Teh$ ^untill$ ^untilling$ +^venders?$ ^wether.* diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index 7675d5d004..f01ac1eea5 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -93,7 +93,7 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }} checkout: true @@ -114,34 +114,33 @@ jobs: cspell:software-terms/softwareTerms.txt cspell:cpp/stdlib-cpp.txt cspell:cpp/stdlib-c.txt - cspell:lorem-ipsum/dictionary.txt + cspell:python/python/python-lib.txt cspell:php/php.txt + cspell:node/node.txt + cspell:dart/dart.txt cspell:filetypes/filetypes.txt cspell:java/java.txt - cspell:node/node.txt - cspell:golang/go.txt - cspell:java/java-terms.txt - cspell:mnemonics/mnemonics.txt + cspell:css/css.txt + cspell:dotnet/dotnet.txt cspell:npm/npm.txt cspell:fullstack/fullstack.txt - cspell:python/python/python-lib.txt - cspell:dotnet/dotnet.txt - cspell:dart/dart.txt - cspell:aws/aws.txt - cspell:python/common/extra.txt - cspell:css/css.txt + cspell:java/java-terms.txt + cspell:r/r.txt + cspell:golang/go.txt cspell:cpp/stdlib-cmath.txt cspell:typescript/typescript.txt + cspell:html/html.txt cspell:cpp/compiler-msvc.txt cspell:django/django.txt - cspell:html/html.txt - cspell:cpp/lang-keywords.txt + cspell:aws/aws.txt + cspell:python/common/extra.txt cspell:cpp/ecosystem.txt - cspell:r/r.txt - cspell:cpp/compiler-clang-attributes.txt - cspell:powershell/powershell.txt + cspell:cpp/lang-keywords.txt cspell:csharp/csharp.txt + cspell:cpp/compiler-clang-attributes.txt cspell:python/python/python.txt + cspell:mnemonics/mnemonics.txt + cspell:powershell/powershell.txt comment-push: name: Report (Push) @@ -154,7 +153,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' steps: - name: comment - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: checkout: true spell_check_this: microsoft/terminal@main @@ -172,7 +171,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: checkout: true spell_check_this: microsoft/terminal@main @@ -198,7 +197,7 @@ jobs: cancel-in-progress: false steps: - name: apply spelling updates - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} checkout: true diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 5dda1dc89c..4adf3837b1 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -1,4 +1,4 @@ -name: Publish to Winget +name: Publish to WinGet on: release: diff --git a/build/Helix/HelixTestHelpers.cs b/build/Helix/HelixTestHelpers.cs index 67f13937c0..cb8591031e 100644 --- a/build/Helix/HelixTestHelpers.cs +++ b/build/Helix/HelixTestHelpers.cs @@ -28,7 +28,7 @@ namespace HelixTestHelpers public List Screenshots { get; private set; } public List RerunResults { get; private set; } } - + // // Azure DevOps doesn't currently provide a way to directly report sub-results for tests that failed at least once // that were run multiple times. To get around that limitation, we'll mark the test as "Skip" since @@ -49,46 +49,46 @@ namespace HelixTestHelpers // TODO (https://github.com/dotnet/arcade/issues/2773): Once we're able to directly report things in a // more granular fashion than just a binary pass/fail result, we should do that. // - [DataContract] + [DataContract] internal class JsonSerializableTestResults - { + { [DataMember] internal string blobPrefix; - + [DataMember] internal string blobSuffix; - + [DataMember] internal string[] errors; - + [DataMember] internal JsonSerializableTestResult[] results; } - - [DataContract] - internal class JsonSerializableTestResult + + [DataContract] + internal class JsonSerializableTestResult { [DataMember] internal string outcome; [DataMember] internal int duration; - + [DataMember(EmitDefaultValue = false)] internal string log; - + [DataMember(EmitDefaultValue = false)] internal string[] screenshots; - + [DataMember(EmitDefaultValue = false)] internal int errorIndex; } - + public class TestPass { public TimeSpan TestPassExecutionTime { get; set; } public List TestResults { get; set; } - + public static TestPass ParseTestWttFile(string fileName, bool cleanupFailuresAreRegressions, bool truncateTestNames) { using (var stream = File.OpenRead(fileName)) @@ -174,7 +174,7 @@ namespace HelixTestHelpers if (testsExecuting == 1) { string testName = element.Attribute("Title").Value; - + if (truncateTestNames) { const string xamlNativePrefix = "Windows::UI::Xaml::Tests::"; @@ -243,7 +243,7 @@ namespace HelixTestHelpers // The test cleanup errors will often come after the test claimed to have - // 'passed'. We treat them as errors as well. + // 'passed'. We treat them as errors as well. if (inTestCleanup) { currentResult.CleanupPassed = false; @@ -292,7 +292,7 @@ namespace HelixTestHelpers foreach(var screenshot in screenshots) { string fileNameSuffix = string.Empty; - + if (fileName.Contains("_rerun_multiple")) { fileNameSuffix = "_rerun_multiple"; @@ -301,7 +301,7 @@ namespace HelixTestHelpers { fileNameSuffix = "_rerun"; } - + currentResult.Screenshots.Add(screenshot.Replace(".jpg", fileNameSuffix + ".jpg")); } } @@ -313,7 +313,7 @@ namespace HelixTestHelpers testPassStopTime = Int64.Parse(doc.Root.Descendants("WexTraceInfo").Last().Attribute("TimeStamp").Value); var testPassTime = TimeSpan.FromSeconds((double)(testPassStopTime - testPassStartTime) / frequency); - + foreach (TestResult testResult in testResults) { if (testResult.Details != null) @@ -331,13 +331,13 @@ namespace HelixTestHelpers return testpass; } } - + public static TestPass ParseTestWttFileWithReruns(string fileName, string singleRerunFileName, string multipleRerunFileName, bool cleanupFailuresAreRegressions, bool truncateTestNames) { TestPass testPass = ParseTestWttFile(fileName, cleanupFailuresAreRegressions, truncateTestNames); TestPass singleRerunTestPass = File.Exists(singleRerunFileName) ? ParseTestWttFile(singleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null; TestPass multipleRerunTestPass = File.Exists(multipleRerunFileName) ? ParseTestWttFile(multipleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null; - + List rerunTestResults = new List(); if (singleRerunTestPass != null) @@ -377,9 +377,9 @@ namespace HelixTestHelpers public static void OutputFailedTestQuery(string wttInputPath) { var testPass = TestPass.ParseTestWttFile(wttInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false); - + List failedTestNames = new List(); - + foreach (var result in testPass.TestResults) { if (!result.Passed) @@ -387,23 +387,23 @@ namespace HelixTestHelpers failedTestNames.Add(result.Name); } } - + if (failedTestNames.Count > 0) { string failedTestSelectQuery = "(@Name='"; - + for (int i = 0; i < failedTestNames.Count; i++) { failedTestSelectQuery += failedTestNames[i]; - + if (i < failedTestNames.Count - 1) { failedTestSelectQuery += "' or @Name='"; } } - + failedTestSelectQuery += "')"; - + Console.WriteLine(failedTestSelectQuery); } else @@ -418,7 +418,7 @@ namespace HelixTestHelpers private string testNamePrefix; private string helixResultsContainerUri; private string helixResultsContainerRsas; - + public TestResultParser(string testNamePrefix, string helixResultsContainerUri, string helixResultsContainerRsas) { this.testNamePrefix = testNamePrefix; @@ -430,7 +430,7 @@ namespace HelixTestHelpers { Dictionary subResultsJsonByMethod = new Dictionary(); TestPass testPass = TestPass.ParseTestWttFileWithReruns(wttInputPath, wttSingleRerunInputPath, wttMultipleRerunInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false); - + foreach (var result in testPass.TestResults) { var methodName = result.Name.Substring(result.Name.LastIndexOf('.') + 1); @@ -488,7 +488,7 @@ namespace HelixTestHelpers int resultCount = results.Count; int passedCount = results.Where(r => r.Passed).Count(); - + // Since we re-run tests on failure, we'll mark every test that failed at least once as "skipped" rather than "failed". // If the test failed sufficiently often enough for it to count as a failed test (determined by a property on the // Azure DevOps job), we'll later mark it as failed during test results processing. @@ -504,15 +504,15 @@ namespace HelixTestHelpers assembly.SetAttributeValue("run-date", DateTime.Now.ToString("yyyy-MM-dd")); // This doesn't need to be completely accurate since it's not exposed anywhere. - // If we need accurate an start time we can probably calculate it from the te.wtl file, but for + // If we need an accurate start time we can probably calculate it from the te.wtl file, but for // now this is fine. assembly.SetAttributeValue("run-time", (DateTime.Now - testPass.TestPassExecutionTime).ToString("hh:mm:ss")); - + assembly.SetAttributeValue("total", resultCount); assembly.SetAttributeValue("passed", passedCount); assembly.SetAttributeValue("failed", failedCount); assembly.SetAttributeValue("skipped", skippedCount); - + assembly.SetAttributeValue("time", (int)testPass.TestPassExecutionTime.TotalSeconds); assembly.SetAttributeValue("errors", 0); root.Add(assembly); @@ -537,9 +537,9 @@ namespace HelixTestHelpers test.SetAttributeValue("method", methodName); test.SetAttributeValue("time", result.ExecutionTime.TotalSeconds); - + string resultString = string.Empty; - + if (result.Passed && !result.Skipped) { resultString = "Pass"; @@ -554,7 +554,7 @@ namespace HelixTestHelpers resultString = "Fail"; } - + if (!result.Passed) { if (result.Skipped) @@ -579,36 +579,36 @@ namespace HelixTestHelpers File.WriteAllText(xunitOutputPath, root.ToString()); } - + private JsonSerializableTestResult ConvertToSerializableResult(TestResult rerunResult, string[] uniqueErrors) { var serializableResult = new JsonSerializableTestResult(); - + serializableResult.outcome = rerunResult.Passed ? "Passed" : "Failed"; serializableResult.duration = (int)Math.Round(rerunResult.ExecutionTime.TotalMilliseconds); - + if (!rerunResult.Passed) { serializableResult.log = Path.GetFileName(rerunResult.SourceWttFile); - + if (rerunResult.Screenshots.Any()) { List screenshots = new List(); - + foreach (var screenshot in rerunResult.Screenshots) { screenshots.Add(Path.GetFileName(screenshot)); } - + serializableResult.screenshots = screenshots.ToArray(); } - + // To conserve space, we'll log the index of the error to index in a list of unique errors rather than // jotting down every single error in its entirety. We'll add one to the result so we can avoid // serializing this property when it has the default value of 0. serializableResult.errorIndex = Array.IndexOf(uniqueErrors, rerunResult.Details) + 1; } - + return serializableResult; } @@ -617,7 +617,7 @@ namespace HelixTestHelpers var filename = Path.GetFileName(filePath); return string.Format("{0}/{1}{2}", helixResultsContainerUri, filename, helixResultsContainerRsas); } - + private string GetTestNameSeparator(string testname) { var separatorString = "."; diff --git a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml index e30e23b88c..513e5c5007 100644 --- a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml +++ b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml @@ -30,7 +30,7 @@ jobs: steps: - checkout: self clean: true - # It is important that this be 0, otherwise git will not fetch the branch ref names that the PGO rules require. + # It is important that this be 0; otherwise, git will not fetch the branch ref names that the PGO rules require. fetchDepth: 0 submodules: false persistCredentials: false diff --git a/doc/Niksa.md b/doc/Niksa.md index ec6af1f146..d9d82fe39d 100644 --- a/doc/Niksa.md +++ b/doc/Niksa.md @@ -15,7 +15,7 @@ This document serves as a storage point for those posts. ## Why do we avoid changing CMD.exe? `setlocal` doesn't behave the same way as an environment variable. It's a thing that would have to be put in at the top of the batch script that is `somefile.cmd` as one of its first commands to adjust the way that one specific batch file is processed by the `cmd.exe` engine. That's probably not suitable for your needs, but that's the way we have to go. -I don't think anyone is disagreeing with you, @mikemaccana, that this would be a five minute development change to read that environment variable and change the behavior of `cmd.exe`. It absolutely would be a tiny development time. +I don't think anyone is disagreeing with you, @mikemaccana, that this would be a five minute development change to read that environment variable and change the behavior of `cmd.exe`. It absolutely would be a tiny development time. It's just that from our experience, we know there's going to be a 3-24 month bug tail here where we get massive investigation callbacks by some billion dollar enterprise customer who for whatever reason was already using the environment variable we pick for another purpose. Their script that they give their rank-and-file folks will tell them to press Ctrl+C at some point in the batch script to do whatever happens, it will do something different, those people will notice the script doesn't match the computer anymore. They will then halt the production line and tell their supervisor. The supervisor tells some director. Their director comes screaming at their Microsoft enterprise support contract person that we've introduced a change to the OS that is costing them millions if not billions of dollars in shipments per month. Our directors at Microsoft then come bashing down our doors angry with us and make us fix it ASAP or revert it, we don't get to go home at 5pm to our families or friends because we're fixing it, we get stressed the heck out, we have to spin up servicing potentially for already shipped operating systems which is expensive and headache-causing...etc. @@ -27,7 +27,7 @@ I would highly recommend that Gulp convert to using PowerShell scripts and that Original Source: https://github.com/microsoft/terminal/issues/217#issuecomment-404240443 -_Addendum_: cmd.exe is the literal embodiment of [xkcd#1172]([url](https://xkcd.com/1172/)). Every change, no matter how small, will break _someone_. +_Addendum_: cmd.exe is the literal embodiment of [xkcd#1172]([url](https://xkcd.com/1172/)). Every change, no matter how small, will break _someone_. ## Why is typing-to-screen performance better than every other app? @@ -37,33 +37,33 @@ Also, I'm happy to discuss this with you until you're utterly sick of reading it If I had to take an educated guess as to what is making us faster than pretty much any other application on Windows at putting your text on the screen... I would say it is because that is literally our only job! Also probably because we are using darn near the oldest and lowest level APIs that Windows has to accomplish this work. -Pretty much everything else you've listed has some sort of layer or framework involved, or many, many layers and frameworks, when you start talking about Electron and JavaScript. We don't. +Pretty much everything else you've listed has some sort of layer or framework involved, or many, many layers and frameworks, when you start talking about Electron and JavaScript. We don't. -We have one bare, super un-special window with no additional controls attached to it. We get our keys fed into us from just barely above the kernel given that we're processing them from window messages and not from some sort of eventing framework common to pretty much any other more complicated UI framework than ours (WPF, WinForms, UWP, Electron). And we dump our text straight onto the window surface using GDI's [PolyTextOut](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-polytextoutw) with no frills. +We have one bare, super un-special window with no additional controls attached to it. We get our keys fed into us from just barely above the kernel given that we're processing them from window messages and not from some sort of eventing framework common to pretty much any other more complicated UI framework than ours (WPF, WinForms, UWP, Electron). And we dump our text straight onto the window surface using GDI's [PolyTextOut](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-polytextoutw) with no frills. Even `notepad.exe` has multiple controls on its window at the very least and is probably (I haven't looked) using some sort of library framework in the edit control to figure out its text layout (which probably is using another library framework for internationalization support...) -Of course this also means that we have trade offs. We don't support fully international text like pretty much every other application will. RTL? No go zone right now. Surrogate pairs and emoji? We're getting there but not there yet. Indic scripts? Nope. +Of course this also means that we have trade offs. We don't support fully international text like pretty much every other application will. RTL? No go zone right now. Surrogate pairs and emoji? We're getting there but not there yet. Indic scripts? Nope. Why are we like this? For one, `conhost.exe` is old as dirt. It has to use the bare metal bottom layer of everything because it was created before most of those other frameworks were created. And also it maintains as low/bottom level as possible because it is pretty much the first thing that one needs to bring up when bringing up a new operating system edition or device before you have all the nice things like frameworks or what those frameworks require to operate. Also it's written in C/C++ which is about as low and bare metal as we can get. -Will this UI enhancement come to other apps on Windows? Almost certainly not. They have too much going on which is both a good and a bad thing. I'm jealous of their ability to just call one method and layout text in an uncomplicated manner in any language without manually calculating pixels or caring about what styles apply to their font. But my manual pixel calculations, dirty region math, scroll region madness, and more makes it so we go faster than them. I'm also jealous that when someone says "hey can you add a status bar to the bottom of your window" that they can pretty much click and drag that into place with their UI Framework and it will just work where as for us, it's been a backlog item forever and gives me heartburn to think about implementing. +Will this UI enhancement come to other apps on Windows? Almost certainly not. They have too much going on which is both a good and a bad thing. I'm jealous of their ability to just call one method and layout text in an uncomplicated manner in any language without manually calculating pixels or caring about what styles apply to their font. But my manual pixel calculations, dirty region math, scroll region madness, and more makes it so we go faster than them. I'm also jealous that when someone says "hey can you add a status bar to the bottom of your window" that they can pretty much click and drag that into place with their UI Framework and it will just work whereas for us, it's been a backlog item forever and gives me heartburn to think about implementing. -Will we try to keep it from regressing? Yes! Right now it's sort of a manual process. We identify that something is getting slow and then we go haul out [WPR](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) and start taking traces. We stare down the hot paths and try to reason out what is going on and then improve them. For instance, in the last cycle or two, we focused on heap allocations as a major area where we could improve our end-to-end performance, changing a ton of our code to use stack-constructed iterator-like facades over the underlying request buffer instead of translating and allocating it into a new heap space for each level of processing. +Will we try to keep it from regressing? Yes! Right now it's sort of a manual process. We identify that something is getting slow and then we go haul out [WPR](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) and start taking traces. We stare down the hot paths and try to reason out what is going on and then improve them. For instance, in the last cycle or two, we focused on heap allocations as a major area where we could improve our end-to-end performance, changing a ton of our code to use stack-constructed iterator-like facades over the underlying request buffer instead of translating and allocating it into a new heap space for each level of processing. As an aside, @bitcrazed wants us to automate performance tests in some conhost specific way, but I haven't quite figured out a controlled environment to do this in yet. The Windows Engineering System runs performance tests each night that give us a coarse-grained way of knowing if we messed something up for the whole operating system, and they technically offer a fine-grained way for us to insert our own performance tests... but I just haven't got around to that yet. If you have an idea for a way for us to do this in an automated fashion, I'm all ears. -If there's anything else you'd like to know, let me know. I could go on all day. I deleted like 15 tangents from this reply before posting it.... +If there's anything else you'd like to know, let me know. I could go on all day. I deleted like 15 tangents from this reply before posting it.... Original Source: https://github.com/microsoft/terminal/issues/327#issuecomment-447391705 ## How are the Windows graphics/messaging stack assembled? -@stakx, I am referring to USER32 and GDI32. +@stakx, I am referring to USER32 and GDI32. I'll give you a cursory overview of what I know off the top of my head without spending hours confirming the details. As such, some of this is subject to handwaving and could be mildly incorrect but is probably in the right direction. Consider every statement to be my personal knowledge on how the world works and subject to opinion or error. -For the graphics part of the pipeline (GDI32), the user-mode portions of GDI are pretty far down. The app calls GDI32, some work is done in that DLL on the user-mode side, then a kernel call jumps over to the kernel and drawing occurs. +For the graphics part of the pipeline (GDI32), the user-mode portions of GDI are pretty far down. The app calls GDI32, some work is done in that DLL on the user-mode side, then a kernel call jumps over to the kernel and drawing occurs. The portion that you're thinking of regarding "silently converted to sit on top of other stuff" is probably that once we hit the kernel calls, a bunch of the kernel GDI stuff tends to be re-platformed on top of the same stuff as DirectX when it is actually handled by the NVIDIA/AMD/Intel/etc. graphics driver and the GPU at the bottom of the stack. I think this happened with the graphics driver re-architecture that came as a part of WDDM for Windows Vista. There's a document out there somewhere about what calls are still really fast in GDI and which are slower as a result of the re-platforming. Last time I found that document and checked, we were using the fast ones. @@ -71,11 +71,11 @@ On top of GDI, I believe there are things like Common Controls or comctl32.dll w As for DirectWrite and D2D and D3D and DXGI themselves, they're a separate set of commands and paths that are completely off to the side from GDI at all both in user and kernel mode. They're not really related other than that there's some interoperability provisions between the two. Most of our other UI frameworks tend to be built on top of the DirectX stack though. XAML is for sure. I think WPF is. Not sure about WinForms. And I believe the composition stack and the window manager are using DirectX as well. -As for the input/interaction part of the pipeline (USER32), I tend to find most other newer things (at least for desktop PCs) are built on top of what is already there. USER32's major concept is windows and window handles and everything is sent to a window handle. As long as you're on a desktop machine (or a laptop or whatever... I mean a classic-style Windows-powered machine), there's a window handle involved and messages floating around and that means we're talking USER32. +As for the input/interaction part of the pipeline (USER32), I tend to find most other newer things (at least for desktop PCs) are built on top of what is already there. USER32's major concept is windows and window handles and everything is sent to a window handle. As long as you're on a desktop machine (or a laptop or whatever... I mean a classic-style Windows-powered machine), there's a window handle involved and messages floating around and that means we're talking USER32. -The window message queue is just a straight up FIFO (more or less) of whatever input has occurred relevant to that window while it's in the foreground + whatever has been sent to the window by other components in the system. +The window message queue is just a straight up FIFO (more or less) of whatever input has occurred relevant to that window while it's in the foreground + whatever has been sent to the window by other components in the system. -The newer technologies and the frameworks like XAML and WPF and WinForms tend to receive the messages from the window message queue one way or another and process them and turn them into event callbacks to various objects that they've provisioned within their world. +The newer technologies and the frameworks like XAML and WPF and WinForms tend to receive the messages from the window message queue one way or another and process them and turn them into event callbacks to various objects that they've provisioned within their world. However, the newer technologies that also work on other non-desktop platforms like XAML tend to have the ability to process stuff off of a completely different non-USER32 stack as well. There's a separate parallel stack to USER32 with all of our new innovations and realizations on how input and interaction should occur that doesn't exactly deal with classic messaging queues and window handles the same way. This is the whole Core* family of things like CoreWindow and CoreMessaging. They also have a different concept of "what is a user" that isn't so centric around your butt in rolling chair in front of a screen with a keyboard and mouse on the desk. @@ -83,7 +83,7 @@ Now, if you're on XAML or one of the other Frameworks... all this intricacy is h The trick is that GDI32 and USER32 were designed for a limited world with a limited set of commands. Desktop PCs were the only thing that existed, single user at the keyboard and mouse, simple graphics output to a VGA monitor. So using them directly at the "low level" like conhost does is pretty easy. The new platforms could be used at the "low level" but they're orders of magnitude more complicated because they now account for everything that has happened with personal computing in 20+ years like different form factors, multiple active users, multiple graphics adapters, and on and on and on and on. So you tend to use a framework when using the new stuff so your head doesn't explode. They handle it for you, but they handle more than they ever did before so they're slower to some degree. -So are GDI32 and USER32 "lower" than the new stuff? Sort of. +So are GDI32 and USER32 "lower" than the new stuff? Sort of. Can you get that low with the newer stuff? Mostly yes, but you probably shouldn't and don't want to. Does new live on top of old or is old replatformed on the new? Sometimes and/or partially. Basically... it's like the answer to anything software... "it's an unmitigated disaster and if we all stepped back a moment, we should be astounded that it works at all." :P @@ -94,7 +94,7 @@ Original Source: https://github.com/microsoft/terminal/issues/327#issuecomment-4 ## Output Processing between "Far East" and "Western" -> +> > ``` > if (WI_IsFlagSet(CharType, C1_CNTRL)) > ``` @@ -120,7 +120,7 @@ Note in both of these, there is a little bit of indirection before `MultiByteToW When we took over the console codebase, this variation between "Western" and "Eastern" countries was especially painful because `conhost.exe` would choose which one it was in based on the `Codepage for Non-Unicode Applications` set in the Control Panel's Regional > Administrative panel and it could only be changed with a reboot. It wouldn't even change properly when you `chcp` to a different codepage. Heck, `chcp` would deny you from switching into many codepages. There was a block in place to prevent going to an "Eastern" codepage if you booted up in a "Western" codepage. There was also a block preventing you from going between "Eastern" codepages, if I recall correctly. In modernizing, I decided a few things: -1. What's good for the "Far East" should be good for the rest of the world. CJK languages that encompassed the "Far East" code have to be able to handle "Western" text as well even if the reverse wasn't true. +1. What's good for the "Far East" should be good for the rest of the world. CJK languages that encompassed the "Far East" code have to be able to handle "Western" text as well even if the reverse wasn't true. 2. We need to scrub all usages of "Far East" from the code. Someone already started that and replaced them with "East Asia" except then they left behind the shorthand of "FE" prefixing dozens of functions which made it hard to follow the code. It took us months to realize "FE" and "East Asia" were the same thing. 3. It's obnoxious that the way this was handled was to literally double-define every output function in the code base to have two definitions, compile them both into the conhost, then choose to run down the SB_ versions or the FE_ versions depending on the startup Non-Unicode codepage. It was a massive pile of complex pre-compilation `#ifdef` and `#else`s that would sometimes surround individual lines in the function bodies. Gross. 4. The fact that the FE_ versions of the functions were way slower than the SB_ ones was unacceptable even for the same output of Latin-character text. @@ -139,13 +139,13 @@ Original Source: https://github.com/microsoft/terminal/issues/166#issuecomment-5 ## Why do we not backport things? -Someone has to prove that this is costing millions to billions of dollars of lost productivity or revenue to outweigh the risks of shipping the fix to hundreds of millions of Windows machines and potentially breaking something. +Someone has to prove that this is costing millions to billions of dollars of lost productivity or revenue to outweigh the risks of shipping the fix to hundreds of millions of Windows machines and potentially breaking something. -Our team generally finds it pretty hard to prove that against the developer audience given that they're only a small portion of the total installed market of Windows machines. +Our team generally finds it pretty hard to prove that against the developer audience given that they're only a small portion of the total installed market of Windows machines. Our only backport successes really come from corporations with massive addressable market (like OEMs shipping PCs) who complain that this is fouling up their manufacturing line (or something of that ilk). Otherwise, our management typically says that the risks don't outweigh the benefits. -It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk. +It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk. So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere every time we make a change. But that's just not how it is today. @@ -189,7 +189,7 @@ _guest speaker @zadjii-msft_ I think there might be a bit of a misunderstanding here - there are two different kinds of applications we're talking about here: * shell applications, like `cmd.exe`, `powershell`, `zsh`, etc. These are text-only applications that emit streams of characters. They don't care at all about how they're eventually rendered to the user. These are also sometimes referred to as "commandline client" applications. -* terminal applications, like the Windows Terminal, gnome-terminal, xterm, iterm2, hyper. These are graphical applications that can be used to render the output of commandline clients. +* terminal applications, like the Windows Terminal, gnome-terminal, xterm, iterm2, hyper. These are graphical applications that can be used to render the output of commandline clients. On Windows, if you just run `cmd.exe` directly, the OS will create an instance of `conhost.exe` as the _terminal_ for `cmd.exe`. The same thing happens for `powershell.exe`, the system will create a new conhost window for any client that's not already connected to a terminal of some sort. This has lead to an enormous amount of confusion for people thinking that a conhost window is actually a "`cmd` window". `cmd` can't have a window, it's just a commandline application. Its window is always some other terminal. diff --git a/doc/WindowsTestPasses.md b/doc/WindowsTestPasses.md index f733f98af5..1689bbb443 100644 --- a/doc/WindowsTestPasses.md +++ b/doc/WindowsTestPasses.md @@ -31,12 +31,12 @@ Prerequisites: 1. Right click the machine name in the `Device Manager` list and choose `Launch T-Shell`. You can also use `Connect via Console` to get a "remote desktop"-like session to the KVM port on the VM. 1. In T-shell, use `testd Microsoft.Console.TestLab.Desktop.testlist` or a command of that format with a different TESTLIST or TESTMD name from our project (see the [UniversalTest.md] documentation). The `testd` utility will automatically resolve the build/branch/flavor information, dig through the build shares for the matching TESTLIST/TESTMD metadata, and attempt to deploy all relevant packages and dependencies on the device. When it's successful, it will move onto running all the tests and giving you the results. On conclusion, the test results should pop up in the web browser or the `Hubble - Log Viewer` tool provided by the Engineering Systems team. -If some of the above things do not work, go to [https://osgwiki.com] and type them into the Search bar. For instance, if T-Shell isn't found or working, you can find out where to get it or download it on `OSGWiki`. The same goes for the other commands besides `testd` to use in T-shell and more information on what `Hubble` or `Nebula` are. +If some of the above things do not work, go to [https://osgwiki.com] and type them into the Search bar. For instance, if T-Shell isn't found or working, you can find out where to get it or download it on `OSGWiki`. The same goes for the other commands besides `testd` to use in T-shell and more information on what `Hubble` or `Nebula` are. Presumably now you have a failure. Or a success. You can attempt to spelunk the logs in `Hubble` and you might come to a conclusion. Or you can move onto debugging directly. -Now that you've relied on `testd` to get everything deployed and orchestrated and run once on the device, you can use `execd` to run things again or to run a smaller subset of things on the remote device through `T-Shell`. +Now that you've relied on `testd` to get everything deployed and orchestrated and run once on the device, you can use `execd` to run things again or to run a smaller subset of things on the remote device through `T-Shell`. -By default, in the `Universal Test` world, everything will be deployed onto the remote machine at `C:\data\test\bin`. In T-Shell, use `cdd C:\data\test\bin` to change to that directory and then `execd te.exe Microsoft.Console.Host.FeatureTests.dll /name:*TestReadFileEcho*` to run just one specific test. Of course you should substitute the file name and test name parameters as makes sense. And of course you can find out more about `cdd` and `execd` on the `T-shell` page of `OSGWiki`. +By default, in the `Universal Test` world, everything will be deployed onto the remote machine at `C:\data\test\bin`. In T-Shell, use `cdd C:\data\test\bin` to change to that directory and then `execd te.exe Microsoft.Console.Host.FeatureTests.dll /name:*TestReadFileEcho*` to run just one specific test. Of course, you should substitute the file name and test name parameters as makes sense. And of course you can find out more about `cdd` and `execd` on the `T-shell` page of `OSGWiki`. Fortunately, running things through `T-shell` in this fashion is exactly the same way that the testlab orchestrates the tests. If you still don't get good data this way, you can use the `Connect via Console` mechanism way above to try to run things under `WinDBG` or the `Visual Studio Remote Debugger` manually on the machine to get them to repro or under the debugger more completely. diff --git a/doc/creating_a_new_project.md b/doc/creating_a_new_project.md index 0f7f3bd880..2e17b097c6 100644 --- a/doc/creating_a_new_project.md +++ b/doc/creating_a_new_project.md @@ -6,7 +6,7 @@ When creating a new DLL, it was really helpful to reference an existing DLL's `. - [ ] Make sure to `` our pre props at the _top_ of the vcxproj, and our post props at the _bottom_ of the vcxproj. ``` - + @@ -35,7 +35,7 @@ DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE - _Note_: If your new library eventually rolls up as a reference to our Centennial Packaging project `CascadiaPackage`, you don't have to worry about manually adding your definitions to the `AppXManifest.xml` because the Centennial Packaging project automatically enumerates the reference tree of WinMDs and stitches that information into the `AppXManifest.xml`. However, if your new project does _not_ ultimately roll up to a packaging project that will automatically put the references into `AppXManifest`, you will have to add them in manually. ### Troubleshooting -- If you hit an error that looks like this: +- If you hit an error that looks like this: ``` X found processing metadata file ..\blah1\Microsoft.UI.Xaml.winmd, type already exists in file ..\blah\NewDLLProject\Microsoft.UI.Xaml.winmd. ``` @@ -51,4 +51,4 @@ DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE - If you hit a `Class not Registered` error, this might be because a class isn't getting registered in the app manifest. You can go check `src/cascadia/CascadiaPackage/bin/x64/Debug/AppX/AppXManifest.xml` to see if there exist entries to the classes of your newly created DLL. If the references aren't there, double check that you've added `` blocks to both `WindowsTerminal.vcxproj` and `TerminalApp.vcxproj`. -- If you hit an extremely vague error along the lines of `Error in the DLL`, and right before that line you notice that your new DLL is loaded and unloaded right after each other, double check that your new DLL's definitions show up in the `AppXManifest.xml` file. If your new DLL is included as a reference to a project that rolls up to `CascadiaPackage`, double check that you've created a `.def` file for the project. Otherwise if your new project _does not_ roll up to a package that populates the `AppXManifest` references for you, you'll have to add those references yourself. +- If you hit an extremely vague error along the lines of `Error in the DLL`, and right before that line you notice that your new DLL is loaded and unloaded right after each other, double check that your new DLL's definitions show up in the `AppXManifest.xml` file. If your new DLL is included as a reference to a project that rolls up to `CascadiaPackage`, double check that you've created a `.def` file for the project. Otherwise, if your new project _does not_ roll up to a package that populates the `AppXManifest` references for you, you'll have to add those references yourself. diff --git a/doc/specs/#1564 - Settings UI/cascading-settings.md b/doc/specs/#1564 - Settings UI/cascading-settings.md index 78d8fd7bbf..0c358f945e 100644 --- a/doc/specs/#1564 - Settings UI/cascading-settings.md +++ b/doc/specs/#1564 - Settings UI/cascading-settings.md @@ -9,7 +9,7 @@ issue id: 1564 ## Abstract -Windows Terminal's settings model adheres to a cascading settings architecture. This allows a settings object to be defined incrementally across multiple layers of declarations. The value for any global setting like `copyOnSelect`, for example, is set to your settings.json value if one is defined, otherwise defaults.json, and otherwise a system set value. Profiles in particular are more complicated in that they must also take into account the values in `profiles.defaults` and dynamic profile generators. +Windows Terminal's settings model adheres to a cascading settings architecture. This allows a settings object to be defined incrementally across multiple layers of declarations. The value for any global setting like `copyOnSelect`, for example, is set to your settings.json value if one is defined; otherwise, defaults.json, and otherwise a system set value. Profiles in particular are more complicated in that they must also take into account the values in `profiles.defaults` and dynamic profile generators. This spec explores how to represent this feature in the Settings UI. diff --git a/doc/specs/#1595 - Suggestions UI/Snippets.md b/doc/specs/#1595 - Suggestions UI/Snippets.md index f80a9cf52f..39cd15de8a 100644 --- a/doc/specs/#1595 - Suggestions UI/Snippets.md +++ b/doc/specs/#1595 - Suggestions UI/Snippets.md @@ -529,7 +529,7 @@ their own workflows. * `--local`: Save to the `.wt.json` in the CWD, if there is one (or create one) * `--parent`: Save to the `.wt.json` in the first ancestor of the CWD, if - there is one. Otherwise create one here. + there is one. Otherwise, create one here. * `--settings`: Manually save to the settings file? * `--profile`: save to this profile???? Not sure if this is actually possible. Maybe with the `WT_SESSION_ID` env var to figure out which profile is in use diff --git a/doc/specs/#1790 - Font features and axes-spec.md b/doc/specs/#1790 - Font features and axes-spec.md index 26cc5fd40a..bc524a4099 100644 --- a/doc/specs/#1790 - Font features and axes-spec.md +++ b/doc/specs/#1790 - Font features and axes-spec.md @@ -23,7 +23,7 @@ In a similar vein, many fonts allow for setting variations on the font along cer ### Font features -It is already possible to pass in a list of [font feature structs](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_font_feature) to DWrite for it to handle. A font feature struct contains only 2 things: +It is already possible to pass in a list of [font feature structs](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_font_feature) to DWrite for it to handle. A font feature struct contains only 2 things: 1. A font feature tag 2. A parameter value @@ -78,7 +78,7 @@ Aside from additional parsing required for the settings file (which inherently o ### Compatibility -Older versions of Windows may not have the DWrite updates that allow for defining font features and axes of variation. We must make sure to fallback to the current implementation in these cases. +Older versions of Windows may not have the DWrite updates that allow for defining font features and axes of variation. We must make sure to fall back to the current implementation in these cases. ### Performance, Power, and Efficiency @@ -102,4 +102,4 @@ We will also need to consider how we want to represent this in the settings UI. [DWRITE_FONT_FEATURE structure](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_font_feature) -[DWRITE_FONT_AXIS_VALUE structure](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/ns-dwrite_3-dwrite_font_axis_value) \ No newline at end of file +[DWRITE_FONT_AXIS_VALUE structure](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/ns-dwrite_3-dwrite_font_axis_value) diff --git a/doc/specs/#4066 - Theme-controlled color scheme switch.md b/doc/specs/#4066 - Theme-controlled color scheme switch.md index 9cb41b3571..ed40c9f42d 100644 --- a/doc/specs/#4066 - Theme-controlled color scheme switch.md +++ b/doc/specs/#4066 - Theme-controlled color scheme switch.md @@ -17,7 +17,7 @@ I work remotely as a developer, so I have to spend a lot of hours in front of my Normally I like dark modes in all the programs and apps I use, but when there's too much sunlight, it becomes annoying, and sometimes even painful, to work in dark mode. So, I have all the programs and apps I use (at least, those that can) set to switch their color themes to what the system has. -The company I work for sent me a Macbook Pro, and my personal phone is an Android, both with automatic dark mode at sunset and light mode at sunrise, and in those devices it's been working relatively well. In Windows, as it is known, there's no such feature, so I manually change between dark and light mode when it's needed, and most of the programs and apps I use go along with this change. Windows Terminal, is not one of them. +The company I work for sent me a MacBook Pro, and my personal phone is an Android, both with automatic dark mode at sunset and light mode at sunrise, and in those devices it's been working relatively well. In Windows, as it is known, there's no such feature, so I manually change between dark and light mode when it's needed, and most of the programs and apps I use go along with this change. Windows Terminal, is not one of them. The theme changes just as expected, but in an app like this, this change only affects the top of the window, leaving almost all of the screen at the mercy of what the color scheme is, and it doesn't depend on the theme, which defeats any attempt to make a good use of the `system` theme feature. diff --git a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md index 538da23725..625ef51e92 100644 --- a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md +++ b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md @@ -94,7 +94,7 @@ configurations: - `"useExisting"`: always glom to the most recent window, regardless of desktop. - `"useExistingOnSameDesktop"`: Only glom if there's an existing window on this - virtual desktop, otherwise create a new window. This will be the new default + virtual desktop; otherwise, create a new window. This will be the new default value. - `"useNew"`: Never glom, always create a new window. This is technically the current behavior of the Terminal. diff --git a/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md b/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md index 72a672cffa..620e5c30a6 100644 --- a/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md +++ b/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md @@ -362,7 +362,7 @@ Essentially, the probabilistic elective monarchy will work in the following way: register. 3. After registering as a server for `Monarch`s, attempt to create a `Monarch` using `winrt::create_instance`. -4. Using that `Monarch`, ask it for it's PID. +4. Using that `Monarch`, ask it for its PID. - If that PID is the same as the PID of the current process, then the window process knows that it is the monarch. - If that PID is some other process, then we know that we're not currently @@ -1102,7 +1102,7 @@ launch to use seems like an obvious next step. See also [#961]. - `true` or `"always"`: always glom to the most recent window, regardless of desktop - `"sameDesktop"`: Only glom if there's an existing window on this virtual - desktop, otherwise create a new window + desktop; otherwise, create a new window - `false` or `"never"`: Never glom, always create a new window. diff --git a/doc/specs/#605 - Search/spec.md b/doc/specs/#605 - Search/spec.md index 66a3b6dd54..e828cec0f8 100644 --- a/doc/specs/#605 - Search/spec.md +++ b/doc/specs/#605 - Search/spec.md @@ -13,7 +13,7 @@ This spec is for feature request #605 "Search". It goes over the details of a ne ## Inspiration -One of the superior features of iTerm2 is it's content search. The search comes in two variants: search from active tab and search from all tabs. In almost any editor, there is an roughly equivalent string search. We also want to realize search experience in Terminal. There will be two variants, search within one tab or from multiple tabs. We will start with one-tab search implementation. +One of the superior features of iTerm2 is it's content search. The search comes in two variants: search from active tab and search from all tabs. In almost any editor, there is a roughly equivalent string search. We also want to realize search experience in Terminal. There will be two variants, search within one tab or from multiple tabs. We will start with one-tab search implementation. ## Solution Design diff --git a/doc/specs/#7335 - Console Allocation Policy.md b/doc/specs/#7335 - Console Allocation Policy.md index c094a8edfc..f05df179d1 100644 --- a/doc/specs/#7335 - Console Allocation Policy.md +++ b/doc/specs/#7335 - Console Allocation Policy.md @@ -331,7 +331,7 @@ Are there other allocation policies we need to consider? - requires coordination between tooling teams both within and without Microsoft (regarding any tool that operates on or produces PE files) -- An exported symbol that shells can check for to determine whether to wait for the attached process to exit +- An exported symbol that shells can check for in order to determine whether to wait for the attached process to exit - relies on shells to update and check for this - cracking an executable to look for symbols is probably the last thing shells want to do - we could provide an API to determine whether to wait or return? diff --git a/doc/specs/#754 - Cascading Default Settings.md b/doc/specs/#754 - Cascading Default Settings.md index 73fd1928dc..5d43c2d0c0 100644 --- a/doc/specs/#754 - Cascading Default Settings.md +++ b/doc/specs/#754 - Cascading Default Settings.md @@ -360,7 +360,7 @@ GUID GetNamespaceGuid(IDynamicProfileGenerator& generator); GUID GetGuidForName(IDynamicProfileGenerator& generator, std::wstring& name); ``` -The generator does not _need_ to use `GetGuidForName` to generate guids for it's +The generator does not _need_ to use `GetGuidForName` to generate guids for its profiles. If the generator can determine another way to generate stable GUIDs for its profiles, it's free to use whatever method it wants. `GetGuidForName` is provided as a convenience. diff --git a/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md b/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md index 5426c9716d..6e27d19238 100644 --- a/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md +++ b/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md @@ -151,11 +151,11 @@ void CascadiaSettings::LayerJson(const Json::Value& json) // repeat the same for Profiles... } ``` -For `defaults.json`, `_globals` will now hold all of the values set in `defaults.json`. If any settings were omitted from the `defaults.json`, `_globals` will fallback to its parent (a `GlobalAppSettings` consisting purely of system-defined values). +For `defaults.json`, `_globals` will now hold all of the values set in `defaults.json`. If any settings were omitted from the `defaults.json`, `_globals` will fall back to its parent (a `GlobalAppSettings` consisting purely of system-defined values). -For `settings.json`, `_globals` will only hold the values set in `settings.json`. If any settings were omitted from `settings.json`, `_globals` will fallback to its parent (the `GlobalAppSettings` built from `defaults.json`). +For `settings.json`, `_globals` will only hold the values set in `settings.json`. If any settings were omitted from `settings.json`, `_globals` will fall back to its parent (the `GlobalAppSettings` built from `defaults.json`). -This process becomes a bit more complex for `Profile` because it can fallback in the following order: +This process becomes a bit more complex for `Profile` because it can fall back in the following order: 1. `settings.json` profile 2. `settings.json` `profiles.defaults` 3. (if a dynamic profile) the hard-coded value in the dynamic profile generator diff --git a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md index c201c0bc33..6bf09a5786 100644 --- a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md +++ b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md @@ -101,8 +101,8 @@ The scopes would work as follows: this tab. - **TODO!: FOR DISCUSSION**: Should this disable the tab's "broadcastToAllPanes" setting? Or should it leave that alone? -* `"disableBroadcastInput"`: Set the global setting to false, the tab's setting - to false, and clear the set of panes being broadcasted to for this tab. +* `"disableBroadcastInput"`: For this tab, set the global setting to false, + the tab's setting to false, and clear the set of panes being broadcasted. - **TODO!** This could also just be `"action": "toggleBroadcastInput", "scope": "none"` @@ -161,7 +161,7 @@ As far as actions, we're looking at something like: from the broadcast set. Otherwise, add all the panes from this tab to the broadcast set. * **D** toggle sending input to the current pane - * If this pane is in the broadcast set, remove it. Otherwise add it. + * If this pane is in the broadcast set, remove it. Otherwise, add it. This seems to break down into the following actions: ```json diff --git a/doc/terminal-a11y-2023.md b/doc/terminal-a11y-2023.md index 3686120319..60be666fb9 100644 --- a/doc/terminal-a11y-2023.md +++ b/doc/terminal-a11y-2023.md @@ -8,12 +8,12 @@ Since accessibility is a very broad area, this document is intended to present r ### First-party terminals For many years, Console Host (Conhost) was the only first-party terminal on Windows. In 2019, Windows Terminal was released to the world as an open source first-party terminal. Windows Terminal was distributed through the Microsoft Store and received regular updates throughout the year, much more frequently than Conhost. In October 2022, Windows Terminal was enabled as the default terminal on Windows. -A significant amount of code is shared between Conhost and Windows Terminal to create the terminal area. To enable an accessible experience for this area, a shared UI Automation provider was introduced in 2019[^1], enabling accessibility tools to navigate and read contents from the terminal area. In 2020, Windows Terminal was updated to dispatch UIA events signaling when the cursor position, text output, or selection changed; this left the work of identifying what changed in the output to the attached screen reader application[^2]. In 2022, Windows Terminal was updated to dispatch UIA notifications with a payload of what text was written to the screen[^3]. +A significant amount of code is shared between Conhost and Windows Terminal to create the terminal area. To enable an accessible experience for this area, a shared UI Automation provider was introduced in 2019[^1], enabling accessibility tools to navigate and read contents from the terminal area. In 2020, Windows Terminal was updated to dispatch UIA events signaling when the cursor position, text output, or selection changed; this left the work of identifying what changed in the output to the attached screen reader application[^2]. In 2022, Windows Terminal was updated to dispatch UIA notifications with a payload of what text was written to the screen[^3]. ### Internal Partners There are many first-party command-line applications on Windows. The following are a few examples of those that are regularly updated: - [**GitHub CLI**](https://cli.github.com/): a tool that can be used to query and interact with GitHub repos (open source) -- [**Winget**](https://github.com/microsoft/winget-cli): a tool to install applications and other packages +- [**WinGet**](https://github.com/microsoft/winget-cli): a tool to install applications and other packages - [**PSReadLine**](https://github.com/PowerShell/PSReadLine): a PowerShell module that enhances the input line experience - [**Windows Subsystem for Linux (WSL)**](https://learn.microsoft.com/en-us/windows/wsl/): a tool to manage and run GNU/Linux environments without a traditional virtual machine - [**PowerShell**](https://github.com/PowerShell/PowerShell): a cross-platform command-line shell (open source) @@ -33,7 +33,7 @@ The following examples don't take over the entire viewport: - [**Oh My Posh**](https://ohmyposh.dev/): a tool to customize shell prompts - **git**: a tool for version control The following examples operate as command-line shells: -- [**Bash**](https://www.gnu.org/software/bash/) is the default shell for most Linux distributions +- [**Bash**](https://www.gnu.org/software/bash/) is the default shell for most Linux distributions - [**Fish shell**](https://fishshell.com/) provides a rich shell experience with features like autosuggestion support and VGA colors - [**Z shell**](https://zsh.sourceforge.io/) is an extended Bourne shell @@ -113,7 +113,7 @@ This issue is tracked by [megathread: Scrollbar Marks · Issue #11000](https://g ### Mark Mode support for degenerate range [PR #13053](https://github.com/microsoft/terminal/pull/13053) added support for mark mode in Windows Terminal. Mark mode allows users to create and modify selections by exclusively using the keyboard. However, screen reader users have reported it as a strange experience because it always has a cell of text selected; this results in the screen reader reading "x selected, y unselected" as opposed to the expected "x" when moving the cursor around. -Unfortunately, the changes required to fix this are very extensive because selections are stored as two inclusive terminal coordinates, which makes it impossible to represent an empty selection. +Unfortunately, the changes required to fix this are very extensive because selections are stored as two inclusive terminal coordinates, which makes it impossible to represent an empty selection. This is tracked by [A11y: windows terminal emits selection/deselection events in mark mode when navigating with arrow keys · Issue #13447](https://github.com/microsoft/terminal/issues/13447). @@ -158,7 +158,7 @@ In 2022, Windows Terminal added UI Automation notifications that contained a pay UIA notifications have provided many compatibility benefits since screen readers automatically read notifications they receive. Additionally, this has provided the possibility for major performance enhancements as screen readers may no longer be required to diff the text buffer and figure out what has changed. NVDA has prototyped listening to notifications and ignoring text changed events entirely[^7]. However, it reveals underlying challenges with this new model such as how to handle passwords. The proposals listed in this section are intended to have Windows Terminal achieve improved performance and accessibility quality. #### VT Screen Reader Control -Some command-line applications are simply too difficult to create a consistent accessible experience. Applications that draw decorative content, for example, may have that content read by a screen reader. +Some command-line applications are simply too difficult to create a consistent accessible experience. Applications that draw decorative content, for example, may have that content read by a screen reader. In 2019, Daniel Imms wrote a spec proposing a VT sequence that can partially control the attached screen reader[^8]. This VT sequence consists of three main formats: 1. Stop announcing incoming data to the screen reader. The screen reader will resume announcing incoming data if any key is pressed. @@ -214,4 +214,4 @@ Generally, the reasoning behind these priorities can be broken down as follows: [^5]: [Implement the Delta E algorithm to improve color perception by PankajBhojwani · Pull Request #11095](https://github.com/microsoft/terminal/pull/11095) [^6]: [Change AdjustIndistinguishableColors to an enum setting instead of a boolean setting by PankajBhojwani · Pull Request #13512](https://github.com/microsoft/terminal/pull/13512) [^7]: [Prototype for Windows Terminal: Use notifications instead of monitoring for new text by leonardder · Pull Request #14047 · nvaccess/nvda (github.com)](https://github.com/nvaccess/nvda/pull/14047) -[^8]: [Control Screen Reader from Applications (#18) · Issues · terminal-wg / specifications · GitLab](https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/18) \ No newline at end of file +[^8]: [Control Screen Reader from Applications (#18) · Issues · terminal-wg / specifications · GitLab](https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/18) diff --git a/policies/en-US/WindowsTerminal.adml b/policies/en-US/WindowsTerminal.adml index f2bcf71d3c..b0be1c3d57 100644 --- a/policies/en-US/WindowsTerminal.adml +++ b/policies/en-US/WindowsTerminal.adml @@ -22,7 +22,7 @@ Note: Existing profiles will disappear from Windows Terminal after adding their Default terminal application Select the default terminal application used in Windows. -If you select Windows Terminal Preview and it is not installed the system will fallback to the legacy Windows Console Host. (Please note that the settings interfaces showing "Let windows decide" in this case as configuration.) +If you select Windows Terminal Preview and it is not installed the system will fall back to the legacy Windows Console Host. (Please note that the settings interfaces showing "Let windows decide" in this case as configuration.) Automatic selection (Windows Terminal, if available) Windows Console Host (legacy) Windows Terminal diff --git a/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml b/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml index 2c67f1cb0e..36cc20b91f 100644 --- a/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml +++ b/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml @@ -32,7 +32,7 @@ BasedOn="{StaticResource TitleBarButtonStyle}" TargetType="{x:Type Button}"> - + diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 11f48ec0f6..6f0336e8bc 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2969,7 +2969,7 @@ void TextBuffer::_SerializeRow(const ROW& row, const til::CoordType startX, cons // parameter and we'll calculate the position of the _end_ of those rows in // the new buffer. The rows's new value is placed back into this parameter. // Return Value: -// - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT. +// - S_OK if we successfully copied the contents to the new buffer; otherwise, an appropriate HRESULT. void TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Viewport* lastCharacterViewport, PositionInformation* positionInfo) { const auto& oldCursor = oldBuffer.GetCursor(); diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp index e46e415e37..58e7766c89 100644 --- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp +++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp @@ -573,7 +573,7 @@ namespace TestCase{ // This triggers the cursor being walked forward w/ newlines to maintain // distance from the last char in the buffer - L"SBCS, cursor at end of buffer, otherwise same as previous test", + L"SBCS, cursor at end of buffer; otherwise, same as previous test", { TestBuffer{ { 6, 5 }, diff --git a/src/cascadia/ShellExtension/OpenTerminalHere.cpp b/src/cascadia/ShellExtension/OpenTerminalHere.cpp index 88d4f43551..e3d0f813f6 100644 --- a/src/cascadia/ShellExtension/OpenTerminalHere.cpp +++ b/src/cascadia/ShellExtension/OpenTerminalHere.cpp @@ -21,7 +21,7 @@ static constexpr std::wstring_view VerbName{ L"WindowsTerminalOpenHere" }; // Arguments: // - psiItemArray: a IShellItemArray which contains the item that's selected. // Return Value: -// - S_OK if we successfully attempted to launch the Terminal, otherwise a +// - S_OK if we successfully attempted to launch the Terminal; otherwise, a // failure from an earlier HRESULT. HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray, IBindCtx* /*pBindContext*/) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 6964892963..50bd7cd122 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -517,7 +517,7 @@ namespace winrt::TerminalApp::implementation else { // Mark as handled only when the move succeeded (e.g. when there - // is a pane to move to), otherwise mark as unhandled so the + // is a pane to move to); otherwise, mark as unhandled so the // keychord can propagate to the terminal (GH#6129) const auto moveSucceeded = _MoveFocus(realArgs.FocusDirection()); args.Handled(moveSucceeded); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 6a0d456142..53c3fde491 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -194,7 +194,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Attempt to load the settings. If we fail for any reason, returns an error. // Return Value: - // - S_OK if we successfully parsed the settings, otherwise an appropriate HRESULT. + // - S_OK if we successfully parsed the settings; otherwise, an appropriate HRESULT. [[nodiscard]] HRESULT AppLogic::_TryLoadSettings() noexcept { auto hr = E_FAIL; diff --git a/src/cascadia/TerminalApp/ColorHelper.cpp b/src/cascadia/TerminalApp/ColorHelper.cpp index c2dd4c2303..2235cc6b37 100644 --- a/src/cascadia/TerminalApp/ColorHelper.cpp +++ b/src/cascadia/TerminalApp/ColorHelper.cpp @@ -8,7 +8,7 @@ using namespace winrt::TerminalApp; // - color: this color is going to be examined whether it // is light or not // Return Value: -// - true of light, false if dark +// - true if light, false if dark bool ColorHelper::IsBrightColor(const winrt::Windows::UI::Color& color) { // https://www.w3.org/TR/AERT#color-contrast diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index e2e7526db8..13d058feab 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -338,7 +338,7 @@ namespace winrt::TerminalApp::implementation } else if (key == VirtualKey::Escape) { - // Dismiss the palette if the text is empty, otherwise clear the + // Dismiss the palette if the text is empty; otherwise, clear the // search string. if (_searchBox().Text().empty()) { diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 3255c5fba3..30ca4714eb 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -1052,7 +1052,7 @@ std::shared_ptr Pane::GetActivePane() // Arguments: // - // Return Value: -// - nullptr if this Pane is an unfocused parent, otherwise the TermControl of this Pane. +// - nullptr if this Pane is an unfocused parent; otherwise, the TermControl of this Pane. TermControl Pane::GetLastFocusedTerminalControl() { if (!_IsLeaf()) @@ -1105,7 +1105,7 @@ IPaneContent Pane::GetLastFocusedContent() // Arguments: // - // Return Value: -// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. +// - nullptr if this Pane is a parent; otherwise, the TermControl of this Pane. TermControl Pane::GetTerminalControl() const { if (const auto& terminalPane{ _getTerminalContent() }) @@ -1597,7 +1597,7 @@ void Pane::_CloseChildRoutine(const bool closeFirst) return; } - // Setup the animation + // Set up the animation auto removedChild = closeFirst ? _firstChild : _secondChild; auto remainingChild = closeFirst ? _secondChild : _firstChild; @@ -1998,7 +1998,7 @@ void Pane::_SetupEntranceAnimation() if (splitWidth) { // If we're animating the first child, then stick to the top/left of - // the parent pane, otherwise use the bottom/right. This is always + // the parent pane; otherwise, use the bottom/right. This is always // the "outside" of the parent pane. childGrid.HorizontalAlignment(isFirstChild ? HorizontalAlignment::Left : HorizontalAlignment::Right); if (control) @@ -2023,7 +2023,7 @@ void Pane::_SetupEntranceAnimation() else { // If we're animating the first child, then stick to the top/left of - // the parent pane, otherwise use the bottom/right. This is always + // the parent pane; otherwise, use the bottom/right. This is always // the "outside" of the parent pane. childGrid.VerticalAlignment(isFirstChild ? VerticalAlignment::Top : VerticalAlignment::Bottom); if (control) @@ -2538,7 +2538,7 @@ std::pair Pane::_CalcChildrenSizes(const float fullSize) const // user doesn't get any pane shrank when they actually expand the window or parent pane. // That is also required by the layout algorithm. // Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. +// - widthOrHeight: if true, operates on width; otherwise, on height. // - fullSize: the amount of space in pixels that should be filled by our children and // their separator. Can be arbitrarily low. // Return Value: @@ -2600,7 +2600,7 @@ Pane::SnapChildrenSizeResult Pane::_CalcSnappedChildrenSizes(const bool widthOrH // align with their character grids as close as possible. Snaps to closes match // (either upward or downward). Also makes sure to fit in minimal sizes of the panes. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height +// - widthOrHeight: if true operates on width; otherwise, on height // - dimension: a dimension (width or height) to snap // Return Value: // - A value corresponding to the next closest snap size for this Pane, either upward or downward @@ -2615,7 +2615,7 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension // align with their character grids as close as possible. Also makes sure to // fit in minimal sizes of the panes. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height +// - widthOrHeight: if true operates on width; otherwise, on height // - dimension: a dimension (width or height) to be snapped // Return Value: // - pair of floats, where first value is the size snapped downward (not greater than @@ -2702,11 +2702,11 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const // Method Description: // - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf -// pane this means the next cell of the terminal. Otherwise it means that one of its children +// pane this means the next cell of the terminal. Otherwise, it means that one of its children // advances (recursively). It expects the given node and its descendants to have either // already snapped or minimum size. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height. +// - widthOrHeight: if true operates on width; otherwise, on height. // - sizeNode: a layout size node that corresponds to this pane. // Return Value: // - @@ -2877,7 +2877,7 @@ Size Pane::_GetMinSize() const // - Builds a tree of LayoutSizeNode that matches the tree of panes. Each node // has minimum size that the corresponding pane can have. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height +// - widthOrHeight: if true operates on width; otherwise, on height // Return Value: // - Root node of built tree that matches this pane. Pane::LayoutSizeNode Pane::_CreateMinSizeTree(const bool widthOrHeight) const @@ -2897,7 +2897,7 @@ Pane::LayoutSizeNode Pane::_CreateMinSizeTree(const bool widthOrHeight) const // - Adjusts split position so that no child pane is smaller then its // minimum size // Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. +// - widthOrHeight: if true, operates on width; otherwise, on height. // - requestedValue: split position value to be clamped // - totalSize: size (width or height) of the parent pane // Return Value: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index 28cb64b5aa..fe03a94f78 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -27,7 +27,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - actionAndArgs: the ShortcutAction and associated args to raise an event for. // Return Value: - // - true if we handled the event was handled, else false. + // - true if the event was handled, else false. bool ShortcutActionDispatch::DoAction(const winrt::Windows::Foundation::IInspectable& sender, const ActionAndArgs& actionAndArgs) { diff --git a/src/cascadia/TerminalApp/SuggestionsControl.cpp b/src/cascadia/TerminalApp/SuggestionsControl.cpp index 3ebc7ca158..ed2bb3f686 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.cpp +++ b/src/cascadia/TerminalApp/SuggestionsControl.cpp @@ -421,7 +421,7 @@ namespace winrt::TerminalApp::implementation } else if (key == VirtualKey::Escape) { - // Dismiss the palette if the text is empty, otherwise clear the + // Dismiss the palette if the text is empty; otherwise, clear the // search string. if (_searchBox().Text().empty()) { diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 810ce5272b..f900e05cb7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1289,7 +1289,7 @@ namespace winrt::TerminalApp::implementation // // We need to do this here, to ensure we tell the ConptyConnection // the correct starting path. If we're being invoked from another - // terminal instance (e.g. wt -w 0 -d .), then we have switched our + // terminal instance (e.g. `wt -w 0 -d .`), then we have switched our // CWD to the provided path. We should treat the StartingDirectory // as relative to the current CWD. // @@ -3970,7 +3970,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - args: the ExecuteCommandlineArgs to synthesize a list of startup actions for. // Return Value: - // - an empty list if we failed to parse, otherwise a list of actions to execute. + // - an empty list if we failed to parse; otherwise, a list of actions to execute. std::vector TerminalPage::ConvertExecuteCommandlineToActions(const ExecuteCommandlineArgs& args) { ::TerminalApp::AppCommandlineArgs appArgs; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 88a839949c..c2afef3412 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -327,7 +327,7 @@ namespace winrt::TerminalApp::implementation // - Show a ContentDialog with buttons to take further action. Uses the // FrameworkElements provided as the title and content of this dialog, and // displays buttons (or a single button). Two buttons (primary and secondary) - // will be displayed if this is an warning dialog for closing the terminal, + // will be displayed if this is a warning dialog for closing the terminal, // this allows the users to abandon the closing action. Otherwise, a single // close button will be displayed. // - Only one dialog can be visible at a time. If another dialog is visible diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 61d699a6cc..b2ad7b7549 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -619,7 +619,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - vkey: The vkey of the key pressed. // - scanCode: The scan code of the key pressed. // - modifiers: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. - // - keyDown: If true, the key was pressed, otherwise the key was released. + // - keyDown: If true, the key was pressed; otherwise, the key was released. bool ControlCore::TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers, @@ -1357,7 +1357,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return false; } - // use action's copyFormatting if it's present, else fallback to globally + // use action's copyFormatting if it's present, else fall back to globally // set copyFormatting. const auto copyFormats = formats != nullptr ? formats.Value() : _settings->CopyFormatting(); diff --git a/src/cascadia/TerminalControl/ControlInteractivity.h b/src/cascadia/TerminalControl/ControlInteractivity.h index dcf2c811d0..02b198a77a 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.h +++ b/src/cascadia/TerminalControl/ControlInteractivity.h @@ -104,7 +104,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // // ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own. // We must ensure that we first destroy the ControlCore before the UiaEngine instance - // in order to safely resolve this unsafe pointer dependency. Otherwise a deallocated + // in order to safely resolve this unsafe pointer dependency. Otherwise, a deallocated // IRenderEngine is accessed when ControlCore calls Renderer::TriggerTeardown. // (C++ class members are destroyed in reverse order.) std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine; diff --git a/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp b/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp index 6eae42a665..92724a2c67 100644 --- a/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp +++ b/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp @@ -45,7 +45,7 @@ static std::wstring Sanitize(std::wstring_view text) // Arguments: // - text: the string we're validating // Return Value: -// - true, if the text is readable. false, otherwise. +// - true, if the text is readable; otherwise, false. static constexpr bool IsReadable(std::wstring_view text) { for (const auto c : text) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 67809d4cea..ea8446f04e 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1033,7 +1033,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Set up each layer's brush used to display the control's background. // - Respects the settings for acrylic, background image and opacity from // _settings. - // * If acrylic is not enabled, setup a solid color background, otherwise + // * If acrylic is not enabled, set up a solid color background; otherwise, // use bgcolor as acrylic's tint // - Avoids image flickering and acrylic brush redraw if settings are changed // but the appropriate brush is still in place. @@ -1898,7 +1898,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - vkey: The vkey of the key pressed. // - scanCode: The scan code of the key pressed. // - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. - // - keyDown: If true, the key was pressed, otherwise the key was released. + // - keyDown: If true, the key was pressed; otherwise, the key was released. bool TermControl::_TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers, @@ -2982,7 +2982,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Adjusts given dimension (width or height) so that it aligns to the character grid. // The snap is always downward. // Arguments: - // - widthOrHeight: if true operates on width, otherwise on height + // - widthOrHeight: if true operates on width; otherwise, on height // - dimension: a dimension (width or height) to be snapped // Return Value: // - A dimension that would be aligned to the character grid. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 7fe3db1be0..2f9dad9784 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -242,7 +242,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // // ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own. // We must ensure that we first destroy the ControlCore before the UiaEngine instance - // in order to safely resolve this unsafe pointer dependency. Otherwise a deallocated + // in order to safely resolve this unsafe pointer dependency. Otherwise, a deallocated // IRenderEngine is accessed when ControlCore calls Renderer::TriggerTeardown. // (C++ class members are destroyed in reverse order.) // Further, the TermControlAutomationPeer must be destructed after _uiaEngine! diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp index 1adb9af5ce..90ad7b9876 100644 --- a/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp @@ -52,7 +52,7 @@ static std::wstring Sanitize(std::wstring_view text) // Arguments: // - text: the string we're validating // Return Value: -// - true, if the text is readable. false, otherwise. +// - true, if the text is readable; otherwise, false. static constexpr bool IsReadable(std::wstring_view text) { for (const auto c : text) @@ -305,7 +305,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation hstring TermControlAutomationPeer::GetNameCore() const { - // fallback to title if profile name is empty + // fall back to title if profile name is empty if (auto control{ _termControl.get() }) { const auto profileName = control->GetProfileName(); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 005916822d..b292fd813b 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -469,7 +469,7 @@ void Terminal::TrySnapOnInput() // Parameters: // - // Return value: -// - true, if we are tracking mouse input. False, otherwise +// - true, if we are tracking mouse input; otherwise, false. bool Terminal::IsTrackingMouseInput() const noexcept { return _getTerminalInput().IsTrackingMouseInput(); @@ -482,7 +482,7 @@ bool Terminal::IsTrackingMouseInput() const noexcept // Parameters: // - // Return value: -// - true, if we are tracking mouse input. False, otherwise +// - true, if we are tracking mouse input; otherwise, false. bool Terminal::ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const noexcept { @@ -599,7 +599,7 @@ std::optional Terminal::GetHyperlinkIntervalFromViewportPos // - vkey: The vkey of the last pressed key. // - scanCode: The scan code of the last pressed key. // - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. -// - keyDown: If true, the key was pressed, otherwise the key was released. +// - keyDown: If true, the key was pressed; otherwise, the key was released. // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. @@ -921,7 +921,7 @@ void Terminal::_StoreKeyEvent(const WORD vkey, const WORD scanCode) noexcept // Arguments: // - scanCode: The scan code. // Return Value: -// - The key code matching the given scan code. Otherwise 0. +// - The key code matching the given scan code. Otherwise, 0. WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept { const auto codes = _lastKeyEventCodes.value_or(KeyEventCodes{}); diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 7d8a706274..9822d68208 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -377,7 +377,7 @@ void Terminal::NotifyBufferRotation(const int delta) auto selection{ _selection.write() }; wil::hide_name _selection; // If the end of the selection will be out of range after the move, we just - // clear the selection. Otherwise we move both the start and end points up + // clear the selection. Otherwise, we move both the start and end points up // by the given delta and clamp to the first row. if (selection->end.y < delta) { diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index d588b67cb0..f0019d6614 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -438,7 +438,7 @@ void Terminal::ExpandSelectionToWord() // Arguments: // - dir: the direction we're scanning the buffer in to find the hyperlink of interest // Return Value: -// - true if we found a hyperlink to select (and selected it). False otherwise. +// - true if we found a hyperlink to select (and selected it); otherwise, false. void Terminal::SelectHyperlink(const SearchDirection dir) { if (_selectionMode != SelectionInteractionMode::Mark) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index b5fdc867b6..6f9c460797 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -258,7 +258,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - // Couldn't find the selected item, fallback to first menu item + // Couldn't find the selected item, fall back to first menu item // This happens when the selected item was a profile which doesn't exist in the new configuration // We can use menuItemsSTL here because the only things they miss are profile entries. const auto& firstItem{ _menuItemSource.GetAt(0).as() }; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 577903edc0..730e646310 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -137,7 +137,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - actionID: the internal ID associated with a Command // Return Value: - // - The command if it exists in this layer, otherwise nullptr + // - The command if it exists in this layer; otherwise, nullptr Model::Command ActionMap::_GetActionByID(const winrt::hstring& actionID) const { // Check current layer @@ -692,7 +692,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - oldKeys: the key binding that we are rebinding // - newKeys: the new key chord that is being used to replace oldKeys // Return Value: - // - true, if successful. False, otherwise. + // - true, if successful; otherwise, false. bool ActionMap::RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) { const auto cmd{ GetActionByKeyChord(oldKeys) }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index bbbb9172c7..16f71dd130 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -1014,7 +1014,7 @@ void SettingsLoader::_appendProfile(winrt::com_ptr&& profile, const win } // If the given ParsedSettings instance contains a profile with the given profile's GUID, -// the profile is added as a parent. Otherwise a new child profile is created. +// the profile is added as a parent. Otherwise, a new child profile is created. void SettingsLoader::_addUserProfileParent(const winrt::com_ptr& profile) { if (const auto [it, inserted] = userSettings.profilesByGuid.emplace(profile->Guid(), nullptr); !inserted) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 7ec4d07374..d1ea74cfd5 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -165,7 +165,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - json: The Json::Value representing the command object we should get the name for. // Return Value: - // - the empty string if we couldn't find a name, otherwise the command's name. + // - the empty string if we couldn't find a name; otherwise, the command's name. static std::optional _nameFromJson(const Json::Value& json) { if (const auto name{ json[JsonKey(NameKey)] }) @@ -489,7 +489,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - expandable: the Command to potentially turn into more commands // - profiles: A list of all the profiles that this command should be expanded on. // Return Value: - // - and empty vector if the command wasn't expandable, otherwise a list of + // - and empty vector if the command wasn't expandable; otherwise, a list of // the newly-created commands. std::vector Command::_expandCommand(Command* const expandable, IVectorView profiles, diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index ab2b93ce96..ef3256f426 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -89,7 +89,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { static constexpr std::optional EmptyV() { return std::nullopt; } static constexpr bool HasValue(const std::optional& o) { return o.has_value(); } - // We can return a reference here because the original value is stored inside an std::optional + // We can return a reference here because the original value is stored inside a std::optional static constexpr auto&& Value(const std::optional& o) { return *o; } }; diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index d1b2efb08b..1e5c61089a 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -310,7 +310,7 @@ std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory) } // Function Description: -// - Generates a unique guid for a profile, given the name. For an given name, will always return the same GUID. +// - Generates a unique guid for a profile, given the name. For a given name, will always return the same GUID. // Arguments: // - name: The name to generate a unique GUID from // Return Value: diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index ff9860aef6..9d3540b145 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -305,7 +305,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _StartingDirectory = profile.EvaluatedStartingDirectory(); - // GH#2373: Use the tabTitle as the starting title if it exists, otherwise + // GH#2373: Use the tabTitle as the starting title if it exists; otherwise, // use the profile name _StartingTitle = !profile.TabTitle().empty() ? profile.TabTitle() : profile.Name(); diff --git a/src/cascadia/TerminalSettingsModel/Theme.cpp b/src/cascadia/TerminalSettingsModel/Theme.cpp index 90fd1d1a76..3b79b5297d 100644 --- a/src/cascadia/TerminalSettingsModel/Theme.cpp +++ b/src/cascadia/TerminalSettingsModel/Theme.cpp @@ -372,7 +372,7 @@ winrt::hstring Theme::ToString() // RequestedTheme, this saves some hassle. If there wasn't a `window` defined // for this theme, this'll quickly just return `system`, to use the OS theme. // Return Value: -// - the set applicationTheme for this Theme, otherwise the system theme. +// - the set applicationTheme for this Theme; otherwise, the system theme. winrt::WUX::ElementTheme Theme::RequestedTheme() const noexcept { return _Window ? _Window.RequestedTheme() : winrt::WUX::ElementTheme::Default; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 6cdc7ff8e7..1e388f0f32 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -951,7 +951,7 @@ void AppHost::_updateTheme() _window->UseDarkTheme(_isActuallyDarkTheme(theme.RequestedTheme())); // Update the window frame. If `rainbowFrame:true` is enabled, then that - // will be used. Otherwise we'll try to use the `FrameBrush` set in the + // will be used. Otherwise, we'll try to use the `FrameBrush` set in the // terminal window, as that will have the right color for the ThemeColor for // this setting. If that value is null, then revert to the default frame // color. diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 414e4043f8..95d971ecea 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -692,7 +692,7 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept // If there's a taskbar on any side of the monitor, reduce our size // a little bit on that edge. // - // Note to future code archeologists: + // Note to future code archaeologists: // This doesn't seem to work for fullscreen on the primary display. // However, testing a bunch of other apps with fullscreen modes // and an auto-hiding taskbar has shown that _none_ of them diff --git a/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs b/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs index ab6a26d460..611cde52a6 100644 --- a/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs +++ b/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs @@ -103,7 +103,7 @@ namespace Microsoft.Terminal.Wpf } /// - /// Gets the selected text in the terminal, clearing the selection. Otherwise returns an empty string. + /// Gets the selected text in the terminal, clearing the selection. Otherwise, returns an empty string. /// /// Selected text, empty string if no content is selected. public string GetSelectedText() diff --git a/src/host/ConsoleArguments.cpp b/src/host/ConsoleArguments.cpp index d1ecdcb2b1..8241a62e0b 100644 --- a/src/host/ConsoleArguments.cpp +++ b/src/host/ConsoleArguments.cpp @@ -182,7 +182,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // should be at (index+1). index will be decremented by one on success. // pSetting: receives the string at index+1 // Return Value: -// S_OK if we parsed the string successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the string successfully; otherwise, E_INVALIDARG indicating // failure. [[nodiscard]] HRESULT ConsoleArguments::s_GetArgumentValue(_Inout_ std::vector& args, _Inout_ size_t& index, @@ -213,7 +213,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // should be at (index+1). index will be decremented by one on success. // pSetting: receives the string at index+1 // Return Value: -// S_OK if we parsed the string successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the string successfully; otherwise, E_INVALIDARG indicating // failure. [[nodiscard]] HRESULT ConsoleArguments::s_HandleFeatureValue(_Inout_ std::vector& args, _Inout_ size_t& index) { @@ -243,7 +243,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // should be at (index+1). index will be decremented by one on success. // pSetting: receives the short at index+1 // Return Value: -// S_OK if we parsed the short successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the short successfully; otherwise, E_INVALIDARG indicating // failure. This could be the case for non-numeric arguments, or for >SHORT_MAX args. [[nodiscard]] HRESULT ConsoleArguments::s_GetArgumentValue(_Inout_ std::vector& args, _Inout_ size_t& index, @@ -332,7 +332,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // index: the index of the argument of which to start the commandline from. // skipFirst: if true, omit the arg at index (which should be "--") // Return Value: -// S_OK if we parsed the string successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the string successfully; otherwise, E_INVALIDARG indicating // failure. [[nodiscard]] HRESULT ConsoleArguments::_GetClientCommandline(_Inout_ std::vector& args, const size_t index, const bool skipFirst) { @@ -369,7 +369,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // Arguments: // // Return Value: -// S_OK if we parsed our _commandline successfully, otherwise E_INVALIDARG +// S_OK if we parsed our _commandline successfully; otherwise, E_INVALIDARG // indicating failure. [[nodiscard]] HRESULT ConsoleArguments::ParseCommandline() { diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index be1d8bd330..53911496a0 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -73,7 +73,7 @@ using namespace Microsoft::Console::Interactivity; // SignalHandle: an optional file handle that will be used to send signals into the console. // This represents the ability to send signals to a *nix tty/pty. // Return Value: -// S_OK if we initialized successfully, otherwise an appropriate HRESULT +// S_OK if we initialized successfully; otherwise, an appropriate HRESULT // indicating failure. [[nodiscard]] HRESULT VtIo::_Initialize(const HANDLE InHandle, const HANDLE OutHandle, @@ -136,7 +136,7 @@ bool VtIo::IsUsingVt() const // Arguments: // // Return Value: -// S_OK if we started successfully or had nothing to start, otherwise an +// S_OK if we started successfully or had nothing to start; otherwise, an // appropriate HRESULT indicating failure. [[nodiscard]] HRESULT VtIo::StartIfNeeded() { @@ -361,7 +361,7 @@ void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attribute wchar_t VtIo::SanitizeUCS2(wchar_t ch) { // If any of the values in the buffer are C0 or C1 controls, we need to - // convert them to printable codepoints, otherwise they'll end up being + // convert them to printable codepoints; otherwise, they'll end up being // evaluated as control characters by the receiving terminal. We use the // DOS 437 code page for the C0 controls and DEL, and just a `?` for the // C1 controls, since that's what you would most likely have seen in the diff --git a/src/host/exe/exemain.cpp b/src/host/exe/exemain.cpp index 2b95ba0916..054cdea490 100644 --- a/src/host/exe/exemain.cpp +++ b/src/host/exe/exemain.cpp @@ -175,7 +175,7 @@ static bool ShouldUseLegacyConhost(const ConsoleArguments& args) { // setup status error hr = HRESULT_FROM_WIN32(GetLastError()); - // fallback to V2 if conhostv1.dll cannot be loaded. + // fall back to V2 if conhostv1.dll cannot be loaded. useV2 = true; } diff --git a/src/host/ft_host/API_AliasTestsHelpers.hpp b/src/host/ft_host/API_AliasTestsHelpers.hpp index 9e2f751e7a..49dd8eb221 100644 --- a/src/host/ft_host/API_AliasTestsHelpers.hpp +++ b/src/host/ft_host/API_AliasTestsHelpers.hpp @@ -233,7 +233,7 @@ void TestGetConsoleAliasHelper(TCH* ptszSourceGiven, if (0 == dwExpectedLastError) { - // If it was successful, it should have been filled. Otherwise it will be zeroed as when it started. + // If it was successful, it should have been filled. Otherwise, it will be zeroed as when it started. StringCbCopyT(ptchExpectedTarget, cbTargetBuffer, ptszExpectedTargetGiven); } diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 9107413e7e..5fc568d456 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -84,7 +84,7 @@ void ConhostInternalGetSet::SetViewportPosition(const til::point position) THROW_IF_FAILED(info.SetViewportOrigin(true, position, true)); // SetViewportOrigin() only updates the virtual bottom (the bottom coordinate of the area // in the text buffer a VT client writes its output into) when it's moving downwards. - // But this function is meant to truly move the viewport no matter what. Otherwise `tput reset` breaks. + // But this function is meant to truly move the viewport no matter what. Otherwise, `tput reset` breaks. info.UpdateBottom(); } diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index ea086b1230..c7d82fae0b 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -184,7 +184,7 @@ COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, // - It may be called more than once. // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or ctrl-break was seen, this argument -// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Whether to convert the final data to A (using Console Input CP) at the end or treat everything as Unicode (UCS-2) // - pReplyStatus - The status code to return to the client application that originally called the API (before it was queued to wait) // - pNumBytes - The number of bytes of data that the server/driver will need to transmit back to the client process diff --git a/src/host/readDataDirect.cpp b/src/host/readDataDirect.cpp index 5009817763..ef36557093 100644 --- a/src/host/readDataDirect.cpp +++ b/src/host/readDataDirect.cpp @@ -36,7 +36,7 @@ DirectReadData::DirectReadData(_In_ InputBuffer* const pInputBuffer, // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or // ctrl-break was seen, this argument contains CtrlC or CtrlBreak. If -// the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Should we return UCS-2 unicode data, or should we // run the final data through the current Input Codepage before // returning? diff --git a/src/host/readDataRaw.cpp b/src/host/readDataRaw.cpp index 9351382c07..8c83143244 100644 --- a/src/host/readDataRaw.cpp +++ b/src/host/readDataRaw.cpp @@ -48,7 +48,7 @@ RAW_READ_DATA::~RAW_READ_DATA() = default; // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or // ctrl-break was seen, this argument contains CtrlC or CtrlBreak. If -// the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Whether to convert the final data to A (using // Console Input CP) at the end or treat everything as Unicode (UCS-2) // - pReplyStatus - The status code to return to the client diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 3e1ae49d4b..3fe3b7b859 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1221,7 +1221,7 @@ void SCREEN_INFORMATION::_AdjustViewportSize(const til::rect* const prcClientNew const til::size* const pcoordSize) { // If the left is the only one that changed (and not the right - // also), then adjust from the left. Otherwise if the right + // also), then adjust from the left. Otherwise, if the right // changes or both changed, bias toward leaving the top-left // corner in place and resize from the bottom right. // -- @@ -2407,7 +2407,7 @@ const FontInfo& SCREEN_INFORMATION::GetCurrentFont() const noexcept // Method Description: // - Gets the desired font of the screen buffer. If we try loading this font and -// have to fallback to another, then GetCurrentFont()!=GetDesiredFont(). +// have to fall back to another, then GetCurrentFont()!=GetDesiredFont(). // We store this separately, so that if we need to reload the font, we can // try again with our preferred font info (in the desired font info) instead // of re-using the looked up value from before. diff --git a/src/host/selection.cpp b/src/host/selection.cpp index d7dcf21ebe..5bc077e9ab 100644 --- a/src/host/selection.cpp +++ b/src/host/selection.cpp @@ -415,7 +415,7 @@ void Selection::ClearSelection(const bool fStartingNewSelection) // If we were using alternate selection, cancel it here before starting a new area. d->fUseAlternateSelection = false; - // Only unblock if we're not immediately starting a new selection. Otherwise stay blocked. + // Only unblock if we're not immediately starting a new selection. Otherwise, stay blocked. if (!fStartingNewSelection) { UnblockWriteConsole(CONSOLE_SELECTING); diff --git a/src/host/ut_host/ConsoleArgumentsTests.cpp b/src/host/ut_host/ConsoleArgumentsTests.cpp index a9148dfd2e..b03b9099db 100644 --- a/src/host/ut_host/ConsoleArgumentsTests.cpp +++ b/src/host/ut_host/ConsoleArgumentsTests.cpp @@ -812,7 +812,7 @@ void ConsoleArgumentsTests::InitialSizeTests() true); // successful parse? commandline = L"conhost.exe --width foo"; - ArgTestsRunner(L"#6 look for an ivalid commandline passing a string", + ArgTestsRunner(L"#6 look for an invalid commandline passing a string", commandline, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, @@ -833,7 +833,7 @@ void ConsoleArgumentsTests::InitialSizeTests() false); // successful parse? commandline = L"conhost.exe --width 2foo"; - ArgTestsRunner(L"#7 look for an ivalid commandline passing a string with a number at the start", + ArgTestsRunner(L"#7 look for an invalid commandline passing a string with a number at the start", commandline, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, @@ -854,7 +854,7 @@ void ConsoleArgumentsTests::InitialSizeTests() false); // successful parse? commandline = L"conhost.exe --width 65535"; - ArgTestsRunner(L"#7 look for an ivalid commandline passing a value that's too big", + ArgTestsRunner(L"#7 look for an invalid commandline passing a value that's too big", commandline, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 77b0f303cf..943d3199db 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -5982,7 +5982,7 @@ void ScreenBufferTests::ClearAlternateBuffer() auto useMain = wil::scope_exit([&] { altBuffer.UseMainScreenBuffer(); }); - // Set the position to home, otherwise it's inherited from the main buffer. + // Set the position to home; otherwise, it's inherited from the main buffer. VERIFY_SUCCEEDED(altBuffer.SetCursorPosition({ 0, 0 }, true)); WriteText(altBuffer.GetTextBuffer()); diff --git a/src/host/utils.cpp b/src/host/utils.cpp index d5ac1fc90d..292450ca5b 100644 --- a/src/host/utils.cpp +++ b/src/host/utils.cpp @@ -124,7 +124,7 @@ UINT s_LoadStringEx(_In_ HINSTANCE hModule, _In_ UINT wID, _Out_writes_(cchBuffe lpsz += cch; // Step to start if next string } - // chhBufferMax == 0 means return a pointer to the read-only resource buffer. + // cchBufferMax == 0 means return a pointer to the read-only resource buffer. if (cchBufferMax == 0) { *(LPTSTR*)lpBuffer = lpsz; diff --git a/src/host/writeData.cpp b/src/host/writeData.cpp index 6250155461..51d017bfc0 100644 --- a/src/host/writeData.cpp +++ b/src/host/writeData.cpp @@ -79,7 +79,7 @@ void WriteData::SetUtf8ConsumedCharacters(const size_t cchUtf8Consumed) // - Called back at a later time to resume the writing operation when the output object becomes unblocked. // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or ctrl-break was seen, this argument -// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Input data was in UCS-2 unicode or it needs to be converted with the current Output Codepage // - pReplyStatus - The status code to return to the client application that originally called the API (before it was queued to wait) // - pNumBytes - The number of bytes of data that the server/driver will need to transmit back to the client process diff --git a/src/inc/til/io.h b/src/inc/til/io.h index 9e28b99630..bfc9c3bce3 100644 --- a/src/inc/til/io.h +++ b/src/inc/til/io.h @@ -23,7 +23,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // Arguments: // - handle: a HANDLE to the file to check // Return Value: - // - true if it had the expected permissions. False otherwise. + // - true if it had the expected permissions; otherwise, false. _TIL_INLINEPREFIX bool isOwnedByAdministrators(const HANDLE& handle) { // If the file is owned by the administrators group, trust the diff --git a/src/inc/til/small_vector.h b/src/inc/til/small_vector.h index 40bc012ff1..027f0484b5 100644 --- a/src/inc/til/small_vector.h +++ b/src/inc/til/small_vector.h @@ -871,7 +871,7 @@ namespace til // An optimization for the most common vector type which is trivially and copyable and noexcept constructible. // Compared to the complex form below, we don't need the 2 moves and 1 destroy, because is_trivially_copyable_v implies - // that we can just memmove() the items in one fell swoop. We don't need a try/catch either because func() is noexcept. + // that we can just memmove() the items all at once. We don't need a try/catch either because func() is noexcept. if constexpr (noexcept(func(begin())) && std::is_trivially_copyable_v) { _size = new_size; diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp index 8b5647bac6..ecc5c2bc18 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -293,7 +293,7 @@ using namespace Microsoft::Console::Interactivity; // - hwnd: Receives the value of the newly created window's HWND. // - owner: the HWND that should be the initial owner of the pseudo window. // Return Value: -// - STATUS_SUCCESS on success, otherwise an appropriate error. +// - STATUS_SUCCESS on success; otherwise, an appropriate error. [[nodiscard]] NTSTATUS InteractivityFactory::CreatePseudoWindow(HWND& hwnd) { hwnd = nullptr; diff --git a/src/interactivity/win32/WindowMetrics.cpp b/src/interactivity/win32/WindowMetrics.cpp index 637a08feba..f809ebc48d 100644 --- a/src/interactivity/win32/WindowMetrics.cpp +++ b/src/interactivity/win32/WindowMetrics.cpp @@ -75,7 +75,7 @@ til::rect WindowMetrics::GetMaxWindowRectInPixels() // - Gets the maximum possible window rectangle in pixels. Based on the monitor the window is on or the primary monitor if no window exists yet. // Arguments: // - prcSuggested - If we were given a suggested rectangle for where the window is going, we can pass it in here to find out the max size on that monitor. -// - If this value is zero and we had a valid window handle, we'll use that instead. Otherwise the value of 0 will make us use the primary monitor. +// - If this value is zero and we had a valid window handle, we'll use that instead. Otherwise, the value of 0 will make us use the primary monitor. // - pDpiSuggested - The dpi that matches the suggested rect. We will attempt to compute this during the function, but if we fail for some reason, // - the original value passed in will be left untouched. // Return Value: diff --git a/src/propsheet/preview.cpp b/src/propsheet/preview.cpp index 64ed9fae40..9becd7b703 100644 --- a/src/propsheet/preview.cpp +++ b/src/propsheet/preview.cpp @@ -422,7 +422,7 @@ VOID PreviewPaint( * return = n1 * m / n2 * This can be used to make an aspect ration calculation where n1/n2 * is the aspect ratio and m is a known value. The return value will - * be the value that corresponds to m with the correct apsect ratio. + * be the value that corresponds to m with the correct aspect ratio. */ LONG AspectScale( diff --git a/src/propslib/DelegationConfig.cpp b/src/propslib/DelegationConfig.cpp index 29339baa2e..2ba4d85ed5 100644 --- a/src/propslib/DelegationConfig.cpp +++ b/src/propslib/DelegationConfig.cpp @@ -293,7 +293,7 @@ try RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_OpenConsoleKey(¤tUserKey, &consoleKey)); - // Create method for registry is a "create if not exists, otherwise open" function. + // Create method for registry is a "create if not exists; otherwise, open" function. wil::unique_hkey startupKey; RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_CreateKey(consoleKey.get(), L"%%Startup", &startupKey)); diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index d2e9f8bf26..6bebd719e8 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -268,7 +268,7 @@ try } // PaintCursor() is only called when the cursor is visible, but we need to invalidate the cursor area - // even if it isn't. Otherwise a transition from a visible to an invisible cursor wouldn't be rendered. + // even if it isn't. Otherwise, a transition from a visible to an invisible cursor wouldn't be rendered. if (const auto r = _api.invalidatedCursorArea; r.non_empty()) { _p.dirtyRectInPx.left = std::min(_p.dirtyRectInPx.left, r.left * _p.s->font->cellSize.x); diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 2acb128591..60ee6aaf1d 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -2324,7 +2324,7 @@ void BackendD3D::_executeCustomShader(RenderingPayload& p) { // Before we do anything else we have to unbound _renderTargetView from being - // a render target, otherwise we can't use it as a shader resource below. + // a render target; otherwise, we can't use it as a shader resource below. p.deviceContext->OMSetRenderTargets(1, _renderTargetView.addressof(), nullptr); // IA: Input Assembler diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 3f70ae6748..6668e2ce3c 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -265,7 +265,7 @@ namespace Microsoft::Console::Render::Atlas private: // These two functions don't need to use scoped objects or standard allocators, - // since this class is in fact an scoped allocator object itself. + // since this class is in fact a scoped allocator object itself. #pragma warning(push) #pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3). #pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique instead (r.11). diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index c923ef8e3f..9567b8cd49 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -1116,7 +1116,7 @@ bool Renderer::_isInHoveredInterval(const til::point coordTarget) const noexcept // Arguments: // - // Return Value: -// - nullopt if the cursor is off or out-of-frame, otherwise a CursorOptions +// - nullopt if the cursor is off or out-of-frame; otherwise, a CursorOptions void Renderer::_updateCursorInfo() { // Get cursor position in buffer diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index d8f98ef50f..bf583f7850 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -560,7 +560,7 @@ GdiEngine::~GdiEngine() // Arguments: // - newTitle: the new string to use for the title of the window // Return Value: -// - S_OK if PostMessageW succeeded, otherwise E_FAIL +// - S_OK if PostMessageW succeeded; otherwise, E_FAIL [[nodiscard]] HRESULT GdiEngine::_DoUpdateTitle(_In_ const std::wstring_view /*newTitle*/) noexcept { // the CM_UPDATE_TITLE handler in windowproc will query the updated title. diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index da08215756..0e5cf60da8 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -486,7 +486,7 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API return pReceiveMsg; } - // For future code archeologists: GH#2988 + // For future code archaeologists: GH#2988 // // Here, the console calls ConsoleControl(ConsoleSetForeground,...) with a // flag depending on if the console is focused or not. This is surprisingly diff --git a/src/terminal/adapter/FontBuffer.cpp b/src/terminal/adapter/FontBuffer.cpp index dabee138f7..bd0a2e2c94 100644 --- a/src/terminal/adapter/FontBuffer.cpp +++ b/src/terminal/adapter/FontBuffer.cpp @@ -259,7 +259,7 @@ void FontBuffer::_prepareCharacterBuffer() { // If any of the attributes have changed since the last time characters // were downloaded, the font dimensions will need to be recalculated, and - // the buffer will need to be cleared. Otherwise we'll just be adding to + // the buffer will need to be cleared. Otherwise, we'll just be adding to // the existing font, assuming the current dimensions. if (_cellMatrix != _pendingCellMatrix || _cellHeight != _pendingCellHeight || @@ -316,7 +316,7 @@ void FontBuffer::_addSixelValue(const VTInt value) noexcept { if (_currentChar < MAX_CHARS && _sixelColumn < _textWidth) { - // Each sixel updates six pixels of a single column, so we setup a bit + // Each sixel updates six pixels of a single column, so we set up a bit // mask for the column we want to update, and then set that bit in each // row for which there is a corresponding "on" bit in the input value. const auto outputColumnBit = (0x8000 >> (_sixelColumn + _textOffset)); @@ -397,7 +397,7 @@ std::tuple FontBuffer::_calculateDimensions() const } // Now we're going to test whether the dimensions are in range for a number - // of known terminals. We use the declared dimensions if given, otherwise + // of known terminals. We use the declared dimensions if given; otherwise, // estimate the size from the used sixel values. If comparing a sixel-based // height, though, we need to round up the target cell height to account for // the fact that our used height will always be a multiple of six. diff --git a/src/terminal/adapter/MacroBuffer.hpp b/src/terminal/adapter/MacroBuffer.hpp index 92c1c07c73..86e5dfed88 100644 --- a/src/terminal/adapter/MacroBuffer.hpp +++ b/src/terminal/adapter/MacroBuffer.hpp @@ -30,7 +30,7 @@ namespace Microsoft::Console::VirtualTerminal public: // The original DEC terminals only supported 6K of memory, which is // probably a bit low for modern usage. But we also don't want to make - // this value too large, otherwise it could be used in a denial-of- + // this value too large; otherwise, it could be used in a denial-of- // service attack. So for now this is probably a sufficient limit, but // we may need to increase it in the future if we intend to support // macros containing sixel sequences. diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index cb8e4feb5a..23e12f979f 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1009,7 +1009,7 @@ void AdaptDispatch::_ChangeRectOrStreamAttributes(const til::rect& changeArea, c // top line is altered from the left offset up to the end of the line. The // bottom line is altered from the start up to the right offset. All the // lines in-between have their entire length altered. The right coordinate - // must be greater than the left, otherwise the operation is ignored. + // must be greater than the left; otherwise, the operation is ignored. else if (lineCount > 1 && changeRect.right > changeRect.left) { const auto pageWidth = page.Width(); @@ -1322,7 +1322,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p // As part of the checksum, we need to include the color indices of each // cell, and in the case of default colors, those indices come from the // color alias table. But if they're not in the bottom 16 range, we just - // fallback to using white on black (7 and 0). + // fall back to using white on black (7 and 0). auto defaultFgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultForeground); auto defaultBgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultBackground); defaultFgIndex = defaultFgIndex < 16 ? defaultFgIndex : 7; @@ -2392,7 +2392,9 @@ void AdaptDispatch::CarriageReturn() // Arguments: // - page - Target page on which the line feed is executed. // - withReturn - Set to true if a carriage return should be performed as well. -// - wrapForced - Set to true is the line feed was the result of the line wrapping. if the viewport panned down. False if not. +// - wrapForced - Set to true if the line feed was the result of the line wrapping. +// Return Value: +// - true if the viewport panned down; otherwise, false. bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const bool wrapForced) { auto& textBuffer = page.Buffer(); @@ -2468,7 +2470,7 @@ bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const b _api.NotifyBufferRotation(1); // We trigger a scroll rather than a redraw, since that's more efficient, - // but we need to turn the cursor off before doing so, otherwise a ghost + // but we need to turn the cursor off before doing so; otherwise, a ghost // cursor can be left behind in the previous position. cursor.SetIsOn(false); textBuffer.TriggerScroll({ 0, -1 }); @@ -2882,7 +2884,7 @@ void AdaptDispatch::AcceptC1Controls(const bool enabled) void AdaptDispatch::SendC1Controls(const bool enabled) { // If this is an attempt to enable C1 controls, the input code page must be - // one of the DOCS choices (UTF-8 or ISO-8859-1), otherwise there's a risk + // one of the DOCS choices (UTF-8 or ISO-8859-1); otherwise, there's a risk // that those controls won't have a valid encoding. const auto codepage = _api.GetInputCodePage(); if (enabled == false || codepage == CP_UTF8 || codepage == 28591) @@ -3354,7 +3356,7 @@ void AdaptDispatch::SetXtermColorResource(const size_t resource, const DWORD col // Method Description: // - Reports the value of one Xterm Color Resource, if it is set. // Return Value: -// True if handled successfully. False otherwise. +// - true if handled successfully; otherwise, false. void AdaptDispatch::RequestXtermColorResource(const size_t resource) { assert(resource >= 10); @@ -4187,7 +4189,7 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting() { // Although we don't yet support any operations with parameter // prefixes, it's important that we still parse the prefix and - // include it in the ID. Otherwise we'll mistakenly respond to + // include it in the ID. Otherwise, we'll mistakenly respond to // prefixed queries that we don't actually recognise. const auto isParameterPrefix = ch >= L'<' && ch <= L'?'; const auto isParameter = ch >= L'0' && ch < L'9'; diff --git a/src/terminal/adapter/ut_adapter/inputTest.cpp b/src/terminal/adapter/ut_adapter/inputTest.cpp index ab893be772..d672004cd1 100644 --- a/src/terminal/adapter/ut_adapter/inputTest.cpp +++ b/src/terminal/adapter/ut_adapter/inputTest.cpp @@ -78,7 +78,7 @@ public: irTest.Event.KeyEvent.bKeyDown = TRUE; // If we want to test a key with the Right Alt modifier, we must generate - // an event for the Alt key first, otherwise the modifier will be dropped. + // an event for the Alt key first; otherwise, the modifier will be dropped. if (WI_IsFlagSet(uiKeystate, RIGHT_ALT_PRESSED)) { irTest.Event.KeyEvent.wVirtualKeyCode = VK_MENU; diff --git a/src/terminal/input/mouseInput.cpp b/src/terminal/input/mouseInput.cpp index c36ce160b4..68ef6ed601 100644 --- a/src/terminal/input/mouseInput.cpp +++ b/src/terminal/input/mouseInput.cpp @@ -269,7 +269,7 @@ static constexpr wchar_t _encodeDefaultCoordinate(const til::CoordType sCoordina // Parameters: // - // Return value: -// - true, if we are tracking mouse input. False, otherwise +// - true, if we are tracking mouse input; otherwise, false. bool TerminalInput::IsTrackingMouseInput() const noexcept { return _inputMode.any(Mode::DefaultMouseTracking, Mode::ButtonEventMouseTracking, Mode::AnyEventMouseTracking); diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index ac0795bdc4..65dfa6f33c 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -171,7 +171,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event) if (matchingLastKeyPress && !_inputMode.test(Mode::AutoRepeat)) { // Note that we must return an empty string here to imply that we've handled - // the event, otherwise the key press can still end up being submitted. + // the event; otherwise, the key press can still end up being submitted. return _makeNoOutput(); } _lastVirtualKeyCode = virtualKeyCode; diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 12954512bd..c45e2fc2f4 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -1511,7 +1511,7 @@ void StateMachine::_EventOscString(const wchar_t wch) // - Handle the two-character termination of a OSC sequence. // Events in this state will: // 1. Trigger the OSC action associated with the param on an OscTerminator -// 2. Otherwise treat this as a normal escape character event. +// 2. Otherwise, treat this as a normal escape character event. // Arguments: // - wch - Character that triggered the event // Return Value: diff --git a/src/tools/U8U16Test/U8U16Test.cpp b/src/tools/U8U16Test/U8U16Test.cpp index 89ed982453..5c4d6a2f10 100644 --- a/src/tools/U8U16Test/U8U16Test.cpp +++ b/src/tools/U8U16Test/U8U16Test.cpp @@ -57,7 +57,7 @@ u8state::u8state() noexcept : { // If the Lead Byte indicates that the last bytes in the string is a partial UTF-8 code point then cache them: // Use the bitmask at index `sequenceLen`. Compare the result with the operand having the same index. If they - // are not equal then the sequence has to be cached because it is a partial code point. Otherwise the + // are not equal then the sequence has to be cached because it is a partial code point. Otherwise, the // sequence is a complete UTF-8 code point and the whole string is ready for the conversion to hstring. if ((*backIter & _cmpMasks.at(sequenceLen)) != _cmpOperands.at(sequenceLen)) { diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 9b3d7040a5..d91c57a08d 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -568,7 +568,7 @@ try // - the anchors have been populated // This means that we've found a contiguous range where the text attribute was found. // No point in searching through the rest of the search space. - // TLDR: keep updating the second anchor and make the range wider until the attribute changes. + // TL;DR: keep updating the second anchor and make the range wider until the attribute changes. break; } } @@ -663,7 +663,7 @@ CATCH_RETURN(); // - pRetVal - the attributeId's sub-type for the first cell in the range (i.e. foreground color) // - attr - the text attribute we're checking // Return Value: -// - true, if the attributeId is supported. false, otherwise. +// - true, if the attributeId is supported. Otherwise, false. // - pRetVal is populated with the appropriate response relevant to the returned bool. bool UiaTextRangeBase::_initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal, const TextAttribute& attr) const { @@ -1310,7 +1310,7 @@ til::CoordType UiaTextRangeBase::_getViewportHeight(const til::inclusive_rect& v { assert(viewport.bottom >= viewport.top); // + 1 because til::inclusive_rect is inclusive on both sides so subtracting top - // and bottom gets rid of 1 more then it should. + // and bottom gets rid of 1 more, then it should. return viewport.bottom - viewport.top + 1; } diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 79c97ddaa2..144ea987a8 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -417,7 +417,7 @@ static void _ClosePseudoConsole(_In_ PseudoConsole* pPty) noexcept // INHERIT_CURSOR: This will cause the created conpty to attempt to inherit the // cursor position of the parent terminal application. This can be useful // for applications like `ssh`, where ssh (currently running in a terminal) -// might want to create a pseudoterminal session for an child application +// might want to create a pseudoterminal session for a child application // and the child inherit the cursor position of ssh. // The created conpty will immediately emit a "Device Status Request" VT // sequence to hOutput, that should be replied to on hInput in the format diff --git a/tools/GenerateAppxFromManifest.ps1 b/tools/GenerateAppxFromManifest.ps1 index 3bf0bba826..a52c1cfc15 100644 --- a/tools/GenerateAppxFromManifest.ps1 +++ b/tools/GenerateAppxFromManifest.ps1 @@ -21,7 +21,7 @@ param ( [xml]$appxPrototypeData = Get-Content $AppxManifestPrototype # You need to make sure each element we add is part of the same namespace as the -# Package, otherwise powershell will append a bunch of `xmlns=""` properties +# Package; otherwise, powershell will append a bunch of `xmlns=""` properties # that will make the appx deployment reject the manifest. $rootNS = $appxPrototypeData.Package.NamespaceURI @@ -38,7 +38,7 @@ $files | ForEach-Object { $InProcessServer = $appxPrototypeData.CreateNode("element", "InProcessServer", $rootNS) $Path = $appxPrototypeData.CreateNode("element", "Path", $rootNS) - # You need to stash the result here, otherwise a blank line will be echoed to + # You need to stash the result here; otherwise, a blank line will be echoed to # the console. $placeholder = $Path.InnerText = $_.name diff --git a/tools/README.md b/tools/README.md index 94e841a0b1..f69efb3a21 100644 --- a/tools/README.md +++ b/tools/README.md @@ -30,7 +30,7 @@ the `%DEFAULT_CONFIGURATION%` configuration, which is `Debug` if you use `razzle ## opencon (and openbash, openps) `opencon` can be used to launch the **last built** OpenConsole binary. If given an -argument, it will try and run that program in the launched window. Otherwise it +argument, it will try and run that program in the launched window. Otherwise, it will default to cmd.exe. `openbash` is similar, it immediately launches bash.exe (the Windows Subsystem From 6e1f4a72bed24514943d88d04a368ba9465c7ad7 Mon Sep 17 00:00:00 2001 From: James Pack Date: Tue, 24 Jun 2025 16:56:49 -0400 Subject: [PATCH 089/177] Add arm64 build support to the build script (#18946) It built successfully following the documentation on my Snapdragon Surface Laptop :) --- tools/OpenConsole.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/OpenConsole.psm1 b/tools/OpenConsole.psm1 index 05fc5af4c2..c11d46570f 100644 --- a/tools/OpenConsole.psm1 +++ b/tools/OpenConsole.psm1 @@ -83,6 +83,7 @@ function Set-MsbuildDevEnvironment switch ($env:PROCESSOR_ARCHITECTURE) { "amd64" { $arch = "x64" } "x86" { $arch = "x86" } + "arm64" { $arch = "arm64" } default { throw "Unknown architecture: $switch" } } From cf95460a2622f33e48303aeb2b9cc03a9ee324e7 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 24 Jun 2025 16:33:27 -0500 Subject: [PATCH 090/177] Try to get the client name during DefTerm handoff (#19014) --- src/cascadia/TerminalConnection/ConptyConnection.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 613ad50c91..5778c0b6ed 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -313,6 +313,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } CATCH_LOG() + try + { + auto processImageName{ wil::QueryFullProcessImageNameW(_piClient.hProcess) }; + _clientName = std::filesystem::path{ std::move(processImageName) }.filename().wstring(); + } + CATCH_LOG() + _pipe = std::move(pipe.server); *in = pipe.client.release(); *out = pipeClientClone.release(); From 6bf315a4c904fbc01187e155a55117dfd7e7f893 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Jun 2025 14:58:33 -0700 Subject: [PATCH 091/177] Fix crash when closing multiple panes simultaneously (#19023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a crash when multiple panes were closed simultaneously (i.e. using broadcast input). The root cause of this crash was that we would get a null pointer exception when trying to access a member/function off of a null `_content`. This is because `Pane::_CloseChild()` would always pass over the content from the non-closed pane and attempt to hook everything up to it. The fix was to operate similarly to `Pane::Close()` and raise a `Closed` event and return early. Since there's no alternative content to attach to, might as well just close the entire pane. This propagates up the stack of listeners to update the UI appropriately and close the parent pane and eventually the entire tab, if necessary.   Closes #18071 Closes #17432 ## Validation Steps Performed 1. Open 2 panes 2. Use broadcast input to send "exit" to both panes 3. ✅ Terminal doesn't crash and the tab closes gracefully --- src/cascadia/TerminalApp/Pane.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 30ca4714eb..d8c7cd47d8 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -1414,6 +1414,13 @@ void Pane::_CloseChild(const bool closeFirst) // take the control, profile, id and isDefTermSession of the pane that _wasn't_ closed. _setPaneContent(remainingChild->_takePaneContent()); + if (!_content) + { + // GH#18071: our content is still null after taking the other pane's content, + // so just notify our parent that we're closed. + Closed.raise(nullptr, nullptr); + return; + } _id = remainingChild->Id(); // Revoke the old event handlers. Remove both the handlers for the panes From fc0a06c3b6cae2ede1326eef202a800244365ebd Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 25 Jun 2025 18:31:28 +0200 Subject: [PATCH 092/177] Preserve the cursor row during Clear Buffer (#18976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces an ABI change to the ConptyClearPseudoConsole signal. Otherwise, we have to make it so that the API call always retains the row the cursor is on, but I feel like that makes it worse. Closes #18732 Closes #18878 ## Validation Steps Performed * Launch `ConsoleMonitor.exe` * Create some text above & below the cursor in PowerShell * Clear Buffer * Buffer is cleared except for the cursor row ✅ * ...same in ConPTY ✅ --- .../TerminalConnection/ConptyConnection.cpp | 4 +- .../TerminalConnection/ConptyConnection.h | 2 +- .../TerminalConnection/ConptyConnection.idl | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 52 +++++++++++++------ .../UnitTests_Control/ControlCoreTests.cpp | 10 ++-- src/host/PtySignalInputThread.cpp | 17 ++++-- src/host/PtySignalInputThread.hpp | 7 ++- src/inc/conpty-static.h | 2 +- src/winconpty/winconpty.cpp | 9 ++-- src/winconpty/winconpty.h | 1 - 10 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 5778c0b6ed..6f15a8c1ed 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -563,13 +563,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } - void ConptyConnection::ClearBuffer() + void ConptyConnection::ClearBuffer(bool keepCursorRow) { // If we haven't connected yet, then we really don't need to do // anything. The connection should already start clear! if (_isConnected()) { - THROW_IF_FAILED(ConptyClearPseudoConsole(_hPC.get())); + THROW_IF_FAILED(ConptyClearPseudoConsole(_hPC.get(), keepCursorRow)); } } diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 69edd7b7a8..4d847fdcd1 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -25,7 +25,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void Resize(uint32_t rows, uint32_t columns); void ResetSize(); void Close() noexcept; - void ClearBuffer(); + void ClearBuffer(bool keepCursorRow); void ShowHide(const bool show); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index e1ed83cc0c..5264238c79 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -15,7 +15,7 @@ namespace Microsoft.Terminal.TerminalConnection UInt16 ShowWindow { get; }; void ResetSize(); - void ClearBuffer(); + void ClearBuffer(Boolean keepCursorRow); void ShowHide(Boolean show); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b2ad7b7549..6a4147637d 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -2264,23 +2264,42 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::ClearBuffer(Control::ClearBufferType clearType) { - std::wstring_view command; - switch (clearType) - { - case ClearBufferType::Screen: - command = L"\x1b[H\x1b[2J"; - break; - case ClearBufferType::Scrollback: - command = L"\x1b[3J"; - break; - case ClearBufferType::All: - command = L"\x1b[H\x1b[2J\x1b[3J"; - break; - } - { const auto lock = _terminal->LockForWriting(); - _terminal->Write(command); + // In absolute buffer coordinates, including the scrollback (= Y is offset by the scrollback height). + const auto viewport = _terminal->GetViewport(); + // The absolute cursor coordinate. + const auto cursor = _terminal->GetViewportRelativeCursorPosition(); + + // GH#18732: Users want the row the cursor is on to be preserved across clears. + std::wstring sequence; + + if (clearType == ClearBufferType::Scrollback || clearType == ClearBufferType::All) + { + sequence.append(L"\x1b[3J"); + } + + if (clearType == ClearBufferType::Screen || clearType == ClearBufferType::All) + { + // Erase any viewport contents below (but not including) the cursor row. + if (viewport.Height() - cursor.y > 1) + { + fmt::format_to(std::back_inserter(sequence), FMT_COMPILE(L"\x1b[{};1H\x1b[J"), cursor.y + 2); + } + + // Erase any viewport contents above (but not including) the cursor row. + if (cursor.y > 0) + { + // An SU sequence would be simpler than this DL sequence, + // but SU isn't well standardized between terminals. + // Generally speaking, it's best avoiding it. + fmt::format_to(std::back_inserter(sequence), FMT_COMPILE(L"\x1b[H\x1b[{}M"), cursor.y); + } + + fmt::format_to(std::back_inserter(sequence), FMT_COMPILE(L"\x1b[1;{}H"), cursor.x + 1); + } + + _terminal->Write(sequence); } if (clearType == Control::ClearBufferType::Screen || clearType == Control::ClearBufferType::All) @@ -2289,8 +2308,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // Since the clearing of ConPTY occurs asynchronously, this call can result weird issues, // where a console application still sees contents that we've already deleted, etc. - // The correct way would be for ConPTY to emit the appropriate CSI n J sequences. - conpty.ClearBuffer(); + conpty.ClearBuffer(true); } } } diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index 999809cb39..69b00b69db 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -248,7 +248,7 @@ namespace ControlUnitTests _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " - L"(leaving the cursor afer 'Bar')"); + L"(leaving the cursor after 'Bar')"); for (auto i = 0; i < 40; ++i) { conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n")); @@ -285,7 +285,7 @@ namespace ControlUnitTests _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " - L"(leaving the cursor afer 'Bar')"); + L"(leaving the cursor after 'Bar')"); for (auto i = 0; i < 40; ++i) { conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n")); @@ -304,9 +304,9 @@ namespace ControlUnitTests Log::Comment(L"Check the buffer after the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); - VERIFY_ARE_EQUAL(41, core->ScrollOffset()); + VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); - VERIFY_ARE_EQUAL(61, core->BufferHeight()); + VERIFY_ARE_EQUAL(41, core->BufferHeight()); // In this test, we can't actually check if we cleared the buffer // contents. ConPTY will handle the actual clearing of the buffer @@ -322,7 +322,7 @@ namespace ControlUnitTests _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " - L"(leaving the cursor afer 'Bar')"); + L"(leaving the cursor after 'Bar')"); for (auto i = 0; i < 40; ++i) { conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n")); diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index 2829ebdbcc..0b03612d9a 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -124,7 +124,13 @@ try } case PtySignal::ClearBuffer: { - _DoClearBuffer(); + ClearBufferData msg = { 0 }; + if (!_GetData(&msg, sizeof(msg))) + { + return S_OK; + } + + _DoClearBuffer(msg.keepCursorRow != 0); break; } case PtySignal::ResizeWindow: @@ -180,7 +186,7 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data) _api.ResizeWindow(data.sx, data.sy); } -void PtySignalInputThread::_DoClearBuffer() const +void PtySignalInputThread::_DoClearBuffer(const bool keepCursorRow) const { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); @@ -196,8 +202,11 @@ void PtySignalInputThread::_DoClearBuffer() const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto& screenInfo = gci.GetActiveOutputBuffer(); - auto& stateMachine = screenInfo.GetStateMachine(); - stateMachine.ProcessString(L"\x1b[H\x1b[2J"); + auto& tb = screenInfo.GetTextBuffer(); + const auto cursor = tb.GetCursor().GetPosition(); + + tb.ClearScrollback(cursor.y, keepCursorRow ? 1 : 0); + tb.GetCursor().SetPosition({ keepCursorRow ? cursor.x : 0, 0 }); } void PtySignalInputThread::_DoShowHide(const ShowHideData& data) diff --git a/src/host/PtySignalInputThread.hpp b/src/host/PtySignalInputThread.hpp index eead316ba9..9ff4fb0c22 100644 --- a/src/host/PtySignalInputThread.hpp +++ b/src/host/PtySignalInputThread.hpp @@ -55,6 +55,11 @@ namespace Microsoft::Console unsigned short show; // used as a bool, but passed as a ushort }; + struct ClearBufferData + { + unsigned short keepCursorRow; + }; + struct SetParentData { uint64_t handle; @@ -64,7 +69,7 @@ namespace Microsoft::Console [[nodiscard]] bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer); void _DoResizeWindow(const ResizeWindowData& data); void _DoSetWindowParent(const SetParentData& data); - void _DoClearBuffer() const; + void _DoClearBuffer(bool keepCursorRow) const; void _DoShowHide(const ShowHideData& data); void _Shutdown(); diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index aa31c67fa3..3aca15dba7 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -38,7 +38,7 @@ CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); CONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size); -CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC); +CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC, BOOL keepCursorRow); CONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show); CONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent); CONPTY_EXPORT HRESULT WINAPI ConptyReleasePseudoConsole(HPCON hPC); diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 144ea987a8..969a8fbdfe 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -278,15 +278,16 @@ HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const CO // Return Value: // - S_OK if the call succeeded, else an appropriate HRESULT for failing to // write the clear message to the pty. -HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty) +static HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty, BOOL keepCursorRow) noexcept { if (pPty == nullptr) { return E_INVALIDARG; } - unsigned short signalPacket[1]; + unsigned short signalPacket[2]; signalPacket[0] = PTY_SIGNAL_CLEAR_WINDOW; + signalPacket[1] = keepCursorRow ? 1 : 0; const auto fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr); return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); @@ -492,13 +493,13 @@ extern "C" HRESULT WINAPI ConptyResizePseudoConsole(_In_ HPCON hPC, _In_ COORD s // - This is used exclusively by ConPTY to support GH#1193, GH#1882. This allows // a terminal to clear the contents of the ConPTY buffer, which is important // if the user would like to be able to clear the terminal-side buffer. -extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC) +extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC, BOOL keepCursorRow) { const PseudoConsole* const pPty = (PseudoConsole*)hPC; auto hr = pPty == nullptr ? E_INVALIDARG : S_OK; if (SUCCEEDED(hr)) { - hr = _ClearPseudoConsole(pPty); + hr = _ClearPseudoConsole(pPty, keepCursorRow); } return hr; } diff --git a/src/winconpty/winconpty.h b/src/winconpty/winconpty.h index 4bb43ddf8f..8a245226ca 100644 --- a/src/winconpty/winconpty.h +++ b/src/winconpty/winconpty.h @@ -68,7 +68,6 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, _Inout_ PseudoConsole* pPty); HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size); -HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty); HRESULT _ShowHidePseudoConsole(_In_ const PseudoConsole* const pPty, const bool show); HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const HWND newParent); void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty); From a25d968fe0026ac5abea62a67d798c89e1280d32 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 1 Jul 2025 21:00:00 +0200 Subject: [PATCH 093/177] Move ConPTY handoff logic into WindowEmperor (#19088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the ConPTY handoff COM server from `REGCLS_SINGLEUSE` to `REGCLS_MULTIPLEUSE`. The former causes a race condition, because handoff runs concurrently with the creation of WinUI windows. This can then result in the a window getting the wrong handoff. It then moves the "root" of ConPTY handoff from `TerminalPage` (WindowEmperor -> AppHost -> TerminalWindow -> TerminalPage) into `WindowEmperor` (WindowEmperor). Closes #19049 ## Validation Steps Performed * Launching cmd from the Start Menu shows a "Command Prompt" tab ✅ * Win+R -> `cmd` creates windows in the foreground ✅ * Win+R -> `cmd /c start /max cmd` creates a fullscreen tab ✅ * This even works for multiple windows, unlike with Canary ✅ * Win+R -> `cmd /c start /min cmd` does not work ❌ * It also doesn't work in Canary, so it's not a bug in this PR ✅ --- .../TerminalApp/AppActionHandlers.cpp | 2 +- .../TerminalApp/AppCommandlineArgs.cpp | 69 +++----- src/cascadia/TerminalApp/AppCommandlineArgs.h | 2 - src/cascadia/TerminalApp/Remoting.cpp | 18 +- src/cascadia/TerminalApp/Remoting.h | 10 +- src/cascadia/TerminalApp/Remoting.idl | 8 +- src/cascadia/TerminalApp/TerminalPage.cpp | 160 ++++-------------- src/cascadia/TerminalApp/TerminalPage.h | 11 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 47 ++--- src/cascadia/TerminalApp/TerminalWindow.h | 1 + .../TerminalConnection/CTerminalHandoff.cpp | 46 ++--- src/cascadia/WindowsTerminal/AppHost.cpp | 5 + src/cascadia/WindowsTerminal/IslandWindow.cpp | 19 ++- .../WindowsTerminal/WindowEmperor.cpp | 56 ++++-- src/cascadia/WindowsTerminal/WindowEmperor.h | 2 +- src/cascadia/WindowsTerminal/pch.h | 3 +- 16 files changed, 166 insertions(+), 293 deletions(-) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 50bd7cd122..afb3e0a6e5 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -756,7 +756,7 @@ namespace winrt::TerminalApp::implementation if (!actions.empty()) { actionArgs.Handled(true); - ProcessStartupActions(std::move(actions), false); + ProcessStartupActions(std::move(actions)); } } } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 1e4b7a3b10..1b7881dc27 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -961,18 +961,6 @@ std::vector& AppCommandlineArgs::GetStartupActions() return _startupActions; } -// Method Description: -// - Returns whether we should start listening for inbound PTY connections -// coming from the operating system default application feature. -// Arguments: -// - -// Return Value: -// - True if the listener should be started. False otherwise. -bool AppCommandlineArgs::IsHandoffListener() const noexcept -{ - return _isHandoffListener; -} - // Method Description: // - Get the string of text that should be displayed to the user on exit. This // is usually helpful for cases where the user entered some sort of invalid @@ -1015,34 +1003,28 @@ bool AppCommandlineArgs::ShouldExitEarly() const noexcept // - void AppCommandlineArgs::ValidateStartupCommands() { - // Only check over the actions list for the potential to add a new-tab - // command if we are not starting for the purposes of receiving an inbound - // handoff connection from the operating system. - if (!_isHandoffListener) + // If we only have a single x-save command, then set our target to the + // current terminal window. This will prevent us from spawning a new + // window just to save the commandline. + if (_startupActions.size() == 1 && + _startupActions.front().Action() == ShortcutAction::SaveSnippet && + _windowTarget.empty()) { - // If we only have a single x-save command, then set our target to the - // current terminal window. This will prevent us from spawning a new - // window just to save the commandline. - if (_startupActions.size() == 1 && - _startupActions.front().Action() == ShortcutAction::SaveSnippet && - _windowTarget.empty()) - { - _windowTarget = "0"; - } - // If we parsed no commands, or the first command we've parsed is not a new - // tab action, prepend a new-tab command to the front of the list. - // (also, we don't need to do this if the only action is a x-save) - else if (_startupActions.empty() || - (_startupActions.front().Action() != ShortcutAction::NewTab && - _startupActions.front().Action() != ShortcutAction::SaveSnippet)) - { - // Build the NewTab action from the values we've parsed on the commandline. - NewTerminalArgs newTerminalArgs{}; - NewTabArgs args{ newTerminalArgs }; - ActionAndArgs newTabAction{ ShortcutAction::NewTab, args }; - // push the arg onto the front - _startupActions.insert(_startupActions.begin(), 1, newTabAction); - } + _windowTarget = "0"; + } + // If we parsed no commands, or the first command we've parsed is not a new + // tab action, prepend a new-tab command to the front of the list. + // (also, we don't need to do this if the only action is a x-save) + else if (_startupActions.empty() || + (_startupActions.front().Action() != ShortcutAction::NewTab && + _startupActions.front().Action() != ShortcutAction::SaveSnippet)) + { + // Build the NewTab action from the values we've parsed on the commandline. + NewTerminalArgs newTerminalArgs{}; + NewTabArgs args{ newTerminalArgs }; + ActionAndArgs newTabAction{ ShortcutAction::NewTab, args }; + // push the arg onto the front + _startupActions.insert(_startupActions.begin(), 1, newTabAction); } } std::optional AppCommandlineArgs::GetPersistedLayoutIdx() const noexcept @@ -1082,13 +1064,9 @@ std::optional AppCommandlineArgs::GetSize() const noexcept // - 0 if the commandline was successfully parsed int AppCommandlineArgs::ParseArgs(winrt::array_view args) { - for (const auto& arg : args) + if (args.size() == 2 && args[1] == L"-Embedding") { - if (arg == L"-Embedding") - { - _isHandoffListener = true; - return 0; - } + return 0; } auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args); @@ -1195,7 +1173,6 @@ void AppCommandlineArgs::FullResetState() _startupActions.clear(); _exitMessage = ""; _shouldExitEarly = false; - _isHandoffListener = false; _windowTarget = {}; } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index 9f580e957b..0a9de3999c 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -35,7 +35,6 @@ public: void ValidateStartupCommands(); std::vector& GetStartupActions(); - bool IsHandoffListener() const noexcept; const std::string& GetExitMessage() const noexcept; bool ShouldExitEarly() const noexcept; @@ -132,7 +131,6 @@ private: std::optional _launchMode{ std::nullopt }; std::optional _position{ std::nullopt }; std::optional _size{ std::nullopt }; - bool _isHandoffListener{ false }; std::vector _startupActions; std::string _exitMessage; bool _shouldExitEarly{ false }; diff --git a/src/cascadia/TerminalApp/Remoting.cpp b/src/cascadia/TerminalApp/Remoting.cpp index 0c8693ed46..078a0d7f11 100644 --- a/src/cascadia/TerminalApp/Remoting.cpp +++ b/src/cascadia/TerminalApp/Remoting.cpp @@ -15,19 +15,6 @@ using namespace winrt::Windows::Foundation; namespace winrt::TerminalApp::implementation { - CommandlineArgs::CommandlineArgs(winrt::array_view args, winrt::hstring currentDirectory, uint32_t showWindowCommand, winrt::hstring envString) : - _args{ args.begin(), args.end() }, - CurrentDirectory{ std::move(currentDirectory) }, - ShowWindowCommand{ showWindowCommand }, - CurrentEnvironment{ std::move(envString) } - { - _parseResult = _parsed.ParseArgs(_args); - if (_parseResult == 0) - { - _parsed.ValidateStartupCommands(); - } - } - ::TerminalApp::AppCommandlineArgs& CommandlineArgs::ParsedArgs() noexcept { return _parsed; @@ -56,6 +43,11 @@ namespace winrt::TerminalApp::implementation void CommandlineArgs::Commandline(const winrt::array_view& value) { _args = { value.begin(), value.end() }; + _parseResult = _parsed.ParseArgs(_args); + if (_parseResult == 0) + { + _parsed.ValidateStartupCommands(); + } } winrt::com_array CommandlineArgs::Commandline() diff --git a/src/cascadia/TerminalApp/Remoting.h b/src/cascadia/TerminalApp/Remoting.h index 2ad297f31e..9d4a48df76 100644 --- a/src/cascadia/TerminalApp/Remoting.h +++ b/src/cascadia/TerminalApp/Remoting.h @@ -13,21 +13,17 @@ namespace winrt::TerminalApp::implementation { struct CommandlineArgs : public CommandlineArgsT { - CommandlineArgs() = default; - CommandlineArgs(winrt::array_view args, winrt::hstring currentDirectory, uint32_t showWindowCommand, winrt::hstring envString); - ::TerminalApp::AppCommandlineArgs& ParsedArgs() noexcept; winrt::com_array& CommandlineRef() noexcept; // These bits are exposed via WinRT: - public: int32_t ExitCode() const noexcept; winrt::hstring ExitMessage() const; winrt::hstring TargetWindow() const; + til::property Connection; void Commandline(const winrt::array_view& value); winrt::com_array Commandline(); - til::property CurrentDirectory; til::property CurrentEnvironment; til::property ShowWindowCommand{ static_cast(SW_NORMAL) }; // SW_NORMAL is 1, 0 is SW_HIDE @@ -86,11 +82,9 @@ namespace winrt::TerminalApp::implementation WINRT_PROPERTY(uint64_t, Id); WINRT_PROPERTY(winrt::hstring, WindowName); - WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command); + WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command, nullptr); WINRT_PROPERTY(winrt::hstring, Content); WINRT_PROPERTY(Windows::Foundation::IReference, InitialBounds); - - private: }; } diff --git a/src/cascadia/TerminalApp/Remoting.idl b/src/cascadia/TerminalApp/Remoting.idl index 17f1318422..07fde64e50 100644 --- a/src/cascadia/TerminalApp/Remoting.idl +++ b/src/cascadia/TerminalApp/Remoting.idl @@ -6,16 +6,16 @@ namespace TerminalApp runtimeclass CommandlineArgs { CommandlineArgs(); - CommandlineArgs(String[] args, String cwd, UInt32 showWindowCommand, String env); Int32 ExitCode { get; }; String ExitMessage { get; }; String TargetWindow { get; }; + Microsoft.Terminal.TerminalConnection.ITerminalConnection Connection; String[] Commandline; - String CurrentDirectory { get; }; - UInt32 ShowWindowCommand { get; }; - String CurrentEnvironment { get; }; + String CurrentDirectory; + UInt32 ShowWindowCommand; + String CurrentEnvironment; }; enum MonitorBehavior diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index f900e05cb7..1de587c26f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include "../../types/inc/utils.hpp" @@ -293,12 +292,11 @@ namespace winrt::TerminalApp::implementation // - true if we're not elevated but all relevant pane-spawning actions are elevated bool TerminalPage::ShouldImmediatelyHandoffToElevated(const CascadiaSettings& settings) const { - // GH#12267: Don't forget about defterm handoff here. If we're being - // created for embedding, then _yea_, we don't need to handoff to an - // elevated window. - if (_startupActions.empty() || IsRunningElevated() || _shouldStartInboundListener) + if (_startupActions.empty() || _startupConnection || IsRunningElevated()) { - // there aren't startup actions, or we're elevated. In that case, go for it. + // No point in handing off if we got no startup actions, or we're already elevated. + // Also, we shouldn't need to elevate handoff ConPTY connections. + assert(!_startupConnection); return false; } @@ -488,46 +486,16 @@ namespace winrt::TerminalApp::implementation { _startupState = StartupState::InStartup; - ProcessStartupActions(std::move(_startupActions), true); - - // If we were told that the COM server needs to be started to listen for incoming - // default application connections, start it now. - // This MUST be done after we've registered the event listener for the new connections - // or the COM server might start receiving requests on another thread and dispatch - // them to nowhere. - _StartInboundListener(); - } - } - - // Routine Description: - // - Will start the listener for inbound console handoffs if we have already determined - // that we should do so. - // NOTE: Must be after TerminalPage::_OnNewConnection has been connected up. - // Arguments: - // - - Looks at _shouldStartInboundListener - // Return Value: - // - - May fail fast if setup fails as that would leave us in a weird state. - void TerminalPage::_StartInboundListener() - { - if (_shouldStartInboundListener) - { - _shouldStartInboundListener = false; - - // Hook up inbound connection event handler - _newConnectionRevoker = ConptyConnection::NewConnection(winrt::auto_revoke, { this, &TerminalPage::_OnNewConnection }); - - try + if (_startupConnection) { - winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener(); + CreateTabFromConnection(std::move(_startupConnection)); } - // If we failed to start the listener, it will throw. - // We don't want to fail fast here because if a peasant has some trouble with - // starting the listener, we don't want it to crash and take all its tabs down - // with it. - catch (...) + else if (!_startupActions.empty()) { - LOG_CAUGHT_EXCEPTION(); + ProcessStartupActions(std::move(_startupActions)); } + + _CompleteInitialization(); } } @@ -545,7 +513,7 @@ namespace winrt::TerminalApp::implementation // nt -d .` from inside another directory to work as expected. // Return Value: // - - safe_void_coroutine TerminalPage::ProcessStartupActions(std::vector actions, const bool initial, const winrt::hstring cwd, const winrt::hstring env) + safe_void_coroutine TerminalPage::ProcessStartupActions(std::vector actions, const winrt::hstring cwd, const winrt::hstring env) { const auto strong = get_strong(); @@ -597,11 +565,29 @@ namespace winrt::TerminalApp::implementation content.Focus(FocusState::Programmatic); } } + } - if (initial) + void TerminalPage::CreateTabFromConnection(ITerminalConnection connection) + { + NewTerminalArgs newTerminalArgs; + + if (const auto conpty = connection.try_as()) { - _CompleteInitialization(); + newTerminalArgs.Commandline(conpty.Commandline()); + newTerminalArgs.TabTitle(conpty.StartingTitle()); } + + // GH #12370: We absolutely cannot allow a defterm connection to + // auto-elevate. Defterm doesn't work for elevated scenarios in the + // first place. If we try accepting the connection, the spawning an + // elevated version of the Terminal with that profile... that's a + // recipe for disaster. We won't ever open up a tab in this window. + newTerminalArgs.Elevate(false); + const auto newPane = _MakePane(newTerminalArgs, nullptr, std::move(connection)); + newPane->WalkTree([](const auto& pane) { + pane->FinalizeConfigurationGivenDefault(); + }); + _CreateNewTabFromPane(newPane); } // Method Description: @@ -629,7 +615,7 @@ namespace winrt::TerminalApp::implementation // GH#12267: Make sure that we don't instantly close ourselves when // we're readying to accept a defterm connection. In that case, we don't // have a tab yet, but will once we're initialized. - if (_tabs.Size() == 0 && !_shouldStartInboundListener && !_isEmbeddingInboundListener) + if (_tabs.Size() == 0) { CloseWindowRequested.raise(*this, nullptr); co_return; @@ -3653,25 +3639,9 @@ namespace winrt::TerminalApp::implementation _startupActions = std::move(actions); } - // Routine Description: - // - Notifies this Terminal Page that it should start the incoming connection - // listener for command-line tools attempting to join this Terminal - // through the default application channel. - // Arguments: - // - isEmbedding - True if COM started us to be a server. False if we're doing it of our own accord. - // Return Value: - // - - void TerminalPage::SetInboundListener(bool isEmbedding) + void TerminalPage::SetStartupConnection(ITerminalConnection connection) { - _shouldStartInboundListener = true; - _isEmbeddingInboundListener = isEmbedding; - - // If the page has already passed the NotInitialized state, - // then it is ready-enough for us to just start this immediately. - if (_startupState != StartupState::NotInitialized) - { - _StartInboundListener(); - } + _startupConnection = std::move(connection); } winrt::TerminalApp::IDialogPresenter TerminalPage::DialogPresenter() const @@ -4073,68 +4043,6 @@ namespace winrt::TerminalApp::implementation ChangeMaximizeRequested.raise(*this, nullptr); } - HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection) - { - _newConnectionRevoker.revoke(); - - // We need to be on the UI thread in order for _OpenNewTab to run successfully. - // HasThreadAccess will return true if we're currently on a UI thread and false otherwise. - // When we're on a COM thread, we'll need to dispatch the calls to the UI thread - // and wait on it hence the locking mechanism. - if (!Dispatcher().HasThreadAccess()) - { - til::latch latch{ 1 }; - auto finalVal = S_OK; - - Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() { - finalVal = _OnNewConnection(connection); - latch.count_down(); - }); - - latch.wait(); - return finalVal; - } - - try - { - NewTerminalArgs newTerminalArgs; - newTerminalArgs.Commandline(connection.Commandline()); - newTerminalArgs.TabTitle(connection.StartingTitle()); - // GH #12370: We absolutely cannot allow a defterm connection to - // auto-elevate. Defterm doesn't work for elevated scenarios in the - // first place. If we try accepting the connection, the spawning an - // elevated version of the Terminal with that profile... that's a - // recipe for disaster. We won't ever open up a tab in this window. - newTerminalArgs.Elevate(false); - const auto newPane = _MakePane(newTerminalArgs, nullptr, connection); - newPane->WalkTree([](const auto& pane) { - pane->FinalizeConfigurationGivenDefault(); - }); - _CreateNewTabFromPane(newPane); - - // Request a summon of this window to the foreground - SummonWindowRequested.raise(*this, nullptr); - - // TEMPORARY SOLUTION - // If the connection has requested for the window to be maximized, - // manually maximize it here. Ideally, we should be _initializing_ - // the session maximized, instead of manually maximizing it after initialization. - // However, because of the current way our defterm handoff works, - // we are unable to get the connection info before the terminal session - // has already started. - - // Make sure that there were no other tabs already existing (in - // the case that we are in glomming mode), because we don't want - // to be maximizing other existing sessions that did not ask for it. - if (_tabs.Size() == 1 && connection.ShowWindow() == SW_SHOWMAXIMIZED) - { - RequestSetMaximized(true); - } - return S_OK; - } - CATCH_RETURN() - } - TerminalApp::IPaneContent TerminalPage::_makeSettingsContent() { if (auto app{ winrt::Windows::UI::Xaml::Application::Current().try_as() }) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 1fd773fb3b..4879f65fc6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -129,8 +129,8 @@ namespace winrt::TerminalApp::implementation void RequestSetMaximized(bool newMaximized); void SetStartupActions(std::vector actions); + void SetStartupConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); - void SetInboundListener(bool isEmbedding); static std::vector ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args); winrt::TerminalApp::IDialogPresenter DialogPresenter() const; @@ -147,9 +147,9 @@ namespace winrt::TerminalApp::implementation void ShowTerminalWorkingDirectory(); safe_void_coroutine ProcessStartupActions(std::vector actions, - const bool initial, const winrt::hstring cwd = winrt::hstring{}, const winrt::hstring env = winrt::hstring{}); + void CreateTabFromConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; }; @@ -257,8 +257,7 @@ namespace winrt::TerminalApp::implementation StartupState _startupState{ StartupState::NotInitialized }; std::vector _startupActions; - bool _shouldStartInboundListener{ false }; - bool _isEmbeddingInboundListener{ false }; + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _startupConnection{ nullptr }; std::shared_ptr _windowIdToast{ nullptr }; std::shared_ptr _actionSavedToast{ nullptr }; @@ -282,8 +281,6 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Foundation::Point dragOffset{ 0, 0 }; } _stashed; - winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker; - safe_void_coroutine _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e); __declspec(noinline) CommandPalette _loadCommandPaletteSlowPath(); @@ -469,8 +466,6 @@ namespace winrt::TerminalApp::implementation void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor); void _ClearNewTabButtonColor(); - void _StartInboundListener(); - safe_void_coroutine _CompleteInitialization(); void _FocusActiveControl(IInspectable sender, IInspectable eventArgs); diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index c2afef3412..0b329a8ee4 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -152,38 +152,23 @@ namespace winrt::TerminalApp::implementation // instead. // * if we have commandline arguments, Pass commandline args into the // TerminalPage. - if (!_initialContentArgs.empty()) + if (_startupConnection) { - _root->SetStartupActions(_initialContentArgs); + _root->SetStartupConnection(std::move(_startupConnection)); } - else + else if (!_initialContentArgs.empty()) + { + _root->SetStartupActions(std::move(_initialContentArgs)); + } + else if (const auto& layout = LoadPersistedLayout()) { // layout will only ever be non-null if there were >0 tabs persisted in // .TabLayout(). We can re-evaluate that as a part of TODO: GH#12633 - if (const auto& layout = LoadPersistedLayout()) - { - std::vector actions; - for (const auto& a : layout.TabLayout()) - { - actions.emplace_back(a); - } - _root->SetStartupActions(actions); - } - else if (_appArgs) - { - _root->SetStartupActions(_appArgs->ParsedArgs().GetStartupActions()); - } + _root->SetStartupActions(wil::to_vector(layout.TabLayout())); } - - // Check if we were started as a COM server for inbound connections of console sessions - // coming out of the operating system default application feature. If so, - // tell TerminalPage to start the listener as we have to make sure it has the chance - // to register a handler to hear about the requests first and is all ready to receive - // them before the COM server registers itself. Otherwise, the request might come - // in and be routed to an event with no handlers or a non-ready Page. - if (_appArgs && _appArgs->ParsedArgs().IsHandoffListener()) + else if (_appArgs) { - _root->SetInboundListener(true); + _root->SetStartupActions(_appArgs->ParsedArgs().GetStartupActions()); } return _root->Initialize(hwnd); @@ -1054,6 +1039,7 @@ namespace winrt::TerminalApp::implementation int32_t TerminalWindow::SetStartupCommandline(TerminalApp::CommandlineArgs args) { _appArgs = winrt::get_self(args); + _startupConnection = args.Connection(); auto& parsedArgs = _appArgs->ParsedArgs(); _WindowProperties->SetInitialCwd(_appArgs->CurrentDirectory()); @@ -1114,13 +1100,16 @@ namespace winrt::TerminalApp::implementation auto& parsedArgs = _appArgs->ParsedArgs(); auto& actions = parsedArgs.GetStartupActions(); - _root->ProcessStartupActions(actions, false, _appArgs->CurrentDirectory(), _appArgs->CurrentEnvironment()); - - if (parsedArgs.IsHandoffListener()) + if (auto conn = args.Connection()) { - _root->SetInboundListener(true); + _root->CreateTabFromConnection(std::move(conn)); + } + else if (!actions.empty()) + { + _root->ProcessStartupActions(actions, _appArgs->CurrentDirectory(), _appArgs->CurrentEnvironment()); } } + // Return the result of parsing with commandline, though it may or may not be used. return _appArgs->ExitCode(); } diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index a7db7a8034..5127b3465c 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -169,6 +169,7 @@ namespace winrt::TerminalApp::implementation winrt::com_ptr _root{ nullptr }; wil::com_ptr _appArgs{ nullptr }; + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _startupConnection{ nullptr }; bool _hasCommandLineArguments{ false }; bool _gotSettingsStartupActions{ false }; std::vector _settingsStartupArgs{}; diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp index 01b25c9ffe..c132a74615 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp @@ -39,7 +39,7 @@ try ComPtr unk; RETURN_IF_FAILED(classFactory.As(&unk)); - RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_cTerminalHandoffRegistration)); + RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &g_cTerminalHandoffRegistration)); return S_OK; } @@ -83,41 +83,19 @@ HRESULT CTerminalHandoff::s_StopListening() // from the registered handler event function. HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) { - try - { - // Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call. - // COM does not automatically clean that up for us. We must do it. - LOG_IF_FAILED(s_StopListening()); + // Report an error if no one registered a handoff function before calling this. + RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); - // Report an error if no one registered a handoff function before calling this. - THROW_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); - - // Call registered handler from when we started listening. - THROW_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo)); + // Call registered handler from when we started listening. + RETURN_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo)); #pragma warning(suppress : 26477) - TraceLoggingWrite( - g_hTerminalConnectionProvider, - "ReceiveTerminalHandoff_Success", - TraceLoggingDescription("successfully received a terminal handoff"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + TraceLoggingWrite( + g_hTerminalConnectionProvider, + "ReceiveTerminalHandoff_Success", + TraceLoggingDescription("successfully received a terminal handoff"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - return S_OK; - } - catch (...) - { - const auto hr = wil::ResultFromCaughtException(); - -#pragma warning(suppress : 26477) - TraceLoggingWrite( - g_hTerminalConnectionProvider, - "ReceiveTerminalHandoff_Failed", - TraceLoggingDescription("failed while receiving a terminal handoff"), - TraceLoggingHResult(hr), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - return hr; - } + return S_OK; } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 1e388f0f32..33d4b31cfb 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -77,6 +77,11 @@ AppHost::AppHost(WindowEmperor* manager, const winrt::TerminalApp::AppLogic& log _windowCallbacks.ShouldExitFullscreen = _window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen }); _window->MakeWindow(); + + // Does window creation mean the window was activated (WM_ACTIVATE)? No. + // But it simplifies `WindowEmperor::_mostRecentWindow()`, because now the creation of a + // new window marks it as the most recent one immediately, even before it becomes active. + QueryPerformanceCounter(&_lastActivatedTime); } bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 7e3ef659b6..ba128e2814 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -1242,9 +1242,12 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled) // - void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args) { - auto actualDropdownDuration = args.DropdownDuration(); + const auto toggleVisibility = args ? args.ToggleVisibility() : false; + const auto toMonitor = args ? args.ToMonitor() : winrt::TerminalApp::MonitorBehavior::InPlace; + auto dropdownDuration = args ? args.DropdownDuration() : 0; + // If the user requested an animation, let's check if animations are enabled in the OS. - if (actualDropdownDuration > 0) + if (dropdownDuration > 0) { auto animationsEnabled = TRUE; SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0); @@ -1258,7 +1261,7 @@ void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args) // _globalActivateWindow/_globalDismissWindow might do if they think // there should be an animation (like making the window appear with // SetWindowPlacement rather than ShowWindow) - actualDropdownDuration = 0; + dropdownDuration = 0; } } @@ -1269,33 +1272,33 @@ void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args) // - activate the window // - else // - dismiss the window - if (args.ToggleVisibility() && GetForegroundWindow() == _window.get()) + if (toggleVisibility && GetForegroundWindow() == _window.get()) { auto handled = false; // They want to toggle the window when it is the FG window, and we are // the FG window. However, if we're on a different monitor than the // mouse, then we should move to that monitor instead of dismissing. - if (args.ToMonitor() == winrt::TerminalApp::MonitorBehavior::ToMouse) + if (toMonitor == winrt::TerminalApp::MonitorBehavior::ToMouse) { const til::rect cursorMonitorRect{ _getMonitorForCursor().rcMonitor }; const til::rect currentMonitorRect{ _getMonitorForWindow(GetHandle()).rcMonitor }; if (cursorMonitorRect != currentMonitorRect) { // We're not on the same monitor as the mouse. Go to that monitor. - _globalActivateWindow(actualDropdownDuration, args.ToMonitor()); + _globalActivateWindow(dropdownDuration, toMonitor); handled = true; } } if (!handled) { - _globalDismissWindow(actualDropdownDuration); + _globalDismissWindow(dropdownDuration); } } else { - _globalActivateWindow(actualDropdownDuration, args.ToMonitor()); + _globalActivateWindow(dropdownDuration, toMonitor); } } diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 1a750af650..1bd15789ec 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -345,20 +345,31 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) for (const auto layout : layouts) { hstring args[] = { L"wt", L"-w", L"new", L"-s", winrt::to_hstring(startIdx) }; - _dispatchCommandline({ args, cwd, showCmd, env }); + _dispatchCommandlineCommon(args, cwd, env, showCmd); startIdx += 1; } } - // Create another window if needed: There aren't any yet, or we got an explicit command line. const auto args = commandlineToArgArray(GetCommandLineW()); - if (_windows.empty() || args.size() != 1) - { - _dispatchCommandline({ args, cwd, showCmd, env }); - } - // If we created no windows, e.g. because the args are "/?" we can just exit now. - _postQuitMessageIfNeeded(); + if (args.size() == 2 && args[1] == L"-Embedding") + { + // We were launched for ConPTY handoff. We have no windows and also don't want to exit. + // + // TODO: Here we could start a timer and exit after, say, 5 seconds + // if no windows are created. But that's a minor concern. + } + else + { + // Create another window if needed: There aren't any yet, OR we got an explicit command line. + if (_windows.empty() || args.size() != 1) + { + _dispatchCommandlineCommon(args, cwd, env, showCmd); + } + + // If we created no windows, e.g. because the args are "/?" we can just exit now. + _postQuitMessageIfNeeded(); + } } // ALWAYS change the _real_ CWD of the Terminal to system32, @@ -386,6 +397,19 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) } } + { + TerminalConnection::ConptyConnection::NewConnection([this](TerminalConnection::ConptyConnection conn) { + TerminalApp::CommandlineArgs args; + args.ShowWindowCommand(conn.ShowWindow()); + args.Connection(std::move(conn)); + _dispatchCommandline(std::move(args)); + _summonWindow(SummonWindowSelectionArgs{ + .SummonBehavior = nullptr, + }); + }); + TerminalConnection::ConptyConnection::StartInboundListener(); + } + // Main message loop. It pumps all windows. bool loggedInteraction = false; MSG msg{}; @@ -588,6 +612,16 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg } } +void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand) +{ + winrt::TerminalApp::CommandlineArgs c; + c.Commandline(args); + c.CurrentDirectory(currentDirectory); + c.CurrentEnvironment(envString); + c.ShowWindowCommand(showWindowCommand); + _dispatchCommandline(std::move(c)); +} + // This is an implementation-detail of _dispatchCommandline(). safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args) { @@ -896,10 +930,8 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c { const auto handoff = deserializeHandoffPayload(static_cast(cds->lpData), static_cast(cds->lpData) + cds->cbData); const winrt::hstring args{ handoff.args }; - const winrt::hstring env{ handoff.env }; - const winrt::hstring cwd{ handoff.cwd }; const auto argv = commandlineToArgArray(args.c_str()); - _dispatchCommandline({ argv, cwd, gsl::narrow_cast(handoff.show), env }); + _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show); } return 0; case WM_HOTKEY: @@ -1198,7 +1230,7 @@ void WindowEmperor::_hotkeyPressed(const long hotkeyIndex) const wil::unique_environstrings_ptr envMem{ GetEnvironmentStringsW() }; const auto env = stringFromDoubleNullTerminated(envMem.get()); const auto cwd = wil::GetCurrentDirectoryW(); - _dispatchCommandline({ argv, cwd, SW_SHOWDEFAULT, std::move(env) }); + _dispatchCommandlineCommon(argv, cwd, env, SW_SHOWDEFAULT); } void WindowEmperor::_registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index c84882665f..6995ab4c68 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -52,10 +52,10 @@ private: void _summonAllWindows() const; void _dispatchSpecialKey(const MSG& msg) const; void _dispatchCommandline(winrt::TerminalApp::CommandlineArgs args); + void _dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand); safe_void_coroutine _dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args); LRESULT _messageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept; void _createMessageWindow(const wchar_t* className); - bool _shouldSkipClosingWindows() const; void _postQuitMessageIfNeeded() const; safe_void_coroutine _showMessageBox(winrt::hstring message, bool error); void _notificationAreaMenuRequested(WPARAM wParam); diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h index 583b67a0e4..87a0ade306 100644 --- a/src/cascadia/WindowsTerminal/pch.h +++ b/src/cascadia/WindowsTerminal/pch.h @@ -67,8 +67,9 @@ Abstract: #include #include -#include #include +#include +#include #include #include From 43b59d504b0d0de7c03167f1576005f181fcfa4b Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 2 Jul 2025 18:01:39 -0500 Subject: [PATCH 094/177] build: partially revert AzCopy changes from 22c509f (#19092) When we first transitioned to the `R1` network isolation environment--which required us to _not_ contact powershellgallery.com--we had to give up on the `AzureFileCopy` task. Since then, the `AzurePowerShell` task has also become somewhat broken while OneBranch fixed the issue that prevented us from using `AzureFileCopy`. In short, we now need to: - Install the Azure PowerShell modules directly from our own feed (which satisfies the network constraint) - ... using `PSResourceGet` ... - ... for Windows PowerShell 5.1 ... - which is used by `AzureFileCopy` to later perform authentication. --- .github/actions/spelling/expect/expect.txt | 2 -- .../job-deploy-to-azure-storage.yml | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3507bf387b..363133dd1a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -78,7 +78,6 @@ autoscrolling Autowrap AVerify awch -AZCOPY azurecr AZZ backgrounded @@ -1342,7 +1341,6 @@ PROPTITLE propvar propvariant psa -PSCRED PSECURITY pseudoconsole psh diff --git a/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml index 8c2836c059..2f68ba92c3 100644 --- a/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml +++ b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml @@ -75,13 +75,30 @@ jobs: } displayName: "Wrangle Unpackaged builds into place, rename" - - task: AzurePowerShell@5 + - task: PowerShell@2 + displayName: Install Azure Modules from custom PowerShell Gallery Repo + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + pwsh: false # We are preparing modules for AzureFileCopy, which uses PowerShell 5.1 + targetType: inline + script: |- + $MachineToken = $env:SYSTEM_ACCESSTOKEN | ConvertTo-SecureString -AsPlainText -Force + $Credential = [PSCredential]::new("ONEBRANCH_TOKEN", $MachineToken) + $MachineToken = $null + $Feed = "https://pkgs.dev.azure.com/shine-oss/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" + Register-PSResourceRepository -Name "PSGalleryUpstream" -Uri $Feed -Trusted + Get-PSResourceRepository + + Install-PSResource -Name Az.Accounts, Az.Storage, Az.Network, Az.Resources, Az.Compute -Repository "PSGalleryUpstream" -Credential $Credential + + - task: AzureFileCopy@6 displayName: Publish to Storage Account inputs: + sourcePath: _out/* + Destination: AzureBlob azureSubscription: ${{ parameters.subscription }} - azurePowerShellVersion: LatestVersion - pwsh: true - ScriptType: InlineScript - Inline: |- - $Env:AZCOPY_AUTO_LOGIN_TYPE="PSCRED" - & AzCopy copy "_out\*" "https://${{ parameters.storageAccount }}.blob.core.windows.net/${{ parameters.storageContainer }}/" --content-type application/octet-stream + storage: ${{ parameters.storageAccount }} + ContainerName: ${{ parameters.storageContainer }} + AdditionalArgumentsForBlobCopy: "--content-type application/octet-stream" + From 97f0a06fbe0d01521c14e7b1461faea2e5a83d5a Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 3 Jul 2025 01:49:33 +0200 Subject: [PATCH 095/177] Fix another VT input double-encoding issue (#19083) Closes #17264 Closes https://github.com/microsoft/edit/issues/182 Long shot, but probably also... Closes #18579 Closes #19082 --- src/terminal/adapter/InteractDispatch.cpp | 33 +++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index 09cc2c970c..8ed76b9d7c 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -62,15 +62,38 @@ void InteractDispatch::WriteString(const std::wstring_view string) { if (!string.empty()) { - const auto codepage = _api.GetOutputCodePage(); - InputEventQueue keyEvents; + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); +#pragma warning(suppress : 26429) // Symbol 'inputBuffer' is never tested for nullness, it can be marked as not_null (f.23). + const auto inputBuffer = gci.GetActiveInputBuffer(); - for (const auto& wch : string) + // The input *may* be keyboard input in which case we must call CharToKeyEvents. + // + // However, it could also be legitimate VT sequences (e.g. a bracketed paste sequence). + // If we called `InputBuffer::Write` with those, we would end up indirectly + // calling `TerminalInput::HandleKey` and "double encode" the sequence. + // The effect of this is noticeable with the German keyboard layout, for instance, + // where the [ key maps to AltGr+8, and we fail to map it back to [ later. + // + // It's worth noting that all of this is bad design in either case. + // The way it should work is that we write INPUT_RECORDs and Strings as-is into the + // InputBuffer, and only during retrieval they're converted into one or the other. + // This prevents any kinds of double-encoding issues. + if (inputBuffer->IsInVirtualTerminalInputMode()) { - CharToKeyEvents(wch, codepage, keyEvents); + inputBuffer->WriteString(string); } + else + { + const auto codepage = _api.GetOutputCodePage(); + InputEventQueue keyEvents; - WriteInput(keyEvents); + for (const auto& wch : string) + { + CharToKeyEvents(wch, codepage, keyEvents); + } + + inputBuffer->Write(keyEvents); + } } } From 4500d428319ee3d13835c13910204f9e080b8d56 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 3 Jul 2025 12:16:04 -0500 Subject: [PATCH 096/177] build: enable the compliance theatre build options (#19094) This makes some compiler warnings into actual bug reports that get filed on us. --- build/pipelines/ob-nightly.yml | 1 + .../pipeline-onebranch-full-release-build.yml | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/build/pipelines/ob-nightly.yml b/build/pipelines/ob-nightly.yml index 033d075cd6..63eba379d8 100644 --- a/build/pipelines/ob-nightly.yml +++ b/build/pipelines/ob-nightly.yml @@ -50,6 +50,7 @@ extends: parameters: pool: { type: windows } variables: + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: "$(Build.SourcesDirectory)/_none" diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml index 0f09271a72..3746402e3e 100644 --- a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -88,6 +88,9 @@ extends: enabled: false globalSdl: # https://aka.ms/obpipelines/sdl enableCheckCFlags: false # CheckCFlags is broken and exploding our builds; to remove, :g/BAD-FLAGS/d + isNativeCode: true + prefast: + enabled: true asyncSdl: enabled: true tsaOptionsFile: 'build/config/tsa.json' @@ -115,6 +118,8 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_runDuring: Build + ob_sdl_checkCompliantCompilerWarnings: true ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -149,6 +154,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a C# build job ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -180,6 +186,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -234,6 +241,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -252,6 +260,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -277,6 +286,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(Build.ArtifactStagingDirectory) From ac07afebcb949687db8cadf60605f11ee804ec15 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 4 Jul 2025 00:19:12 +0200 Subject: [PATCH 097/177] Fix a crash during commandline handoff (#19096) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crash occurs because WinRT `abort()`s when it encounters a `std::wstring_view` without null-terminator. Closes #19093 ## Validation Steps Performed * Set `wtd` as the default terminal * Launch `cmd` * Launch `wtd` * 2 windows ✅ --- .../WindowsTerminal/WindowEmperor.cpp | 36 ++++++++++--------- src/cascadia/WindowsTerminal/WindowEmperor.h | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 1bd15789ec..7cd5223a50 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -49,7 +49,7 @@ static std::vector commandlineToArgArray(const wchar_t* commandL } // Returns the length of a double-null encoded string *excluding* the trailing double-null character. -static std::wstring_view stringFromDoubleNullTerminated(const wchar_t* beg) +static wil::zwstring_view stringFromDoubleNullTerminated(const wchar_t* beg) { auto end = beg; @@ -57,7 +57,7 @@ static std::wstring_view stringFromDoubleNullTerminated(const wchar_t* beg) { } - return { beg, end }; + return { beg, gsl::narrow_cast(end - beg) }; } // Appends an uint32_t to a byte vector. @@ -78,36 +78,39 @@ static const uint8_t* deserializeUint32(const uint8_t* it, const uint8_t* end, u return it + sizeof(uint32_t); } -// Writes an uint32_t length prefix, followed by the string data, to the output vector. -static void serializeString(std::vector& out, std::wstring_view str) +// Writes a null-terminated string to `out`: A uint32_t length prefix, +// *including null byte*, followed by the string data, followed by the null-terminator. +static void serializeString(std::vector& out, wil::zwstring_view str) { const auto ptr = reinterpret_cast(str.data()); - const auto len = gsl::narrow(str.size()); - serializeUint32(out, len); + const auto len = str.size() + 1; + serializeUint32(out, gsl::narrow(len)); out.insert(out.end(), ptr, ptr + len * sizeof(wchar_t)); } -// Parses the next string from the input iterator. Performs bounds-checks. +// Counter-part to `serializeString`. Performs bounds-checks. // Returns an iterator that points past it. -static const uint8_t* deserializeString(const uint8_t* it, const uint8_t* end, std::wstring_view& str) +static const uint8_t* deserializeString(const uint8_t* it, const uint8_t* end, wil::zwstring_view& str) { uint32_t len; it = deserializeUint32(it, end, len); - if (static_cast(end - it) < len * sizeof(wchar_t)) + const auto bytes = static_cast(len) * sizeof(wchar_t); + + if (bytes == 0 || static_cast(end - it) < bytes) { throw std::out_of_range("Not enough data for string content"); } - str = { reinterpret_cast(it), len }; - return it + len * sizeof(wchar_t); + str = { reinterpret_cast(it), len - 1 }; + return it + bytes; } struct Handoff { - std::wstring_view args; - std::wstring_view env; - std::wstring_view cwd; + wil::zwstring_view args; + wil::zwstring_view env; + wil::zwstring_view cwd; uint32_t show; }; @@ -612,7 +615,7 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg } } -void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand) +void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view args, wil::zwstring_view currentDirectory, wil::zwstring_view envString, uint32_t showWindowCommand) { winrt::TerminalApp::CommandlineArgs c; c.Commandline(args); @@ -929,8 +932,7 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c if (const auto cds = reinterpret_cast(lParam); cds->dwData == TERMINAL_HANDOFF_MAGIC) { const auto handoff = deserializeHandoffPayload(static_cast(cds->lpData), static_cast(cds->lpData) + cds->cbData); - const winrt::hstring args{ handoff.args }; - const auto argv = commandlineToArgArray(args.c_str()); + const auto argv = commandlineToArgArray(handoff.args.c_str()); _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show); } return 0; diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 6995ab4c68..b0213e7635 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -52,7 +52,7 @@ private: void _summonAllWindows() const; void _dispatchSpecialKey(const MSG& msg) const; void _dispatchCommandline(winrt::TerminalApp::CommandlineArgs args); - void _dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand); + void _dispatchCommandlineCommon(winrt::array_view args, wil::zwstring_view currentDirectory, wil::zwstring_view envString, uint32_t showWindowCommand); safe_void_coroutine _dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args); LRESULT _messageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept; void _createMessageWindow(const wchar_t* className); From 02f173d504d82a05da048981c56b7402c11b6c0c Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 8 Jul 2025 17:27:21 -0500 Subject: [PATCH 098/177] Include the hash of the SID in the Window Class and Mutant (#19109) Right now, we do not use a sufficiently unique name to disambiguate Terminal instances running on the same desktop. Mutexes (mutants) are named objects that live in the user's session, under `Sessions\1\BaseNamedObjects` (for the local desktop session). When multiple users are logged into the same session--such as with "Run as different user"--they share a local BaseNamedObjects namespace. Ugh. We cannot use [`CreatePrivateNamespace`] as it requires a boundary descriptor, and the only boundary descriptors supported by the current API are based on package identity. I also fear that `CreatePrivateNamespace` is subject to a race condition with `OpenPrivateNamespace`; Create will not Open an existing one, so we would need to back off and retry either opening or creating. Yuck. After this commit, we will hash the user's SID into the name of both the window class and the mutant, right after the path hash (if running unpackaged). Closes #18704 [`CreatePrivateNamespace`]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprivatenamespacea --- src/cascadia/WindowsTerminal/WindowEmperor.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 7cd5223a50..c19d2f22d5 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "AppHost.h" #include "resource.h" @@ -275,7 +276,7 @@ AppHost* WindowEmperor::_mostRecentWindow() const noexcept void WindowEmperor::HandleCommandlineArgs(int nCmdShow) { std::wstring windowClassName; - windowClassName.reserve(47); // "Windows Terminal Preview Admin 0123456789012345" + windowClassName.reserve(64); // "Windows Terminal Preview Admin 0123456789012345 0123456789012345" #if defined(WT_BRANDING_RELEASE) windowClassName.append(L"Windows Terminal"); #elif defined(WT_BRANDING_PREVIEW) @@ -300,6 +301,18 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) #endif } + { + wil::unique_handle processToken{ GetCurrentProcessToken() }; + const auto userTokenInfo{ wil::get_token_information(processToken.get()) }; + const auto sidLength{ GetLengthSid(userTokenInfo->User.Sid) }; + const auto hash{ til::hash(userTokenInfo->User.Sid, sidLength) }; +#ifdef _WIN64 + fmt::format_to(std::back_inserter(windowClassName), FMT_COMPILE(L" {:016x}"), hash); +#else + fmt::format_to(std::back_inserter(windowClassName), FMT_COMPILE(L" {:08x}"), hash); +#endif + } + // Windows Terminal is a single-instance application. Either acquire ownership // over the mutex, or hand off the command line to the existing instance. const auto mutex = acquireMutexOrAttemptHandoff(windowClassName.c_str(), nCmdShow); From c4fbb58f69b0a5cc86c245505311d0a0b3cc1399 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 9 Jul 2025 15:42:23 -0500 Subject: [PATCH 099/177] During session save, use the profile's GUID rather than its Name (#19113) This will prevent Terminal from erroneously selecting a hidden (deleted, disabled or otherwise) profile of the same name during restoration and subsequently using the wrong settings. I am not certain why we used the name at all! Closes #19105 --- src/cascadia/TerminalApp/TerminalPaneContent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index c85009ea0c..42e6e195f0 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -95,7 +95,7 @@ namespace winrt::TerminalApp::implementation NewTerminalArgs args{}; const auto& controlSettings = _control.Settings(); - args.Profile(controlSettings.ProfileName()); + args.Profile(::Microsoft::Console::Utils::GuidToString(_profile.Guid())); // If we know the user's working directory use it instead of the profile. if (const auto dir = _control.WorkingDirectory(); !dir.empty()) { From f14718f738bea5744ab6b61c3d63aaf998d97ad7 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 9 Jul 2025 16:36:13 -0500 Subject: [PATCH 100/177] version: fix the LCID in our VERSIONINFO, add descriptions to each file (#19114) Closes #19106 --- custom.props | 3 +++ src/cascadia/ElevateShim/elevate-shim.vcxproj | 1 + src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj | 1 + src/cascadia/TerminalApp/dll/TerminalApp.vcxproj | 1 + src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj | 1 + src/cascadia/TerminalConnection/TerminalConnection.vcxproj | 1 + src/cascadia/TerminalControl/dll/TerminalControl.vcxproj | 1 + .../Microsoft.Terminal.Settings.Editor.vcxproj | 1 + .../dll/Microsoft.Terminal.Settings.Model.vcxproj | 1 + src/cascadia/UIHelpers/UIHelpers.vcxproj | 1 + src/cascadia/UIMarkdown/UIMarkdown.vcxproj | 1 + src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj | 1 + src/cascadia/wt/wt.vcxproj | 1 + src/host/exe/Host.EXE.vcxproj | 1 + src/propsheet/propsheet.vcxproj | 1 + src/winconpty/dll/winconptydll.vcxproj | 1 + 16 files changed, 18 insertions(+) diff --git a/custom.props b/custom.props index 8e358b3e0b..d3fea955c2 100644 --- a/custom.props +++ b/custom.props @@ -7,5 +7,8 @@ 1 24 Windows Terminal + 1033 + + \xa9 Microsoft Corporation. All rights reserved. diff --git a/src/cascadia/ElevateShim/elevate-shim.vcxproj b/src/cascadia/ElevateShim/elevate-shim.vcxproj index 95b6240659..80dccc6b0c 100644 --- a/src/cascadia/ElevateShim/elevate-shim.vcxproj +++ b/src/cascadia/ElevateShim/elevate-shim.vcxproj @@ -7,6 +7,7 @@ elevate-shim elevate-shim Application + Windows Terminal Administrator Launch Helper diff --git a/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj b/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj index 4207b58282..d4cc256839 100644 --- a/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj +++ b/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj @@ -10,6 +10,7 @@ Console false + Windows Terminal Open Here Shell Extension true diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 8e158d51da..6a20f0ac9b 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -11,6 +11,7 @@ true true + Windows Terminal Main UI Library true diff --git a/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj b/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj index eada6967cb..29719f4850 100644 --- a/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj +++ b/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj @@ -11,6 +11,7 @@ false Windows Store Windows + Windows Terminal Azure Cloud Shell Connector diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj index 8eda993772..2c7d276c50 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj @@ -8,6 +8,7 @@ Console true true + Windows Terminal Connection Library true diff --git a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj index e6cd662dd1..2af748bb22 100644 --- a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj @@ -21,6 +21,7 @@ projects compile properly when they depend on this "Microsoft.winmd." --> 3 + Windows Terminal Control Library diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index da55bbd757..20f1538978 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -31,6 +31,7 @@ --> 4 nested + Windows Terminal Settings UI Library true diff --git a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj index 6ab7c60689..eaf33503a4 100644 --- a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj +++ b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj @@ -11,6 +11,7 @@ true true + Windows Terminal Settings Model Library true diff --git a/src/cascadia/UIHelpers/UIHelpers.vcxproj b/src/cascadia/UIHelpers/UIHelpers.vcxproj index eaae5b75ac..61a82db255 100644 --- a/src/cascadia/UIHelpers/UIHelpers.vcxproj +++ b/src/cascadia/UIHelpers/UIHelpers.vcxproj @@ -7,6 +7,7 @@ DynamicLibrary Console true + Windows Terminal UI Support Library true diff --git a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj index c23ec98a87..f347b49979 100644 --- a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj +++ b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj @@ -17,6 +17,7 @@ --> 4 nested + Windows Terminal Markdown Control Library true diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index ef96673272..5135d643fb 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -13,6 +13,7 @@ true false Windows + Windows Terminal Host true diff --git a/src/cascadia/wt/wt.vcxproj b/src/cascadia/wt/wt.vcxproj index 20e4b3f6f2..6d7edd9d1c 100644 --- a/src/cascadia/wt/wt.vcxproj +++ b/src/cascadia/wt/wt.vcxproj @@ -7,6 +7,7 @@ wt wt Application + Windows Terminal Launcher Shim diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index f055eb9146..3deb6f37f3 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -7,6 +7,7 @@ Host.EXE OpenConsole Application + Console Window and PTY Host (Open Source) true diff --git a/src/propsheet/propsheet.vcxproj b/src/propsheet/propsheet.vcxproj index dd31fa42d3..77ea31ef93 100644 --- a/src/propsheet/propsheet.vcxproj +++ b/src/propsheet/propsheet.vcxproj @@ -7,6 +7,7 @@ Propsheet.DLL console DynamicLibrary + Windows Console Host Property Sheet diff --git a/src/winconpty/dll/winconptydll.vcxproj b/src/winconpty/dll/winconptydll.vcxproj index ef86147e67..80ed63fa23 100644 --- a/src/winconpty/dll/winconptydll.vcxproj +++ b/src/winconpty/dll/winconptydll.vcxproj @@ -7,6 +7,7 @@ winconpty.DLL conpty DynamicLibrary + ConPTY Interface Library From 0cbb6b1f2f32a8c3242b6cb40c8eee0c3cd9fcd4 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 14 Jul 2025 15:40:20 -0500 Subject: [PATCH 101/177] Rewrite HighlightedTextControl and remove HighlightedText (#19130) `HighlightedTextControl` is a XAML user control that contains a text block and takes vector of `HighlightedText`. `HighlightedText` uses tuples `(string, boolean)`. Allocating an entire object to store a string and an integer felt like a waste, especially when we were doing it thousands of times for the command palette _and just to pass them into another object that stores a string and a few more integers (`Run`)._ The new `HighlightedTextControl` is a standard templated control, and supports styling of both the inner text block and of the highlighted runs. It no longer takes a `HighlightedText`, but rather a standard string and a set of runs (tuple `(int start, int end)`); these can be stored more efficiently, and this change moves the construction of text and runs directly into `HighlightedTextControl` itself as an implementation detail rather than an API contract. ### XAML Properties - `Text`: the string to highlight - `HighlightedRuns`: a vector of `(start, end)` pairs, indicating which regions are intended to be highlighted. Can be empty (which indicates there is no highlight and that the entire string is styled normally.) - `TextBlockStyle`: the `Style` applied to the inner text block; optional; allows consumers to change how both normal and highlighted text looks. - `HighlightedRunStyle`: a `Style` applied only to the highlighted runs; optional; allows consumers to change how highlighted text looks. If left NULL, highlighted runs will be bold. `HighlightedRunStyle` is a little bodgy. It only applies to `Run` objects (which is fine, and XAML somewhat supports), but since `Run` is not a `FrameworkElement`, it _doesn't actually have a `Style` member._ We need to crack open the style and apply it manually, entry by entry. `FontWeight` is special because XAML is a special little flower. --- .github/actions/spelling/expect/expect.txt | 1 + .../FilteredCommandTests.cpp | 77 ++++---- src/cascadia/TerminalApp/App.xaml | 2 + src/cascadia/TerminalApp/CommandPalette.xaml | 9 +- src/cascadia/TerminalApp/FilteredCommand.cpp | 55 +++--- src/cascadia/TerminalApp/FilteredCommand.h | 2 +- src/cascadia/TerminalApp/FilteredCommand.idl | 2 +- src/cascadia/TerminalApp/HighlightedText.cpp | 31 --- src/cascadia/TerminalApp/HighlightedText.h | 35 ---- src/cascadia/TerminalApp/HighlightedText.idl | 22 --- .../TerminalApp/HighlightedTextControl.cpp | 180 +++++++++++++----- .../TerminalApp/HighlightedTextControl.h | 17 +- .../TerminalApp/HighlightedTextControl.idl | 21 +- .../TerminalApp/HighlightedTextControl.xaml | 11 -- .../HighlightedTextControlStyle.xaml | 23 +++ .../TerminalApp/SnippetsPaneContent.xaml | 3 +- .../TerminalApp/SuggestionsControl.xaml | 6 +- .../TerminalApp/TerminalAppLib.vcxproj | 11 +- .../TerminalAppLib.vcxproj.filters | 11 +- src/cascadia/TerminalSettingsEditor/Utils.h | 21 -- src/cascadia/inc/cppwinrt_utils.h | 40 ++++ 21 files changed, 305 insertions(+), 275 deletions(-) delete mode 100644 src/cascadia/TerminalApp/HighlightedText.cpp delete mode 100644 src/cascadia/TerminalApp/HighlightedText.h delete mode 100644 src/cascadia/TerminalApp/HighlightedText.idl delete mode 100644 src/cascadia/TerminalApp/HighlightedTextControl.xaml create mode 100644 src/cascadia/TerminalApp/HighlightedTextControlStyle.xaml diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 363133dd1a..357606d5fa 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -2,6 +2,7 @@ aaaaabbb aabbcc ABANDONFONT abbcc +abcc abgr ABORTIFHUNG ACCESSTOKEN diff --git a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp index 023ac04702..3175d80749 100644 --- a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp @@ -28,74 +28,81 @@ namespace TerminalAppLocalTests TEST_METHOD(VerifyCompareIgnoreCase); }; + static void _verifySegment(auto&& segments, uint32_t index, uint64_t start, uint64_t end) + { + const auto& segment{ segments.GetAt(index) }; + VERIFY_ARE_EQUAL(segment.Start, start, NoThrowString().Format(L"segment %zu", index)); + VERIFY_ARE_EQUAL(segment.End, end, NoThrowString().Format(L"segment %zu", index)); + } + void FilteredCommandTests::VerifyHighlighting() { auto result = RunOnUIThread([]() { + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + const auto paletteItem{ winrt::make(L"AAAAAABBBBBBCCC") }; const auto filteredCommand = winrt::make_self(paletteItem); { Log::Comment(L"Testing command name segmentation with no filter"); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_IS_NULL(segments); // No matches = no segments } { Log::Comment(L"Testing command name segmentation with empty filter"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_IS_NULL(segments); // No matches = no segments } { Log::Comment(L"Testing command name segmentation with filter equal to the string"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"AAAAAABBBBBBCCC"))); - auto segments = filteredCommand->HighlightedName().Segments(); + const auto segments = filteredCommand->NameHighlights(); + VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); + _verifySegment(segments, 0, 0, 14); // one segment for the entire string } { Log::Comment(L"Testing command name segmentation with filter with first character matching"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"A"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 2u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 1u); // only one bold segment + _verifySegment(segments, 0, 0, 0); // it only covers the first character } { Log::Comment(L"Testing command name segmentation with filter with other case"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"a"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 2u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 1u); // only one bold segment + _verifySegment(segments, 0, 0, 0); // it only covers the first character } { Log::Comment(L"Testing command name segmentation with filter matching several characters"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"ab"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 3u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAA"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AB"); - VERIFY_IS_TRUE(segments.GetAt(1).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"BBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(2).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 1u); // one bold segment + _verifySegment(segments, 0, 5, 6); // middle 'ab' + } + { + Log::Comment(L"Testing command name segmentation with filter matching several regions"); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"abcc"))); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 2u); // two bold segments + _verifySegment(segments, 0, 5, 6); // middle 'ab' + _verifySegment(segments, 1, 12, 13); // start of 'cc' } { Log::Comment(L"Testing command name segmentation with non matching filter"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"abcd"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_IS_NULL(segments); // No matches = no segments } }); diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index cc0ce1517d..4210b65b0a 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -243,6 +243,8 @@ + + diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml index 7a4b598d5b..b646c2e4a3 100644 --- a/src/cascadia/TerminalApp/CommandPalette.xaml +++ b/src/cascadia/TerminalApp/CommandPalette.xaml @@ -73,7 +73,8 @@ + HighlightedRuns="{x:Bind NameHighlights, Mode=OneWay}" + Text="{x:Bind Item.Name, Mode=OneWay}" /> + + + + + diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.xaml b/src/cascadia/TerminalApp/SnippetsPaneContent.xaml index b47187f5e6..b6d054ae52 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.xaml +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.xaml @@ -170,8 +170,9 @@ + Text="{x:Bind FilteredCommand.Item.Name, Mode=OneWay}" /> - - + @@ -99,11 +98,10 @@ PaletteItemTemplateSelector.idl Code - + TabBase.idl - TaskbarState.idl @@ -192,11 +190,10 @@ - - + @@ -209,12 +206,10 @@ PaletteItemTemplateSelector.idl Code - TabBase.idl - TaskbarState.idl @@ -319,8 +314,6 @@ - - AboutDialog.xaml @@ -331,7 +324,7 @@ Designer - + @@ -341,7 +334,6 @@ Code - diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 5f67df4bdf..52b5113bdb 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -26,16 +26,7 @@ commandPalette - - commandPalette - - - commandPalette - - - commandPalette - - + commandPalette @@ -62,16 +53,10 @@ commandPalette - + commandPalette - - commandPalette - - - commandPalette - - + commandPalette @@ -135,16 +120,7 @@ commandPalette - - commandPalette - - - commandPalette - - - commandPalette - - + commandPalette From 5b63e24c73b22d8c58ccbb77d0ac88f202fadffd Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 16 Jul 2025 17:10:06 -0500 Subject: [PATCH 105/177] ci: turn on SARIF reporting unconditionally for check-spelling (#19135) The logic currently present occasionally fails to enable reporting for some PRs. --- .github/workflows/spelling2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index f01ac1eea5..9c9cb9ea85 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -104,7 +104,7 @@ jobs: report-timing: 1 warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check,unclosed-block-ignore-begin,unclosed-block-ignore-end experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} - use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }} + use_sarif: 1 check_extra_dictionaries: "" dictionary_source_prefixes: > { From 7b841628dfd332dbd6e69a44dc1b85ec17ca5837 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 16 Jul 2025 21:13:12 -0500 Subject: [PATCH 106/177] Re-enable web-source icons in Stable and Preview builds (#19137) Disables a controversial part of #19044. Refs #19075 --- .../TerminalSettingsModel/CascadiaSettings.cpp | 11 ++++++++--- src/features.xml | 11 +++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 9dc2c156f2..1528b97a4e 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -499,9 +499,14 @@ static bool _validateSingleMediaResource(std::wstring_view resource) return false; } - const auto scheme{ resourceUri.SchemeName() }; - // Only file: URIs and ms-* URIs are permissible. http, https, ftp, gopher, etc. are not. - return til::equals_insensitive_ascii(scheme, L"file") || til::starts_with_insensitive_ascii(scheme, L"ms-"); + if constexpr (Feature_DisableWebSourceIcons::IsEnabled()) + { + const auto scheme{ resourceUri.SchemeName() }; + // Only file: URIs and ms-* URIs are permissible. http, https, ftp, gopher, etc. are not. + return til::equals_insensitive_ascii(scheme, L"file") || til::starts_with_insensitive_ascii(scheme, L"ms-"); + } + + return true; } catch (...) { diff --git a/src/features.xml b/src/features.xml index 894875ecd5..d5c7942104 100644 --- a/src/features.xml +++ b/src/features.xml @@ -202,4 +202,15 @@ + + Feature_DisableWebSourceIcons + Disables icon paths that make web requests + 19075 + AlwaysDisabled + + Dev + Canary + + + From a04e410a39563b84aecbf7d71b57ee63fea1fed7 Mon Sep 17 00:00:00 2001 From: Paulina Kalicka <71526180+paulinek13@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:09:52 +0200 Subject: [PATCH 107/177] feat: add option to enable zoom with `ctrl + scroll` (#19127) This PR adds a new global setting `scrollToZoom` that allows users to enable font zooming with scrolling. When disabled, **this setting prevents accidental font size changes** that can occur when users scroll while holding the Ctrl key. Note: after disabling this setting, users may still change font size using `Ctrl+` and `Ctrl-` keyboard shortcuts. Other Ctrl+Scroll functionality (like transparency adjustments) remains unaffected. ## Validation Steps Performed - Verified the setting can be toggled in the Settings UI (Interaction tab) - Confirmed that when disabled, holding Ctrl and scrolling no longer changes font size - Validated that the setting persists across terminal restarts --- Note: I used the existing `FocusFollowMouse` setting as a reference for implementing this. Closes #11710 Closes #3793 Closes #11906 Closes #3990 --- doc/cascadia/profiles.schema.json | 5 +++++ src/cascadia/TerminalControl/ControlInteractivity.cpp | 2 +- src/cascadia/TerminalControl/IControlSettings.idl | 1 + src/cascadia/TerminalSettingsEditor/Interaction.xaml | 6 ++++++ src/cascadia/TerminalSettingsEditor/InteractionViewModel.h | 1 + .../TerminalSettingsEditor/InteractionViewModel.idl | 1 + .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 4 ++++ src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl | 1 + src/cascadia/TerminalSettingsModel/MTSMSettings.h | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.cpp | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + 12 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index a5011bb980..10f5a8b87f 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2376,6 +2376,11 @@ "description": "When set to true, the terminal will focus the pane on mouse hover.", "type": "boolean" }, + "experimental.scrollToZoom": { + "default": true, + "description": "When set to true, holding the Ctrl key while scrolling will increase or decrease the terminal font size.", + "type": "boolean" + }, "compatibility.allowHeadless": { "default": false, "description": "When set to true, Windows Terminal will run in the background. This allows globalSummon and quakeMode actions to work even when no windows are open.", diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index f289912145..ba740a4364 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -519,7 +519,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _mouseTransparencyHandler(delta); } - else if (ctrlPressed) + else if (ctrlPressed && _core->Settings().ScrollToZoom()) { _mouseZoomHandler(delta); } diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index ad621fdff8..2bd4bfc854 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -60,6 +60,7 @@ namespace Microsoft.Terminal.Control Boolean CopyOnSelect { get; }; Microsoft.Terminal.Control.CopyFormat CopyFormatting { get; }; Boolean FocusFollowMouse { get; }; + Boolean ScrollToZoom { get; }; String Commandline { get; }; String StartingDirectory { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index 10d45c7899..eba8b8129e 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -83,6 +83,12 @@ Style="{StaticResource ToggleSwitchInExpanderStyle}" />
+ + + + + Automatically focus pane on mouse hover Header for a control to toggle the "focus follow mouse" setting. When enabled, hovering over a pane puts it in focus. + + Adjust terminal font size by scrolling while holding the Ctrl key + Header for a control to toggle font size changes with scrolling. When enabled, holding the Ctrl key while scrolling will increase or decrease the terminal font size. + Pane animations Header for a control to toggle animations on panes. "Enabled" value enables the animations. diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 9bf5267733..5a99e05bc3 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -90,6 +90,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, DisableAnimations); INHERITABLE_SETTING(String, StartupActions); INHERITABLE_SETTING(Boolean, FocusFollowMouse); + INHERITABLE_SETTING(Boolean, ScrollToZoom); INHERITABLE_SETTING(WindowingMode, WindowingBehavior); INHERITABLE_SETTING(Boolean, TrimBlockSelection); INHERITABLE_SETTING(Boolean, DetectURLs); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 01bd5ce5c8..725cddbb68 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -24,6 +24,7 @@ Author(s): X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \ X(bool, CopyOnSelect, "copyOnSelect", false) \ X(bool, FocusFollowMouse, "focusFollowMouse", false) \ + X(bool, ScrollToZoom, "experimental.scrollToZoom", true) \ X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI, "rendering.graphicsAPI") \ X(bool, DisablePartialInvalidation, "rendering.disablePartialInvalidation", false) \ X(bool, SoftwareRendering, "rendering.software", false) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 9d3540b145..5b34afbc5a 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -367,6 +367,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _CopyOnSelect = globalSettings.CopyOnSelect(); _CopyFormatting = globalSettings.CopyFormatting(); _FocusFollowMouse = globalSettings.FocusFollowMouse(); + _ScrollToZoom = globalSettings.ScrollToZoom(); _GraphicsAPI = globalSettings.GraphicsAPI(); _DisablePartialInvalidation = globalSettings.DisablePartialInvalidation(); _SoftwareRendering = globalSettings.SoftwareRendering(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 9591ad3487..b9f4af3d30 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -94,6 +94,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, bool, CopyOnSelect, false); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0); INHERITABLE_SETTING(Model::TerminalSettings, bool, FocusFollowMouse, false); + INHERITABLE_SETTING(Model::TerminalSettings, bool, ScrollToZoom, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, AllowVtChecksumReport, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, TrimBlockSelection, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, DetectURLs, true); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 107773da06..ab6fa1fe01 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -42,6 +42,7 @@ X(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS) \ X(bool, CopyOnSelect, false) \ X(bool, FocusFollowMouse, false) \ + X(bool, ScrollToZoom, true) \ X(winrt::Windows::Foundation::IReference, TabColor, nullptr) \ X(winrt::Windows::Foundation::IReference, StartingTabColor, nullptr) \ X(bool, TrimBlockSelection, true) \ From fedf7b3b6c190c7b11819da2a2b5892cc54db961 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 18 Jul 2025 19:09:12 -0500 Subject: [PATCH 108/177] Undo the damage done to FilteredCommand in #17330 (#19148) PR #17330 changed FilteredCommand so that FilteredTask could derive from it. It also **did not** implement FilteredTask as a derived class. We've been carrying around the debt of a decision we un-decided for like a year. --- src/cascadia/TerminalApp/FilteredCommand.cpp | 20 +++----------------- src/cascadia/TerminalApp/FilteredCommand.h | 5 +---- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/cascadia/TerminalApp/FilteredCommand.cpp b/src/cascadia/TerminalApp/FilteredCommand.cpp index 794476cdfb..d8b23abdfb 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.cpp +++ b/src/cascadia/TerminalApp/FilteredCommand.cpp @@ -21,25 +21,11 @@ namespace winrt::TerminalApp::implementation { // This class is a wrapper of IPaletteItem, that is used as an item of a filterable list in CommandPalette. // It manages a highlighted text that is computed by matching search filter characters to item name - FilteredCommand::FilteredCommand(const winrt::TerminalApp::IPaletteItem& item) + FilteredCommand::FilteredCommand(const winrt::TerminalApp::IPaletteItem& item) : + _Item{ item }, _Weight{ 0 } { - // Actually implement the ctor in _constructFilteredCommand - _constructFilteredCommand(item); - } - - // We need to actually implement the ctor in a separate helper. This is - // because we have a FilteredTask class which derives from FilteredCommand. - // HOWEVER, for cppwinrt ~ r e a s o n s ~, it doesn't actually derive from - // FilteredCommand directly, so we can't just use the FilteredCommand ctor - // directly in the base class. - void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::IPaletteItem& item) - { - _Item = item; - _Weight = 0; - - _update(); - // Recompute the highlighted name if the item name changes + // Our Item will not change, so we don't need to update the revoker if it does. _itemChangedRevoker = _Item.as().PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& /*sender*/, auto& e) { auto filteredCommand{ weakThis.get() }; if (filteredCommand && e.PropertyName() == L"Name") diff --git a/src/cascadia/TerminalApp/FilteredCommand.h b/src/cascadia/TerminalApp/FilteredCommand.h index e13aa476ad..e91995d9e0 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.h +++ b/src/cascadia/TerminalApp/FilteredCommand.h @@ -20,7 +20,7 @@ namespace winrt::TerminalApp::implementation FilteredCommand() = default; FilteredCommand(const winrt::TerminalApp::IPaletteItem& item); - virtual void UpdateFilter(std::shared_ptr pattern); + void UpdateFilter(std::shared_ptr pattern); static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second); @@ -29,9 +29,6 @@ namespace winrt::TerminalApp::implementation WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::Collections::IVector, NameHighlights, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise); - protected: - void _constructFilteredCommand(const winrt::TerminalApp::IPaletteItem& item); - private: std::shared_ptr _pattern; void _update(); From 3979e82c2bc31adbac8057eba59e7474ba434554 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 21 Jul 2025 17:11:26 -0500 Subject: [PATCH 109/177] Remove IconElement caching from PaletteItem (#19149) In #19132, we introduced caching for BasePaletteItem::ResolvedIcon as a late optimization. We actually can't cache those, because they're UI elements, with parents and relationships and all. When you filter a list with icons and the list elements change physical position, they're assigned new templates-- and new parents. Fixes e7939bb4e Co-authored-by: Eric Nelson --- src/cascadia/TerminalApp/BasePaletteItem.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalApp/BasePaletteItem.h b/src/cascadia/TerminalApp/BasePaletteItem.h index 1a74253759..c4ee9ce301 100644 --- a/src/cascadia/TerminalApp/BasePaletteItem.h +++ b/src/cascadia/TerminalApp/BasePaletteItem.h @@ -14,14 +14,14 @@ namespace winrt::TerminalApp::implementation Windows::UI::Xaml::Controls::IconElement ResolvedIcon() { const auto icon{ static_cast(this)->Icon() }; - if (!_resolvedIcon && !icon.empty()) + if (!icon.empty()) { const auto resolvedIcon{ Microsoft::Terminal::UI::IconPathConverter::IconWUX(icon) }; resolvedIcon.Width(16); resolvedIcon.Height(16); - _resolvedIcon = resolvedIcon; + return resolvedIcon; } - return _resolvedIcon; + return nullptr; } til::property_changed_event PropertyChanged; @@ -34,11 +34,7 @@ namespace winrt::TerminalApp::implementation void InvalidateResolvedIcon() { - _resolvedIcon = nullptr; BaseRaisePropertyChanged(L"ResolvedIcon"); } - - private: - Windows::UI::Xaml::Controls::IconElement _resolvedIcon{ nullptr }; }; } From 452fa87937f0335bb2f34036168d9f66ca6d7ca2 Mon Sep 17 00:00:00 2001 From: Paulina Kalicka <71526180+paulinek13@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:24:46 +0200 Subject: [PATCH 110/177] feat: add option to adjust opacity with Ctrl+Shift+scroll (#19151) ## Summary of the Pull Request This PR introduces an experimental setting that allows to toggle opacity changes with scrolling. ## References and Relevant Issues #3793 ## Detailed Description of the Pull Request / Additional comments By default, holding Ctrl + Shift while scrolling changes the terminal's opacity. This PR adds an option to disable that behavior. ## Validation Steps Performed I built the project locally and verified that the new feature works as intended. ## PR Checklist - [x] Resolves https://github.com/microsoft/terminal/issues/3793#issuecomment-3085684640 - [x] Tests ~~added/~~ passed - [x] Documentation updated - If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: https://github.com/MicrosoftDocs/terminal/pull/873 - [X] Schema updated (if necessary) --- doc/cascadia/profiles.schema.json | 5 +++++ src/cascadia/TerminalControl/ControlInteractivity.cpp | 4 ++-- src/cascadia/TerminalControl/IControlSettings.idl | 1 + src/cascadia/TerminalSettingsEditor/Interaction.xaml | 6 ++++++ src/cascadia/TerminalSettingsEditor/InteractionViewModel.h | 1 + .../TerminalSettingsEditor/InteractionViewModel.idl | 1 + .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 4 ++++ src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl | 1 + src/cascadia/TerminalSettingsModel/MTSMSettings.h | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.cpp | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + 12 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 10f5a8b87f..275db4fa37 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2381,6 +2381,11 @@ "description": "When set to true, holding the Ctrl key while scrolling will increase or decrease the terminal font size.", "type": "boolean" }, + "experimental.scrollToChangeOpacity": { + "default": true, + "description": "When set to true, holding the Ctrl and Shift keys while scrolling will change the window opacity.", + "type": "boolean" + }, "compatibility.allowHeadless": { "default": false, "description": "When set to true, Windows Terminal will run in the background. This allows globalSummon and quakeMode actions to work even when no windows are open.", diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index ba740a4364..d9bc37571b 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -515,11 +515,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto ctrlPressed = modifiers.IsCtrlPressed(); const auto shiftPressed = modifiers.IsShiftPressed(); - if (ctrlPressed && shiftPressed) + if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity()) { _mouseTransparencyHandler(delta); } - else if (ctrlPressed && _core->Settings().ScrollToZoom()) + else if (ctrlPressed && !shiftPressed && _core->Settings().ScrollToZoom()) { _mouseZoomHandler(delta); } diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2bd4bfc854..c273574ed7 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -61,6 +61,7 @@ namespace Microsoft.Terminal.Control Microsoft.Terminal.Control.CopyFormat CopyFormatting { get; }; Boolean FocusFollowMouse { get; }; Boolean ScrollToZoom { get; }; + Boolean ScrollToChangeOpacity { get; }; String Commandline { get; }; String StartingDirectory { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index eba8b8129e..84a5019ea6 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -89,6 +89,12 @@ Style="{StaticResource ToggleSwitchInExpanderStyle}" /> + + + + + Adjust terminal font size by scrolling while holding the Ctrl key Header for a control to toggle font size changes with scrolling. When enabled, holding the Ctrl key while scrolling will increase or decrease the terminal font size. + + Adjust terminal opacity by scrolling while holding the Ctrl and Shift keys + Header for a control to toggle opacity changes with scrolling. When enabled, holding the Ctrl and Shift keys while scrolling will increase or decrease the window opacity. + Pane animations Header for a control to toggle animations on panes. "Enabled" value enables the animations. diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 5a99e05bc3..7e6c1180e4 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -91,6 +91,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(String, StartupActions); INHERITABLE_SETTING(Boolean, FocusFollowMouse); INHERITABLE_SETTING(Boolean, ScrollToZoom); + INHERITABLE_SETTING(Boolean, ScrollToChangeOpacity); INHERITABLE_SETTING(WindowingMode, WindowingBehavior); INHERITABLE_SETTING(Boolean, TrimBlockSelection); INHERITABLE_SETTING(Boolean, DetectURLs); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 725cddbb68..5fa1fdd59c 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -25,6 +25,7 @@ Author(s): X(bool, CopyOnSelect, "copyOnSelect", false) \ X(bool, FocusFollowMouse, "focusFollowMouse", false) \ X(bool, ScrollToZoom, "experimental.scrollToZoom", true) \ + X(bool, ScrollToChangeOpacity, "experimental.scrollToChangeOpacity", true) \ X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI, "rendering.graphicsAPI") \ X(bool, DisablePartialInvalidation, "rendering.disablePartialInvalidation", false) \ X(bool, SoftwareRendering, "rendering.software", false) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 5b34afbc5a..5e7e5ec29f 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -368,6 +368,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _CopyFormatting = globalSettings.CopyFormatting(); _FocusFollowMouse = globalSettings.FocusFollowMouse(); _ScrollToZoom = globalSettings.ScrollToZoom(); + _ScrollToChangeOpacity = globalSettings.ScrollToChangeOpacity(); _GraphicsAPI = globalSettings.GraphicsAPI(); _DisablePartialInvalidation = globalSettings.DisablePartialInvalidation(); _SoftwareRendering = globalSettings.SoftwareRendering(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index b9f4af3d30..a2f20bd4b2 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -95,6 +95,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0); INHERITABLE_SETTING(Model::TerminalSettings, bool, FocusFollowMouse, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, ScrollToZoom, true); + INHERITABLE_SETTING(Model::TerminalSettings, bool, ScrollToChangeOpacity, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, AllowVtChecksumReport, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, TrimBlockSelection, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, DetectURLs, true); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index ab6fa1fe01..d4e12a94b4 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -43,6 +43,7 @@ X(bool, CopyOnSelect, false) \ X(bool, FocusFollowMouse, false) \ X(bool, ScrollToZoom, true) \ + X(bool, ScrollToChangeOpacity, true) \ X(winrt::Windows::Foundation::IReference, TabColor, nullptr) \ X(winrt::Windows::Foundation::IReference, StartingTabColor, nullptr) \ X(bool, TrimBlockSelection, true) \ From 8c20d2052df20a6cb40b2f6d0e0b3d1dbbfb529b Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 21 Jul 2025 15:33:52 -0700 Subject: [PATCH 111/177] Add telemetry for new tab menu traffic (#19142) ## Summary of the Pull Request Adds new telemetry events to track traffic through the new tab menu. Specifically, the following events are added: - `NewTabMenuDefaultButtonClicked`: Event emitted when the default button from the new tab split button is invoked - `NewTabMenuOpened`: Event emitted when the new tab menu is opened - `NewTabMenuClosed`: Event emitted when the new tab menu is closed - `NewTabMenuItemClicked`: Event emitted when an item from the new tab menu is invoked - Has an `ItemType` parameter that can be set to `Settings`, `CommandPalette`, `About, `Profile`, `Action` - Has a `TabCount` parameter that keeps tracked of the number of tabs in the window before changing the state - `NewTabMenuCreatedNewTerminalSession`: Event emitted when a new terminal was created via the new tab menu - Has a `SessionType` parameter that can be set to `ElevatedWindow`, `Window`, `Pane`, `Tab` - Instead of `TabCount`, has a `NewTabCount` that keeps track of the _new_ number of tabs after the session has been created - `NewTabMenuItemElevateSubmenuItemClicked`: Event emitted when the elevate submenu item from the new tab menu is invoked ## Validation Steps Performed Used TVPP to see events generated from interacting with the new tab menu. --- src/cascadia/TerminalApp/TerminalPage.cpp | 122 +++++++++++++++++++++- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 1de587c26f..87ccb19a32 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -237,6 +237,14 @@ namespace winrt::TerminalApp::implementation _newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuDefaultButtonClicked", + TraceLoggingDescription("Event emitted when the default button from the new tab split button is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + page->_OpenNewTerminalViaDropdown(NewTerminalArgs()); } }); @@ -834,14 +842,36 @@ namespace winrt::TerminalApp::implementation // Since the previous focus location might be discarded in the background, // e.g., the command palette will be dismissed by the menu, // and then closing the fly-out will move the focus to wrong location. - newTabFlyout.Opening([this](auto&&, auto&&) { - _FocusCurrentTab(true); + newTabFlyout.Opening([weakThis{ get_weak() }](auto&&, auto&&) { + if (auto page{ weakThis.get() }) + { + page->_FocusCurrentTab(true); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuOpened", + TraceLoggingDescription("Event emitted when the new tab menu is opened"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The Count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } }); // Necessary for fly-out sub items to get focus on a tab before collapsing. Related to #15049 - newTabFlyout.Closing([this](auto&&, auto&&) { - if (!_commandPaletteIs(Visibility::Visible)) + newTabFlyout.Closing([weakThis{ get_weak() }](auto&&, auto&&) { + if (auto page{ weakThis.get() }) { - _FocusCurrentTab(true); + if (!page->_commandPaletteIs(Visibility::Visible)) + { + page->_FocusCurrentTab(true); + } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuClosed", + TraceLoggingDescription("Event emitted when the new tab menu is closed"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The Count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } }); _newTabButton.Flyout(newTabFlyout); @@ -1047,6 +1077,15 @@ namespace winrt::TerminalApp::implementation profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("Profile", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + NewTerminalArgs newTerminalArgs{ profileIndex }; page->_OpenNewTerminalViaDropdown(newTerminalArgs); } @@ -1093,6 +1132,15 @@ namespace winrt::TerminalApp::implementation actionMenuItem.Click([action, weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("Action", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + page->_actionDispatch->DoAction(action.ActionAndArgs()); } }); @@ -1154,6 +1202,7 @@ namespace winrt::TerminalApp::implementation const auto dispatchToElevatedWindow = ctrlPressed && !IsRunningElevated(); + auto sessionType = ""; if ((shiftPressed || dispatchToElevatedWindow) && !debugTap) { // Manually fill in the evaluated profile. @@ -1171,10 +1220,12 @@ namespace winrt::TerminalApp::implementation if (dispatchToElevatedWindow) { _OpenElevatedWT(newTerminalArgs); + sessionType = "ElevatedWindow"; } else { _OpenNewWindow(newTerminalArgs); + sessionType = "Window"; } } else @@ -1193,12 +1244,23 @@ namespace winrt::TerminalApp::implementation SplitDirection::Automatic, 0.5f, newPane); + sessionType = "Pane"; } else { _CreateNewTabFromPane(newPane); + sessionType = "Tab"; } } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuCreatedNewTerminalSession", + TraceLoggingDescription("Event emitted when a new terminal was created via the new tab menu"), + TraceLoggingValue(NumberOfTabs(), "NewTabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue(sessionType, "SessionType", "The type of session that was created"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } std::wstring TerminalPage::_evaluatePathForCwd(const std::wstring_view path) @@ -1405,6 +1467,30 @@ namespace winrt::TerminalApp::implementation { target = SettingsTarget::DefaultsFile; } + + const auto targetAsString = [&target]() { + switch (target) + { + case SettingsTarget::SettingsFile: + return "SettingsFile"; + case SettingsTarget::DefaultsFile: + return "DefaultsFile"; + case SettingsTarget::SettingsUI: + default: + return "UI"; + } + }(); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("Settings", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingValue(targetAsString, "SettingsTarget", "The target settings file or UI"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + _LaunchSettings(target); } @@ -1416,6 +1502,15 @@ namespace winrt::TerminalApp::implementation auto p = LoadCommandPalette(); p.EnableCommandPaletteMode(CommandPaletteLaunchMode::Action); p.Visibility(Visibility::Visible); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("CommandPalette", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } // Method Description: @@ -1428,6 +1523,15 @@ namespace winrt::TerminalApp::implementation const RoutedEventArgs&) { _ShowAboutDialog(); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("About", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } // Method Description: @@ -5365,6 +5469,14 @@ namespace winrt::TerminalApp::implementation runAsAdminItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemElevateSubmenuItemClicked", + TraceLoggingDescription("Event emitted when the elevate submenu item from the new tab menu is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + NewTerminalArgs args{ profileIndex }; args.Elevate(true); page->_OpenNewTerminalViaDropdown(args); From cb0289fff263924b0564a7bf6e876765965a2d37 Mon Sep 17 00:00:00 2001 From: HO-COOH Date: Tue, 22 Jul 2025 06:35:09 +0800 Subject: [PATCH 112/177] Center text in the Default Terminal dropdown (#19072) This is a small UI fix so that we center the default terminal application ComboBox text. --- src/cascadia/TerminalSettingsEditor/Launch.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 44feee7016..cf9967882a 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -116,6 +116,7 @@ From f22295ef2c230f79340763b91a57f3f6c6739a24 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 22 Jul 2025 10:04:19 -0500 Subject: [PATCH 113/177] Merge TabBase+TerminalTab into just Tab (#19136) This removes the need to construct two objects per tab (TabBase, Actual Tab) and the other overhead inherent in WinRT composition-based inheritance. Important renames: - `GetTerminalTabImpl` -> `GetTabImpl` This pull request does not rename `TerminalTabStatus`; that is left as work for a future PR. Closes #17529 --- .../LocalTests_TerminalApp/TabTests.cpp | 54 +- .../TerminalApp/AppActionHandlers.cpp | 31 +- src/cascadia/TerminalApp/CommandPalette.cpp | 4 +- src/cascadia/TerminalApp/CommandPalette.h | 6 +- src/cascadia/TerminalApp/CommandPalette.idl | 6 +- .../TerminalApp/CommandPaletteItems.cpp | 10 +- .../TerminalApp/CommandPaletteItems.h | 11 +- src/cascadia/TerminalApp/Pane.cpp | 2 +- src/cascadia/TerminalApp/Pane.h | 4 +- .../TerminalApp/SuggestionsControl.idl | 1 - .../TerminalApp/{TerminalTab.cpp => Tab.cpp} | 853 ++++++++++++++++-- .../TerminalApp/{TerminalTab.h => Tab.h} | 88 +- .../TerminalApp/{TabBase.idl => Tab.idl} | 6 +- src/cascadia/TerminalApp/TabBase.cpp | 753 ---------------- src/cascadia/TerminalApp/TabBase.h | 92 -- src/cascadia/TerminalApp/TabManagement.cpp | 58 +- .../TerminalApp/TerminalAppLib.vcxproj | 17 +- .../TerminalAppLib.vcxproj.filters | 5 +- src/cascadia/TerminalApp/TerminalPage.cpp | 150 ++- src/cascadia/TerminalApp/TerminalPage.h | 66 +- src/cascadia/TerminalApp/TerminalTab.idl | 13 - .../TerminalApp/dll/TerminalApp.vcxproj | 1 - 22 files changed, 1034 insertions(+), 1197 deletions(-) rename src/cascadia/TerminalApp/{TerminalTab.cpp => Tab.cpp} (64%) rename src/cascadia/TerminalApp/{TerminalTab.h => Tab.h} (67%) rename src/cascadia/TerminalApp/{TabBase.idl => Tab.idl} (78%) delete mode 100644 src/cascadia/TerminalApp/TabBase.cpp delete mode 100644 src/cascadia/TerminalApp/TabBase.h delete mode 100644 src/cascadia/TerminalApp/TerminalTab.idl diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 941d5fa06f..4e1744c66c 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -8,7 +8,7 @@ #include "../TerminalApp/MinMaxCloseControl.h" #include "../TerminalApp/TabRowControl.h" #include "../TerminalApp/ShortcutActionDispatch.h" -#include "../TerminalApp/TerminalTab.h" +#include "../TerminalApp/Tab.h" #include "../TerminalApp/CommandPalette.h" #include "../TerminalApp/ContentManager.h" #include "CppWinrtTailored.h" @@ -307,7 +307,7 @@ namespace TerminalAppLocalTests // reliably in the unit tests. Log::Comment(L"Ensure we set the first tab as the selected one."); auto tab = page->_tabs.GetAt(0); - auto tabImpl = page->_GetTerminalTabImpl(tab); + auto tabImpl = page->_GetTabImpl(tab); page->_tabView.SelectedItem(tabImpl->TabViewItem()); page->_UpdatedSelectedTab(tab); }); @@ -510,7 +510,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); @@ -520,7 +520,7 @@ namespace TerminalAppLocalTests page->_SplitPane(nullptr, SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); @@ -538,7 +538,7 @@ namespace TerminalAppLocalTests page->_SplitPane(nullptr, SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(3, tab->GetLeafPaneCount(), L"We should successfully duplicate a pane hosting a deleted profile."); @@ -706,7 +706,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -717,7 +717,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -727,7 +727,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); @@ -744,7 +744,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -758,7 +758,7 @@ namespace TerminalAppLocalTests page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -772,7 +772,7 @@ namespace TerminalAppLocalTests page->_HandleMoveFocus(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -789,7 +789,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -803,7 +803,7 @@ namespace TerminalAppLocalTests page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -816,7 +816,7 @@ namespace TerminalAppLocalTests page->_HandleClosePane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); @@ -827,7 +827,7 @@ namespace TerminalAppLocalTests Log::Comment(L"Check to ensure there's only one pane left."); result = RunOnUIThread([&page]() { - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(1, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); @@ -850,7 +850,7 @@ namespace TerminalAppLocalTests uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0; TestOnUIThread([&]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); firstId = tab->_activePane->Id().value(); // We start with 1 tab, split vertically to get // ------------------- @@ -876,7 +876,7 @@ namespace TerminalAppLocalTests // | | | // ------------------- page->_SplitPane(nullptr, SplitDirection::Down, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); // Split again to make the 3rd tab thirdId = tab->_activePane->Id().value(); }); @@ -896,13 +896,13 @@ namespace TerminalAppLocalTests // | | | // ------------------- page->_SplitPane(nullptr, SplitDirection::Down, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); fourthId = tab->_activePane->Id().value(); }); Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // just to be complete, make sure we actually have 4 different ids VERIFY_ARE_NOT_EQUAL(firstId, fourthId); @@ -936,7 +936,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -967,7 +967,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -998,7 +998,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -1029,7 +1029,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -1174,16 +1174,16 @@ namespace TerminalAppLocalTests Log::Comment(L"give alphabetical names to all switch tab actions"); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(0))->Title(L"a"); + page->_GetTabImpl(page->_tabs.GetAt(0))->Title(L"a"); }); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(1))->Title(L"b"); + page->_GetTabImpl(page->_tabs.GetAt(1))->Title(L"b"); }); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(2))->Title(L"c"); + page->_GetTabImpl(page->_tabs.GetAt(2))->Title(L"c"); }); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(3))->Title(L"d"); + page->_GetTabImpl(page->_tabs.GetAt(3))->Title(L"d"); }); TestOnUIThread([&page]() { diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index afb3e0a6e5..65845ffc6c 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -41,13 +41,13 @@ namespace winrt::TerminalApp::implementation } return _GetActiveControl(); } - winrt::com_ptr TerminalPage::_senderOrFocusedTab(const IInspectable& sender) + winrt::com_ptr TerminalPage::_senderOrFocusedTab(const IInspectable& sender) { if (sender) { - if (auto tab{ sender.try_as() }) + if (auto tab = sender.try_as()) { - return _GetTerminalTabImpl(tab); + return _GetTabImpl(tab); } } return _GetFocusedTabImpl(); @@ -193,17 +193,17 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleCloseOtherPanes(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto& terminalTab{ _senderOrFocusedTab(sender) }) + if (const auto& activeTab{ _senderOrFocusedTab(sender) }) { - const auto activePane = terminalTab->GetActivePane(); - if (terminalTab->GetRootPane() != activePane) + const auto activePane = activeTab->GetActivePane(); + if (activeTab->GetRootPane() != activePane) { _UnZoomIfNeeded(); // Accumulate list of all unfocused leaf panes, ignore read-only panes std::vector unfocusedPaneIds; const auto activePaneId = activePane->Id(); - terminalTab->GetRootPane()->WalkTree([&](auto&& p) { + activeTab->GetRootPane()->WalkTree([&](auto&& p) { const auto id = p->Id(); if (id.has_value() && id != activePaneId && !p->ContainsReadOnly()) { @@ -215,7 +215,7 @@ namespace winrt::TerminalApp::implementation { // Start by removing the panes that were least recently added sort(begin(unfocusedPaneIds), end(unfocusedPaneIds), std::less()); - _ClosePanes(terminalTab->get_weak(), std::move(unfocusedPaneIds)); + _ClosePanes(activeTab->get_weak(), std::move(unfocusedPaneIds)); args.Handled(true); return; } @@ -281,9 +281,9 @@ namespace winrt::TerminalApp::implementation const auto& duplicateFromTab{ realArgs.SplitMode() == SplitType::Duplicate ? _GetFocusedTab() : nullptr }; - const auto& terminalTab{ _senderOrFocusedTab(sender) }; + const auto& activeTab{ _senderOrFocusedTab(sender) }; - _SplitPane(terminalTab, + _SplitPane(activeTab, realArgs.SplitDirection(), // This is safe, we're already filtering so the value is (0, 1) realArgs.SplitSize(), @@ -302,14 +302,14 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleTogglePaneZoom(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto terminalTab{ _senderOrFocusedTab(sender) }) + if (const auto activeTab{ _senderOrFocusedTab(sender) }) { // Don't do anything if there's only one pane. It's already zoomed. - if (terminalTab->GetLeafPaneCount() > 1) + if (activeTab->GetLeafPaneCount() > 1) { // Togging the zoom on the tab will cause the tab to inform us of // the new root Content for this tab. - terminalTab->ToggleZoom(); + activeTab->ToggleZoom(); } } @@ -783,7 +783,7 @@ namespace winrt::TerminalApp::implementation } // Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove - std::vector tabsToRemove; + std::vector tabsToRemove; if (index > 0) { std::copy(begin(_tabs), begin(_tabs) + index, std::back_inserter(tabsToRemove)); @@ -822,7 +822,7 @@ namespace winrt::TerminalApp::implementation } // Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove - std::vector tabsToRemove; + std::vector tabsToRemove; std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove)); _RemoveTabs(tabsToRemove); @@ -1559,7 +1559,6 @@ namespace winrt::TerminalApp::implementation activeTab->ToggleBroadcastInput(); args.Handled(true); } - // If the focused tab wasn't a TerminalTab, then leave handled=false } void TerminalPage::_HandleRestartConnection(const IInspectable& sender, diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 02c368e7e8..6caca24036 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -1072,7 +1072,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - void CommandPalette::_bindTabs( - const Windows::Foundation::Collections::IObservableVector& source, + const Windows::Foundation::Collections::IObservableVector& source, const Windows::Foundation::Collections::IVector& target) { target.Clear(); @@ -1084,7 +1084,7 @@ namespace winrt::TerminalApp::implementation } } - void CommandPalette::SetTabs(const Collections::IObservableVector& tabs, const Collections::IObservableVector& mruTabs) + void CommandPalette::SetTabs(const Collections::IObservableVector& tabs, const Collections::IObservableVector& mruTabs) { _bindTabs(tabs, _tabActions); _bindTabs(mruTabs, _mruTabActions); diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index 0da7ee1334..8e621b104d 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -31,7 +31,7 @@ namespace winrt::TerminalApp::implementation Windows::Foundation::Collections::IObservableVector FilteredActions(); - void SetTabs(const Windows::Foundation::Collections::IObservableVector& tabs, const Windows::Foundation::Collections::IObservableVector& mruTabs); + void SetTabs(const Windows::Foundation::Collections::IObservableVector& tabs, const Windows::Foundation::Collections::IObservableVector& mruTabs); void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); @@ -48,7 +48,7 @@ namespace winrt::TerminalApp::implementation void EnableTabSearchMode(); til::property_changed_event PropertyChanged; - til::typed_event SwitchToTabRequested; + til::typed_event SwitchToTabRequested; til::typed_event CommandLineExecutionRequested; til::typed_event DispatchCommandRequested; til::typed_event PreviewAction; @@ -135,7 +135,7 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::TabSwitcherMode _tabSwitcherMode; uint32_t _switcherStartIdx; - void _bindTabs(const Windows::Foundation::Collections::IObservableVector& source, const Windows::Foundation::Collections::IVector& target); + void _bindTabs(const Windows::Foundation::Collections::IObservableVector& source, const Windows::Foundation::Collections::IVector& target); void _anchorKeyUpHandler(); winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker; diff --git a/src/cascadia/TerminalApp/CommandPalette.idl b/src/cascadia/TerminalApp/CommandPalette.idl index 48787135a9..79fa26d736 100644 --- a/src/cascadia/TerminalApp/CommandPalette.idl +++ b/src/cascadia/TerminalApp/CommandPalette.idl @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "TabBase.idl"; +import "Tab.idl"; import "HighlightedTextControl.idl"; import "FilteredCommand.idl"; @@ -20,7 +20,7 @@ namespace TerminalApp Windows.Foundation.Collections.IObservableVector FilteredActions { get; }; - void SetTabs(Windows.Foundation.Collections.IObservableVector tabs, Windows.Foundation.Collections.IObservableVector mruTabs); + void SetTabs(Windows.Foundation.Collections.IObservableVector tabs, Windows.Foundation.Collections.IObservableVector mruTabs); void SetActionMap(Microsoft.Terminal.Settings.Model.IActionMapView actionMap); @@ -30,7 +30,7 @@ namespace TerminalApp void EnableTabSwitcherMode(UInt32 startIdx, Microsoft.Terminal.Settings.Model.TabSwitcherMode tabSwitcherMode); void EnableTabSearchMode(); - event Windows.Foundation.TypedEventHandler SwitchToTabRequested; + event Windows.Foundation.TypedEventHandler SwitchToTabRequested; event Windows.Foundation.TypedEventHandler DispatchCommandRequested; event Windows.Foundation.TypedEventHandler CommandLineExecutionRequested; event Windows.Foundation.TypedEventHandler PreviewAction; diff --git a/src/cascadia/TerminalApp/CommandPaletteItems.cpp b/src/cascadia/TerminalApp/CommandPaletteItems.cpp index e23009cff9..e7e66a5164 100644 --- a/src/cascadia/TerminalApp/CommandPaletteItems.cpp +++ b/src/cascadia/TerminalApp/CommandPaletteItems.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT license. #include "pch.h" -#include "TerminalTab.h" +#include "Tab.h" #include @@ -19,11 +19,11 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::TerminalApp::implementation { - TabPaletteItem::TabPaletteItem(const winrt::TerminalApp::TabBase& tab) : + TabPaletteItem::TabPaletteItem(const winrt::TerminalApp::Tab& tab) : _tab{ tab } { _tabChangedRevoker = tab.PropertyChanged(winrt::auto_revoke, [=](auto& sender, auto& e) { - if (auto senderTab{ sender.try_as() }) + if (auto senderTab{ sender.try_as() }) { auto changedProperty = e.PropertyName(); if (changedProperty == L"Title") @@ -38,10 +38,8 @@ namespace winrt::TerminalApp::implementation } }); - if (const auto terminalTab{ tab.try_as() }) + if (const auto status = tab.TabStatus()) { - const auto status = terminalTab.TabStatus(); - _tabStatusChangedRevoker = status.PropertyChanged(winrt::auto_revoke, [=](auto& /*sender*/, auto& /*e*/) { // Sometimes nested bindings do not get updated, // thus let's notify property changed on TabStatus when one of its properties changes diff --git a/src/cascadia/TerminalApp/CommandPaletteItems.h b/src/cascadia/TerminalApp/CommandPaletteItems.h index e625ac1d76..1912ee37b4 100644 --- a/src/cascadia/TerminalApp/CommandPaletteItems.h +++ b/src/cascadia/TerminalApp/CommandPaletteItems.h @@ -71,9 +71,9 @@ namespace winrt::TerminalApp::implementation public TabPaletteItemT, BasePaletteItem { - TabPaletteItem(const winrt::TerminalApp::TabBase& tab); + TabPaletteItem(const winrt::TerminalApp::Tab& tab); - winrt::TerminalApp::TabBase Tab() const noexcept + winrt::TerminalApp::Tab Tab() const noexcept { return _tab.get(); } @@ -105,16 +105,13 @@ namespace winrt::TerminalApp::implementation { if (auto tab = _tab.get()) { - if (auto terminalTab = tab.try_as()) - { - return terminalTab.TabStatus(); - } + return tab.TabStatus(); } return { nullptr }; } private: - winrt::weak_ref _tab; + winrt::weak_ref _tab; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _tabChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _tabStatusChangedRevoker; }; diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index d8c7cd47d8..1afe0d118a 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -2429,7 +2429,7 @@ std::optional Pane::Id() noexcept // Method Description: // - Sets this pane's ID -// - Panes are given IDs upon creation by TerminalTab +// - Panes are given IDs upon creation by Tab // Arguments: // - The number to set this pane's ID to void Pane::Id(uint32_t id) noexcept diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 8bce64852d..ecc81fad82 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -31,7 +31,7 @@ namespace TerminalAppLocalTests namespace winrt::TerminalApp::implementation { - struct TerminalTab; + struct Tab; } enum class Borders : int @@ -398,6 +398,6 @@ private: LayoutSizeNode& operator=(const LayoutSizeNode& other); }; - friend struct winrt::TerminalApp::implementation::TerminalTab; + friend struct winrt::TerminalApp::implementation::Tab; friend class ::TerminalAppLocalTests::TabTests; }; diff --git a/src/cascadia/TerminalApp/SuggestionsControl.idl b/src/cascadia/TerminalApp/SuggestionsControl.idl index 0faea43928..e2b00784d9 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.idl +++ b/src/cascadia/TerminalApp/SuggestionsControl.idl @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "TabBase.idl"; import "HighlightedTextControl.idl"; import "FilteredCommand.idl"; diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/Tab.cpp similarity index 64% rename from src/cascadia/TerminalApp/TerminalTab.cpp rename to src/cascadia/TerminalApp/Tab.cpp index c19730b597..6c305c66d1 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -4,9 +4,9 @@ #include "pch.h" #include #include "ColorPickupFlyout.h" -#include "TerminalTab.h" +#include "Tab.h" #include "SettingsPaneContent.h" -#include "TerminalTab.g.cpp" +#include "Tab.g.cpp" #include "Utils.h" #include "ColorHelper.h" #include "AppLogic.h" @@ -30,7 +30,7 @@ namespace winrt namespace winrt::TerminalApp::implementation { - TerminalTab::TerminalTab(std::shared_ptr rootPane) + Tab::Tab(std::shared_ptr rootPane) { _rootPane = rootPane; _activePane = nullptr; @@ -77,7 +77,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_Setup() + void Tab::_Setup() { _rootClosedToken = _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) { Closed.raise(nullptr, nullptr); @@ -118,7 +118,7 @@ namespace winrt::TerminalApp::implementation // - Removes the bell indicator from the tab header // Arguments: // - sender, e: not used - void TerminalTab::_BellIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/) + void Tab::_BellIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/) { ShowBellIndicator(false); _bellIndicatorTimer.Stop(); @@ -130,9 +130,31 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_MakeTabViewItem() + void Tab::_MakeTabViewItem() { - TabBase::_MakeTabViewItem(); + TabViewItem(::winrt::MUX::Controls::TabViewItem{}); + + // GH#3609 If the tab was tapped, and no one else was around to handle + // it, then ask our parent to toss focus into the active control. + TabViewItem().Tapped([weakThis{ get_weak() }](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->RequestFocusActiveControl.raise(); + } + }); + + // BODGY: When the tab is drag/dropped, the TabView gets a + // TabDragStarting. However, the way it is implemented[^1], the + // TabViewItem needs either an Item or a Content for the event to + // include the correct TabViewItem. Otherwise, it will just return the + // first TabViewItem in the TabView with the same Content as the dragged + // tab (which, if the Content is null, will be the _first_ tab). + // + // So here, we'll stick an empty border in, just so that every tab has a + // Content which is not equal to the others. + // + // [^1]: microsoft-ui-xaml/blob/92fbfcd55f05c92ac65569f5d284c5b36492091e/dev/TabView/TabView.cpp#L751-L758 + TabViewItem().Content(winrt::WUX::Controls::Border{}); TabViewItem().DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) { if (auto tab{ weakThis.get() }) @@ -145,7 +167,7 @@ namespace winrt::TerminalApp::implementation _RecalculateAndApplyTabColor(); } - void TerminalTab::_UpdateHeaderControlMaxWidth() + void Tab::_UpdateHeaderControlMaxWidth() { try { @@ -164,6 +186,73 @@ namespace winrt::TerminalApp::implementation CATCH_LOG() } + void Tab::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) + { + ASSERT_UI_THREAD(); + + _dispatch = dispatch; + } + + void Tab::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) + { + ASSERT_UI_THREAD(); + + _actionMap = actionMap; + _UpdateSwitchToTabKeyChord(); + } + + // Method Description: + // - Sets the key chord resulting in switch to the current tab. + // Updates tool tip if required + // Arguments: + // - keyChord - string representation of the key chord that switches to the current tab + // Return Value: + // - + void Tab::_UpdateSwitchToTabKeyChord() + { + const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex); + const auto keyChord{ _actionMap.GetKeyBindingForAction(id) }; + const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L""; + + if (_keyChord == keyChordText) + { + return; + } + + _keyChord = keyChordText; + _UpdateToolTip(); + } + + // Method Description: + // - Sets tab tool tip to a concatenation of title and key chord + // Arguments: + // - + // Return Value: + // - + void Tab::_UpdateToolTip() + { + auto titleRun = WUX::Documents::Run(); + titleRun.Text(_CreateToolTipTitle()); + + auto textBlock = WUX::Controls::TextBlock{}; + textBlock.TextWrapping(WUX::TextWrapping::Wrap); + textBlock.TextAlignment(WUX::TextAlignment::Center); + textBlock.Inlines().Append(titleRun); + + if (!_keyChord.empty()) + { + auto keyChordRun = WUX::Documents::Run(); + keyChordRun.Text(_keyChord); + keyChordRun.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic); + textBlock.Inlines().Append(WUX::Documents::LineBreak{}); + textBlock.Inlines().Append(keyChordRun); + } + + WUX::Controls::ToolTip toolTip{}; + toolTip.Content(textBlock); + WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip); + } + // Method Description: // - Returns nullptr if no children of this tab were the last control to be // focused, the active control of the current pane, or the last active child control @@ -175,7 +264,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - nullptr if no children were marked `_lastFocused`, else the TermControl // that was last focused. - TermControl TerminalTab::GetActiveTerminalControl() const + TermControl Tab::GetActiveTerminalControl() const { ASSERT_UI_THREAD(); @@ -186,7 +275,7 @@ namespace winrt::TerminalApp::implementation return nullptr; } - IPaneContent TerminalTab::GetActiveContent() const + IPaneContent Tab::GetActiveContent() const { return _activePane ? _activePane->GetContent() : nullptr; } @@ -198,7 +287,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::Initialize() + void Tab::Initialize() { ASSERT_UI_THREAD(); @@ -219,7 +308,7 @@ namespace winrt::TerminalApp::implementation // - focused: our new focus state // Return Value: // - - void TerminalTab::Focus(WUX::FocusState focusState) + void Tab::Focus(WUX::FocusState focusState) { ASSERT_UI_THREAD(); @@ -253,7 +342,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - nullopt if no children of this tab were the last control to be // focused, else the GUID of the profile of the last control to be focused - Profile TerminalTab::GetFocusedProfile() const noexcept + Profile Tab::GetFocusedProfile() const noexcept { ASSERT_UI_THREAD(); @@ -266,7 +355,7 @@ namespace winrt::TerminalApp::implementation // of the settings that apply to all tabs. // Return Value: // - - void TerminalTab::UpdateSettings(const CascadiaSettings& settings) + void Tab::UpdateSettings(const CascadiaSettings& settings) { ASSERT_UI_THREAD(); @@ -286,7 +375,7 @@ namespace winrt::TerminalApp::implementation // - iconPath: The new path string to use as the IconPath for our TabViewItem // Return Value: // - - void TerminalTab::UpdateIcon(const winrt::hstring& iconPath, const winrt::Microsoft::Terminal::Settings::Model::IconStyle iconStyle) + void Tab::UpdateIcon(const winrt::hstring& iconPath, const winrt::Microsoft::Terminal::Settings::Model::IconStyle iconStyle) { ASSERT_UI_THREAD(); @@ -324,7 +413,7 @@ namespace winrt::TerminalApp::implementation // - Used when we want to show the progress ring, which should replace the icon // Arguments: // - hide: if true, we hide the icon; if false, we show the icon - void TerminalTab::HideIcon(const bool hide) + void Tab::HideIcon(const bool hide) { ASSERT_UI_THREAD(); @@ -348,7 +437,7 @@ namespace winrt::TerminalApp::implementation // - Hide or show the bell indicator in the tab header // Arguments: // - show: if true, we show the indicator; if false, we hide the indicator - void TerminalTab::ShowBellIndicator(const bool show) + void Tab::ShowBellIndicator(const bool show) { ASSERT_UI_THREAD(); @@ -358,14 +447,14 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Activates the timer for the bell indicator in the tab // - Called if a bell raised when the tab already has focus - void TerminalTab::ActivateBellIndicatorTimer() + void Tab::ActivateBellIndicatorTimer() { ASSERT_UI_THREAD(); if (!_bellIndicatorTimer) { _bellIndicatorTimer.Interval(std::chrono::milliseconds(2000)); - _bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick }); + _bellIndicatorTimer.Tick({ get_weak(), &Tab::_BellIndicatorTimerTick }); } _bellIndicatorTimer.Start(); @@ -378,7 +467,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - the title string of the last focused terminal control in our tree. - winrt::hstring TerminalTab::_GetActiveTitle() const + winrt::hstring Tab::_GetActiveTitle() const { if (!_runtimeTabText.empty()) { @@ -400,7 +489,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::UpdateTitle() + void Tab::UpdateTitle() { ASSERT_UI_THREAD(); @@ -422,7 +511,7 @@ namespace winrt::TerminalApp::implementation // - delta: a number of lines to move the viewport relative to the current viewport. // Return Value: // - - void TerminalTab::Scroll(const int delta) + void Tab::Scroll(const int delta) { ASSERT_UI_THREAD(); @@ -438,7 +527,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - A vector of commands - std::vector TerminalTab::BuildStartupActions(BuildStartupKind kind) const + std::vector Tab::BuildStartupActions(BuildStartupKind kind) const { ASSERT_UI_THREAD(); @@ -525,9 +614,9 @@ namespace winrt::TerminalApp::implementation // could itself be a parent pane/the root node of a tree of panes // Return Value: // - a pair of (the Pane that now holds the original content, the new Pane in the tree) - std::pair, std::shared_ptr> TerminalTab::SplitPane(SplitDirection splitType, - const float splitSize, - std::shared_ptr pane) + std::pair, std::shared_ptr> Tab::SplitPane(SplitDirection splitType, + const float splitSize, + std::shared_ptr pane) { ASSERT_UI_THREAD(); @@ -586,7 +675,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The removed pane, if the remove succeeded. - std::shared_ptr TerminalTab::DetachPane() + std::shared_ptr Tab::DetachPane() { ASSERT_UI_THREAD(); @@ -615,7 +704,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The root pane. - std::shared_ptr TerminalTab::DetachRoot() + std::shared_ptr Tab::DetachRoot() { ASSERT_UI_THREAD(); @@ -643,7 +732,7 @@ namespace winrt::TerminalApp::implementation // - pane: The pane to add. // Return Value: // - - void TerminalTab::AttachPane(std::shared_ptr pane) + void Tab::AttachPane(std::shared_ptr pane) { ASSERT_UI_THREAD(); @@ -694,7 +783,7 @@ namespace winrt::TerminalApp::implementation // - colorPicker: The color picker that we should attach to ourselves // Return Value: // - - void TerminalTab::AttachColorPicker(TerminalApp::ColorPickupFlyout& colorPicker) + void Tab::AttachColorPicker(TerminalApp::ColorPickupFlyout& colorPicker) { ASSERT_UI_THREAD(); @@ -734,7 +823,7 @@ namespace winrt::TerminalApp::implementation // its parent. E.g. switch from Horizontal to Vertical. // Return Value: // - - void TerminalTab::ToggleSplitOrientation() + void Tab::ToggleSplitOrientation() { ASSERT_UI_THREAD(); @@ -743,7 +832,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - See Pane::CalcSnappedDimension - float TerminalTab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const + float Tab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { ASSERT_UI_THREAD(); @@ -757,7 +846,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the separator in. // Return Value: // - - void TerminalTab::ResizePane(const ResizeDirection& direction) + void Tab::ResizePane(const ResizeDirection& direction) { ASSERT_UI_THREAD(); @@ -774,7 +863,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - Whether changing the focus succeeded. This allows a keychord to propagate // to the terminal when no other panes are present (GH#6219) - bool TerminalTab::NavigateFocus(const FocusDirection& direction) + bool Tab::NavigateFocus(const FocusDirection& direction) { ASSERT_UI_THREAD(); @@ -806,7 +895,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the pane in. // Return Value: // - true if two panes were swapped. - bool TerminalTab::SwapPane(const FocusDirection& direction) + bool Tab::SwapPane(const FocusDirection& direction) { ASSERT_UI_THREAD(); @@ -831,7 +920,7 @@ namespace winrt::TerminalApp::implementation return false; } - bool TerminalTab::FocusPane(const uint32_t id) + bool Tab::FocusPane(const uint32_t id) { ASSERT_UI_THREAD(); @@ -847,12 +936,12 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections. - void TerminalTab::Shutdown() + void Tab::Shutdown() { ASSERT_UI_THREAD(); - // Don't forget to call the overridden function. :) - TabBase::Shutdown(); + // NOTE: `TerminalPage::_HandleCloseTabRequested` relies on the content being null after this call. + Content(nullptr); if (_rootPane) { @@ -868,14 +957,14 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ClosePane() + void Tab::ClosePane() { ASSERT_UI_THREAD(); _activePane->Close(); } - void TerminalTab::SetTabText(winrt::hstring title) + void Tab::SetTabText(winrt::hstring title) { ASSERT_UI_THREAD(); @@ -883,14 +972,14 @@ namespace winrt::TerminalApp::implementation UpdateTitle(); } - winrt::hstring TerminalTab::GetTabText() const + winrt::hstring Tab::GetTabText() const { ASSERT_UI_THREAD(); return _runtimeTabText; } - void TerminalTab::ResetTabText() + void Tab::ResetTabText() { ASSERT_UI_THREAD(); @@ -905,7 +994,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ActivateTabRenamer() + void Tab::ActivateTabRenamer() { ASSERT_UI_THREAD(); @@ -921,7 +1010,7 @@ namespace winrt::TerminalApp::implementation // - paneId: The ID of the pane that contains the given content. // Return Value: // - - void TerminalTab::_DetachEventHandlersFromContent(const uint32_t paneId) + void Tab::_DetachEventHandlersFromContent(const uint32_t paneId) { auto it = _contentEvents.find(paneId); if (it != _contentEvents.end()) @@ -943,7 +1032,7 @@ namespace winrt::TerminalApp::implementation // - control: the TermControl to add events to. // Return Value: // - - void TerminalTab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content) + void Tab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content) { auto weakThis{ get_weak() }; auto dispatcher = TabViewItem().Dispatcher(); @@ -1059,7 +1148,7 @@ namespace winrt::TerminalApp::implementation if (const auto& terminal{ content.try_as() }) { - events.RestartTerminalRequested = terminal.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalTab::_bubbleRestartTerminalRequested }); + events.RestartTerminalRequested = terminal.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &Tab::_bubbleRestartTerminalRequested }); } if (_tabStatus.IsInputBroadcastActive()) @@ -1085,7 +1174,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - A TaskbarState object representing the combined taskbar state and // progress percentage of all our panes. - winrt::TerminalApp::TaskbarState TerminalTab::GetCombinedTaskbarState() const + winrt::TerminalApp::TaskbarState Tab::GetCombinedTaskbarState() const { ASSERT_UI_THREAD(); @@ -1111,7 +1200,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_UpdateProgressState() + void Tab::_UpdateProgressState() { const auto state{ GetCombinedTaskbarState() }; @@ -1155,7 +1244,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_UpdateConnectionClosedState() + void Tab::_UpdateConnectionClosedState() { ASSERT_UI_THREAD(); @@ -1176,7 +1265,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalTab::_RestartActivePaneConnection() + void Tab::_RestartActivePaneConnection() { ActionAndArgs restartConnection{ ShortcutAction::RestartConnection, nullptr }; _dispatch.DoAction(*this, restartConnection); @@ -1190,7 +1279,7 @@ namespace winrt::TerminalApp::implementation // - pane: a Pane to mark as active. // Return Value: // - - void TerminalTab::_UpdateActivePane(std::shared_ptr pane) + void Tab::_UpdateActivePane(std::shared_ptr pane) { // Clear the active state of the entire tree, and mark only the pane as active. _rootPane->ClearActive(); @@ -1255,7 +1344,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_AttachEventHandlersToPane(std::shared_ptr pane) + void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) { auto weakThis{ get_weak() }; std::weak_ptr weakPane{ pane }; @@ -1369,6 +1458,139 @@ namespace winrt::TerminalApp::implementation }); } + void Tab::_AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) + { + auto weakThis{ get_weak() }; + + // Move to new window + { + Controls::FontIcon moveTabToNewWindowTabSymbol; + moveTabToNewWindowTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); + moveTabToNewWindowTabSymbol.Glyph(L"\xE8A7"); + + _moveToNewWindowMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + MoveTabArgs args{ L"new", MoveTabDirection::Forward }; + ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; + tab->_dispatch.DoAction(*tab, actionAndArgs); + } + }); + _moveToNewWindowMenuItem.Text(RS_(L"MoveTabToNewWindowText")); + _moveToNewWindowMenuItem.Icon(moveTabToNewWindowTabSymbol); + + const auto moveTabToNewWindowToolTip = RS_(L"MoveTabToNewWindowToolTip"); + WUX::Controls::ToolTipService::SetToolTip(_moveToNewWindowMenuItem, box_value(moveTabToNewWindowToolTip)); + Automation::AutomationProperties::SetHelpText(_moveToNewWindowMenuItem, moveTabToNewWindowToolTip); + } + + // Move left + { + _moveLeftMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + MoveTabArgs args{ hstring{}, MoveTabDirection::Backward }; + ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; + tab->_dispatch.DoAction(*tab, actionAndArgs); + } + }); + _moveLeftMenuItem.Text(RS_(L"TabMoveLeft")); + } + + // Move right + { + _moveRightMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + MoveTabArgs args{ hstring{}, MoveTabDirection::Forward }; + ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; + tab->_dispatch.DoAction(*tab, actionAndArgs); + } + }); + _moveRightMenuItem.Text(RS_(L"TabMoveRight")); + } + + // Create a sub-menu for our extended move tab items. + Controls::MenuFlyoutSubItem moveSubMenu; + moveSubMenu.Text(RS_(L"TabMoveSubMenu")); + moveSubMenu.Items().Append(_moveToNewWindowMenuItem); + moveSubMenu.Items().Append(_moveRightMenuItem); + moveSubMenu.Items().Append(_moveLeftMenuItem); + flyout.Items().Append(moveSubMenu); + } + + // Method Description: + // - Append the close menu items to the context menu flyout + // Arguments: + // - flyout - the menu flyout to which the close items must be appended + // Return Value: + // - the sub-item that we use for all the nested "close" entries. This + // enables subclasses to add their own entries to this menu. + winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem Tab::_AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) + { + auto weakThis{ get_weak() }; + + // Close tabs after + _closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + CloseTabsAfterArgs args{ tab->_TabViewIndex }; + ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args }; + tab->_dispatch.DoAction(*tab, closeTabsAfter); + } + }); + _closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter")); + const auto closeTabsAfterToolTip = RS_(L"TabCloseAfterToolTip"); + + WUX::Controls::ToolTipService::SetToolTip(_closeTabsAfterMenuItem, box_value(closeTabsAfterToolTip)); + Automation::AutomationProperties::SetHelpText(_closeTabsAfterMenuItem, closeTabsAfterToolTip); + + // Close other tabs + _closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + CloseOtherTabsArgs args{ tab->_TabViewIndex }; + ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args }; + tab->_dispatch.DoAction(*tab, closeOtherTabs); + } + }); + _closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther")); + const auto closeOtherTabsToolTip = RS_(L"TabCloseOtherToolTip"); + + WUX::Controls::ToolTipService::SetToolTip(_closeOtherTabsMenuItem, box_value(closeOtherTabsToolTip)); + Automation::AutomationProperties::SetHelpText(_closeOtherTabsMenuItem, closeOtherTabsToolTip); + + // Close + Controls::MenuFlyoutItem closeTabMenuItem; + Controls::FontIcon closeSymbol; + closeSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); + closeSymbol.Glyph(L"\xE711"); + + closeTabMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->CloseRequested.raise(nullptr, nullptr); + } + }); + closeTabMenuItem.Text(RS_(L"TabClose")); + closeTabMenuItem.Icon(closeSymbol); + const auto closeTabToolTip = RS_(L"TabCloseToolTip"); + + WUX::Controls::ToolTipService::SetToolTip(closeTabMenuItem, box_value(closeTabToolTip)); + Automation::AutomationProperties::SetHelpText(closeTabMenuItem, closeTabToolTip); + + // Create a sub-menu for our extended close items. + Controls::MenuFlyoutSubItem closeSubMenu; + closeSubMenu.Text(RS_(L"TabCloseSubMenu")); + closeSubMenu.Items().Append(_closeTabsAfterMenuItem); + closeSubMenu.Items().Append(_closeOtherTabsMenuItem); + flyout.Items().Append(closeSubMenu); + + flyout.Items().Append(closeTabMenuItem); + + return closeSubMenu; + } + // Method Description: // - Creates a context menu attached to the tab. // Currently contains elements allowing to select or @@ -1377,7 +1599,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_CreateContextMenu() + void Tab::_CreateContextMenu() { auto weakThis{ get_weak() }; @@ -1388,7 +1610,7 @@ namespace winrt::TerminalApp::implementation colorPickSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); colorPickSymbol.Glyph(L"\xE790"); - chooseColorMenuItem.Click({ get_weak(), &TerminalTab::_chooseColorClicked }); + chooseColorMenuItem.Click({ get_weak(), &Tab::_chooseColorClicked }); chooseColorMenuItem.Text(RS_(L"TabColorChoose")); chooseColorMenuItem.Icon(colorPickSymbol); @@ -1405,7 +1627,7 @@ namespace winrt::TerminalApp::implementation renameTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); renameTabSymbol.Glyph(L"\xE8AC"); // Rename - renameTabMenuItem.Click({ get_weak(), &TerminalTab::_renameTabClicked }); + renameTabMenuItem.Click({ get_weak(), &Tab::_renameTabClicked }); renameTabMenuItem.Text(RS_(L"RenameTabText")); renameTabMenuItem.Icon(renameTabSymbol); @@ -1422,7 +1644,7 @@ namespace winrt::TerminalApp::implementation duplicateTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); duplicateTabSymbol.Glyph(L"\xF5ED"); - duplicateTabMenuItem.Click({ get_weak(), &TerminalTab::_duplicateTabClicked }); + duplicateTabMenuItem.Click({ get_weak(), &Tab::_duplicateTabClicked }); duplicateTabMenuItem.Text(RS_(L"DuplicateTabText")); duplicateTabMenuItem.Icon(duplicateTabSymbol); @@ -1439,7 +1661,7 @@ namespace winrt::TerminalApp::implementation splitTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); splitTabSymbol.Glyph(L"\xF246"); // ViewDashboard - splitTabMenuItem.Click({ get_weak(), &TerminalTab::_splitTabClicked }); + splitTabMenuItem.Click({ get_weak(), &Tab::_splitTabClicked }); splitTabMenuItem.Text(RS_(L"SplitTabText")); splitTabMenuItem.Icon(splitTabSymbol); @@ -1452,7 +1674,7 @@ namespace winrt::TerminalApp::implementation Controls::MenuFlyoutItem closePaneMenuItem = _closePaneMenuItem; { // "Close pane" - closePaneMenuItem.Click({ get_weak(), &TerminalTab::_closePaneClicked }); + closePaneMenuItem.Click({ get_weak(), &Tab::_closePaneClicked }); closePaneMenuItem.Text(RS_(L"ClosePaneText")); const auto closePaneToolTip = RS_(L"ClosePaneToolTip"); @@ -1468,7 +1690,7 @@ namespace winrt::TerminalApp::implementation exportTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); exportTabSymbol.Glyph(L"\xE74E"); // Save - exportTabMenuItem.Click({ get_weak(), &TerminalTab::_exportTextClicked }); + exportTabMenuItem.Click({ get_weak(), &Tab::_exportTextClicked }); exportTabMenuItem.Text(RS_(L"ExportTabText")); exportTabMenuItem.Icon(exportTabSymbol); @@ -1485,7 +1707,7 @@ namespace winrt::TerminalApp::implementation findSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); findSymbol.Glyph(L"\xF78B"); // SearchMedium - findMenuItem.Click({ get_weak(), &TerminalTab::_findClicked }); + findMenuItem.Click({ get_weak(), &Tab::_findClicked }); findMenuItem.Text(RS_(L"FindText")); findMenuItem.Icon(findSymbol); @@ -1556,13 +1778,47 @@ namespace winrt::TerminalApp::implementation TabViewItem().ContextFlyout(contextMenuFlyout); } + // Method Description: + // - Enable menu items based on tab index and total number of tabs + // Arguments: + // - + // Return Value: + // - + void Tab::_EnableMenuItems() + { + const auto tabIndex = TabViewIndex(); + const auto numOfTabs = TabViewNumTabs(); + + // enabled if there are other tabs + _closeOtherTabsMenuItem.IsEnabled(numOfTabs > 1); + + // enabled if there are other tabs on the right + _closeTabsAfterMenuItem.IsEnabled(tabIndex < numOfTabs - 1); + + // enabled if not left-most tab + _moveLeftMenuItem.IsEnabled(tabIndex > 0); + + // enabled if not last tab + _moveRightMenuItem.IsEnabled(tabIndex < numOfTabs - 1); + } + + void Tab::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs) + { + ASSERT_UI_THREAD(); + + TabViewIndex(idx); + TabViewNumTabs(numTabs); + _EnableMenuItems(); + _UpdateSwitchToTabKeyChord(); + } + // Method Description: // Returns the tab color, if any // Arguments: // - // Return Value: // - The tab's color, if any - std::optional TerminalTab::GetTabColor() + std::optional Tab::GetTabColor() { ASSERT_UI_THREAD(); @@ -1602,7 +1858,7 @@ namespace winrt::TerminalApp::implementation // - color: the color the user picked for their tab // Return Value: // - - void TerminalTab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color) + void Tab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color) { ASSERT_UI_THREAD(); @@ -1619,7 +1875,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ResetRuntimeTabColor() + void Tab::ResetRuntimeTabColor() { ASSERT_UI_THREAD(); @@ -1628,7 +1884,7 @@ namespace winrt::TerminalApp::implementation _tabStatus.TabColorIndicator(GetTabColor().value_or(Windows::UI::Colors::Transparent())); } - winrt::Windows::UI::Xaml::Media::Brush TerminalTab::_BackgroundBrush() + winrt::Windows::UI::Xaml::Media::Brush Tab::_BackgroundBrush() { Media::Brush terminalBrush{ nullptr }; if (const auto& c{ GetActiveContent() }) @@ -1644,7 +1900,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The total number of leaf panes hosted by this tab. - int TerminalTab::GetLeafPaneCount() const noexcept + int Tab::GetLeafPaneCount() const noexcept { ASSERT_UI_THREAD(); @@ -1661,9 +1917,9 @@ namespace winrt::TerminalApp::implementation // Return value: // - This will return nullopt if a split of the given size/direction was not possible, // or it will return the split direction with automatic converted to a cardinal direction. - std::optional TerminalTab::PreCalculateCanSplit(SplitDirection splitType, - const float splitSize, - winrt::Windows::Foundation::Size availableSpace) const + std::optional Tab::PreCalculateCanSplit(SplitDirection splitType, + const float splitSize, + winrt::Windows::Foundation::Size availableSpace) const { ASSERT_UI_THREAD(); @@ -1676,7 +1932,7 @@ namespace winrt::TerminalApp::implementation // - newFocus: the new pane to be zoomed // Return Value: // - - void TerminalTab::UpdateZoom(std::shared_ptr newFocus) + void Tab::UpdateZoom(std::shared_ptr newFocus) { ASSERT_UI_THREAD(); @@ -1698,7 +1954,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ToggleZoom() + void Tab::ToggleZoom() { ASSERT_UI_THREAD(); @@ -1712,7 +1968,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalTab::EnterZoom() + void Tab::EnterZoom() { ASSERT_UI_THREAD(); @@ -1727,7 +1983,7 @@ namespace winrt::TerminalApp::implementation _tabStatus.IsPaneZoomed(true); Content(_zoomedPane->GetRootElement()); } - void TerminalTab::ExitZoom() + void Tab::ExitZoom() { ASSERT_UI_THREAD(); @@ -1739,7 +1995,7 @@ namespace winrt::TerminalApp::implementation Content(_rootPane->GetRootElement()); } - bool TerminalTab::IsZoomed() + bool Tab::IsZoomed() { ASSERT_UI_THREAD(); @@ -1762,7 +2018,7 @@ namespace winrt::TerminalApp::implementation // - Toggle read-only mode on the active pane // - If a parent pane is selected, this will ensure that all children have // the same read-only status. - void TerminalTab::TogglePaneReadOnly() + void Tab::TogglePaneReadOnly() { ASSERT_UI_THREAD(); @@ -1796,7 +2052,7 @@ namespace winrt::TerminalApp::implementation // - Set read-only mode on the active pane // - If a parent pane is selected, this will ensure that all children have // the same read-only status. - void TerminalTab::SetPaneReadOnly(const bool readOnlyState) + void Tab::SetPaneReadOnly(const bool readOnlyState) { auto hasReadOnly = false; auto allReadOnly = true; @@ -1828,7 +2084,7 @@ namespace winrt::TerminalApp::implementation // - Calculates if the tab is read-only. // The tab is considered read-only if one of the panes is read-only. // If after the calculation the tab is read-only we hide the close button on the tab view item - void TerminalTab::_RecalculateAndApplyReadOnly() + void Tab::_RecalculateAndApplyReadOnly() { const auto control = GetActiveTerminalControl(); if (control) @@ -1845,7 +2101,7 @@ namespace winrt::TerminalApp::implementation _rootPane->WalkTree([](const auto& p) { p->UpdateVisuals(); }); } - std::shared_ptr TerminalTab::GetActivePane() const + std::shared_ptr Tab::GetActivePane() const { ASSERT_UI_THREAD(); @@ -1859,7 +2115,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The value to populate in the title run of the tool tip - winrt::hstring TerminalTab::_CreateToolTipTitle() + winrt::hstring Tab::_CreateToolTipTitle() { if (const auto& control{ GetActiveTerminalControl() }) { @@ -1875,7 +2131,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Toggle broadcasting input to all the panes in this tab. - void TerminalTab::ToggleBroadcastInput() + void Tab::ToggleBroadcastInput() { const bool newIsBroadcasting = !_tabStatus.IsInputBroadcastActive(); _tabStatus.IsInputBroadcastActive(newIsBroadcasting); @@ -1914,7 +2170,7 @@ namespace winrt::TerminalApp::implementation }); } - void TerminalTab::_addBroadcastHandlers(const TermControl& termControl, ContentEventTokens& events) + void Tab::_addBroadcastHandlers(const TermControl& termControl, ContentEventTokens& events) { auto weakThis{ get_weak() }; // ADD EVENT HANDLERS HERE @@ -1957,48 +2213,451 @@ namespace winrt::TerminalApp::implementation }); } - void TerminalTab::_chooseColorClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, + const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, + const til::color& tabRowColor) + { + ASSERT_UI_THREAD(); + + _themeColor = focused; + _unfocusedThemeColor = unfocused; + _tabRowColor = tabRowColor; + _RecalculateAndApplyTabColor(); + } + + // Method Description: + // - This function dispatches a function to the UI thread to recalculate + // what this tab's current background color should be. If a color is set, + // it will apply the given color to the tab's background. Otherwise, it + // will clear the tab's background color. + // Arguments: + // - + // Return Value: + // - + void Tab::_RecalculateAndApplyTabColor() + { + // GetTabColor will return the color set by the color picker, or the + // color specified in the profile. If neither of those were set, + // then look to _themeColor to see if there's a value there. + // Otherwise, clear our color, falling back to the TabView defaults. + const auto currentColor = GetTabColor(); + if (currentColor.has_value()) + { + _ApplyTabColorOnUIThread(currentColor.value()); + } + else if (_themeColor != nullptr) + { + // Safely get the active control's brush. + const Media::Brush terminalBrush{ _BackgroundBrush() }; + + if (const auto themeBrush{ _themeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) + { + // ThemeColor.Evaluate will get us a Brush (because the + // TermControl could have an acrylic BG, for example). Take + // that brush, and get the color out of it. We don't really + // want to have the tab items themselves be acrylic. + _ApplyTabColorOnUIThread(til::color{ ThemeColor::ColorFromBrush(themeBrush) }); + } + else + { + _ClearTabBackgroundColor(); + } + } + else + { + _ClearTabBackgroundColor(); + } + } + + // Method Description: + // - Applies the given color to the background of this tab's TabViewItem. + // - Sets the tab foreground color depending on the luminance of + // the background color + // - This method should only be called on the UI thread. + // Arguments: + // - color: the color the user picked for their tab + // Return Value: + // - + void Tab::_ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color) + { + Media::SolidColorBrush selectedTabBrush{}; + Media::SolidColorBrush deselectedTabBrush{}; + Media::SolidColorBrush fontBrush{}; + Media::SolidColorBrush deselectedFontBrush{}; + Media::SolidColorBrush secondaryFontBrush{}; + Media::SolidColorBrush hoverTabBrush{}; + Media::SolidColorBrush subtleFillColorSecondaryBrush; + Media::SolidColorBrush subtleFillColorTertiaryBrush; + + // calculate the luminance of the current color and select a font + // color based on that + // see https://www.w3.org/TR/WCAG20/#relativeluminancedef + if (TerminalApp::ColorHelper::IsBrightColor(color)) + { + auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black(); + subtleFillColorSecondary.A = 0x09; + subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); + auto subtleFillColorTertiary = winrt::Windows::UI::Colors::Black(); + subtleFillColorTertiary.A = 0x06; + subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); + } + else + { + auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White(); + subtleFillColorSecondary.A = 0x0F; + subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); + auto subtleFillColorTertiary = winrt::Windows::UI::Colors::White(); + subtleFillColorTertiary.A = 0x0A; + subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); + } + + // The tab font should be based on the evaluated appearance of the tab color layered on tab row. + const auto layeredTabColor = til::color{ color }.layer_over(_tabRowColor); + if (TerminalApp::ColorHelper::IsBrightColor(layeredTabColor)) + { + fontBrush.Color(winrt::Windows::UI::Colors::Black()); + auto secondaryFontColor = winrt::Windows::UI::Colors::Black(); + // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269 + secondaryFontColor.A = 0x9E; + secondaryFontBrush.Color(secondaryFontColor); + } + else + { + fontBrush.Color(winrt::Windows::UI::Colors::White()); + auto secondaryFontColor = winrt::Windows::UI::Colors::White(); + // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14 + secondaryFontColor.A = 0xC5; + secondaryFontBrush.Color(secondaryFontColor); + } + + selectedTabBrush.Color(color); + + // Start with the current tab color, set to Opacity=.3 + til::color deselectedTabColor{ color }; + deselectedTabColor = deselectedTabColor.with_alpha(77); // 255 * .3 = 77 + + // If we DON'T have a color set from the color picker, or the profile's + // tabColor, but we do have a unfocused color in the theme, use the + // unfocused theme color here instead. + if (!GetTabColor().has_value() && + _unfocusedThemeColor != nullptr) + { + // Safely get the active control's brush. + const Media::Brush terminalBrush{ _BackgroundBrush() }; + + // Get the color of the brush. + if (const auto themeBrush{ _unfocusedThemeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) + { + // We did figure out the brush. Get the color out of it. If it + // was "accent" or "terminalBackground", then we're gonna set + // the alpha to .3 manually here. + // (ThemeColor::UnfocusedTabOpacity will do this for us). If the + // user sets both unfocused and focused tab.background to + // terminalBackground, this will allow for some differentiation + // (and is generally just sensible). + deselectedTabColor = til::color{ ThemeColor::ColorFromBrush(themeBrush) }.with_alpha(_unfocusedThemeColor.UnfocusedTabOpacity()); + } + } + + // currently if a tab has a custom color, a deselected state is + // signified by using the same color with a bit of transparency + deselectedTabBrush.Color(deselectedTabColor.with_alpha(255)); + deselectedTabBrush.Opacity(deselectedTabColor.a / 255.f); + + hoverTabBrush.Color(color); + hoverTabBrush.Opacity(0.6); + + // Account for the color of the tab row when setting the color of text + // on inactive tabs. Consider: + // * black active tabs + // * on a white tab row + // * with a transparent inactive tab color + // + // We don't want that to result in white text on a white tab row for + // inactive tabs. + const auto deselectedActualColor = deselectedTabColor.layer_over(_tabRowColor); + if (TerminalApp::ColorHelper::IsBrightColor(deselectedActualColor)) + { + deselectedFontBrush.Color(winrt::Windows::UI::Colors::Black()); + } + else + { + deselectedFontBrush.Color(winrt::Windows::UI::Colors::White()); + } + + // Add the empty theme dictionaries + const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; + ResourceDictionary lightThemeDictionary; + ResourceDictionary darkThemeDictionary; + ResourceDictionary highContrastThemeDictionary; + tabItemThemeResources.Insert(winrt::box_value(L"Light"), lightThemeDictionary); + tabItemThemeResources.Insert(winrt::box_value(L"Dark"), darkThemeDictionary); + tabItemThemeResources.Insert(winrt::box_value(L"HighContrast"), highContrastThemeDictionary); + + // Apply the color to the tab + TabViewItem().Background(deselectedTabBrush); + + // Now actually set the resources we want in them. + // Before, we used to put these on the ResourceDictionary directly. + // However, HighContrast mode may require some adjustments. So let's just add + // all three so we can make those adjustments on the HighContrast version. + for (const auto& [k, v] : tabItemThemeResources) + { + const bool isHighContrast = winrt::unbox_value(k) == L"HighContrast"; + const auto& currentDictionary = v.as(); + + // TabViewItem.Background + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), isHighContrast ? fontBrush : hoverTabBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); + + // TabViewItem.Foreground (aka text) + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForeground"), deselectedFontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), isHighContrast ? selectedTabBrush : fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); + + // TabViewItem.CloseButton.Foreground (aka X) + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForeground"), deselectedFontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPressed"), isHighContrast ? deselectedFontBrush : secondaryFontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPointerOver"), isHighContrast ? deselectedFontBrush : fontBrush); + + // TabViewItem.CloseButton.Foreground _when_ interacting with the tab + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPressedCloseButtonForeground"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPointerOverCloseButtonForeground"), isHighContrast ? selectedTabBrush : fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderSelectedCloseButtonForeground"), fontBrush); + + // TabViewItem.CloseButton.Background (aka X button) + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPressed"), isHighContrast ? selectedTabBrush : subtleFillColorTertiaryBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPointerOver"), isHighContrast ? selectedTabBrush : subtleFillColorSecondaryBrush); + + // A few miscellaneous resources that WinUI said may be removed in the future + currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPressed"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPointerOver"), fontBrush); + + // Add a few extra ones for high contrast mode + // BODGY: contrary to the docs, Insert() seems to throw if the value already exists + // Make sure you don't touch any that already exist here! + if (isHighContrast) + { + // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPressed"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPointerOver"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushSelected"), fontBrush); + } + } + + _RefreshVisualState(); + } + + // Method Description: + // - Clear out any color we've set for the TabViewItem. + // - This method should only be called on the UI thread. + // Arguments: + // - + // Return Value: + // - + void Tab::_ClearTabBackgroundColor() + { + static const winrt::hstring keys[] = { + // TabViewItem.Background + L"TabViewItemHeaderBackground", + L"TabViewItemHeaderBackgroundSelected", + L"TabViewItemHeaderBackgroundPointerOver", + L"TabViewItemHeaderBackgroundPressed", + + // TabViewItem.Foreground (aka text) + L"TabViewItemHeaderForeground", + L"TabViewItemHeaderForegroundSelected", + L"TabViewItemHeaderForegroundPointerOver", + L"TabViewItemHeaderForegroundPressed", + + // TabViewItem.CloseButton.Foreground (aka X) + L"TabViewItemHeaderCloseButtonForeground", + L"TabViewItemHeaderForegroundSelected", + L"TabViewItemHeaderCloseButtonForegroundPointerOver", + L"TabViewItemHeaderCloseButtonForegroundPressed", + + // TabViewItem.CloseButton.Foreground _when_ interacting with the tab + L"TabViewItemHeaderPressedCloseButtonForeground", + L"TabViewItemHeaderPointerOverCloseButtonForeground", + L"TabViewItemHeaderSelectedCloseButtonForeground", + + // TabViewItem.CloseButton.Background (aka X button) + L"TabViewItemHeaderCloseButtonBackground", + L"TabViewItemHeaderCloseButtonBackgroundPressed", + L"TabViewItemHeaderCloseButtonBackgroundPointerOver", + + // A few miscellaneous resources that WinUI said may be removed in the future + L"TabViewButtonForegroundActiveTab", + L"TabViewButtonForegroundPressed", + L"TabViewButtonForegroundPointerOver", + + // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible + L"TabViewItemHeaderCloseButtonBorderBrushPressed", + L"TabViewItemHeaderCloseButtonBorderBrushPointerOver", + L"TabViewItemHeaderCloseButtonBorderBrushSelected" + }; + + const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; + + // simply clear any of the colors in the tab's dict + for (const auto& keyString : keys) + { + const auto key = winrt::box_value(keyString); + for (const auto& [_, v] : tabItemThemeResources) + { + const auto& themeDictionary = v.as(); + themeDictionary.Remove(key); + } + } + + // GH#11382 DON'T set the background to null. If you do that, then the + // tab won't be hit testable at all. Transparent, however, is a totally + // valid hit test target. That makes sense. + TabViewItem().Background(WUX::Media::SolidColorBrush{ Windows::UI::Colors::Transparent() }); + + _RefreshVisualState(); + } + + // Method Description: + // BODGY + // - Toggles the requested theme of the tab view item, + // so that changes to the tab color are reflected immediately + // - Prior to MUX 2.8, we only toggled the visual state here, but that seemingly + // doesn't work in 2.8. + // - Just changing the Theme also doesn't seem to work by itself - there + // seems to be a way for the tab to set the deselected foreground onto + // itself as it becomes selected. If the mouse isn't over the tab, that + // can result in mismatched fg/bg's (see GH#15184). So that's right, we + // need to do both. + // Arguments: + // - + // Return Value: + // - + void Tab::_RefreshVisualState() + { + const auto& item{ TabViewItem() }; + + const auto& reqTheme = TabViewItem().RequestedTheme(); + item.RequestedTheme(ElementTheme::Light); + item.RequestedTheme(ElementTheme::Dark); + item.RequestedTheme(reqTheme); + + if (TabViewItem().IsSelected()) + { + VisualStateManager::GoToState(item, L"Normal", true); + VisualStateManager::GoToState(item, L"Selected", true); + } + else + { + VisualStateManager::GoToState(item, L"Selected", true); + VisualStateManager::GoToState(item, L"Normal", true); + } + } + + TabCloseButtonVisibility Tab::CloseButtonVisibility() + { + return _closeButtonVisibility; + } + + // Method Description: + // - set our internal state to track if we were requested to have a visible + // tab close button or not. + // - This is called every time the active tab changes. That way, the changes + // in focused tab can be reflected for the "ActiveOnly" state. + void Tab::CloseButtonVisibility(TabCloseButtonVisibility visibility) + { + _closeButtonVisibility = visibility; + _updateIsClosable(); + } + + // Method Description: + // - Update our close button's visibility, to reflect both the ReadOnly + // state of the tab content, and also if if we were told to have a visible + // close button at all. + // - the tab being read-only takes precedence. That will always suppress + // the close button. + // - Otherwise we'll use the state set in CloseButtonVisibility to control + // the tab's visibility. + void Tab::_updateIsClosable() + { + bool isClosable = true; + + if (ReadOnly()) + { + isClosable = false; + } + else + { + switch (_closeButtonVisibility) + { + case TabCloseButtonVisibility::Never: + isClosable = false; + break; + case TabCloseButtonVisibility::Hover: + isClosable = true; + break; + case TabCloseButtonVisibility::ActiveOnly: + isClosable = _focused(); + break; + default: + isClosable = true; + break; + } + } + TabViewItem().IsClosable(isClosable); + } + + bool Tab::_focused() const noexcept + { + return _focusState != FocusState::Unfocused; + } + + void Tab::_chooseColorClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { _dispatch.DoAction(*this, { ShortcutAction::OpenTabColorPicker, nullptr }); } - void TerminalTab::_renameTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_renameTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActivateTabRenamer(); } - void TerminalTab::_duplicateTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_duplicateTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{ ShortcutAction::DuplicateTab, nullptr }; _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_splitTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_splitTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate } }; _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_closePaneClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_closePaneClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ClosePane(); } - void TerminalTab::_exportTextClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_exportTextClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{}; actionAndArgs.Action(ShortcutAction::ExportBuffer); _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_findClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_findClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{ ShortcutAction::Find, nullptr }; _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_bubbleRestartTerminalRequested(TerminalApp::TerminalPaneContent sender, - const winrt::Windows::Foundation::IInspectable& args) + void Tab::_bubbleRestartTerminalRequested(TerminalApp::TerminalPaneContent sender, + const winrt::Windows::Foundation::IInspectable& args) { RestartTerminalRequested.raise(sender, args); } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/Tab.h similarity index 67% rename from src/cascadia/TerminalApp/TerminalTab.h rename to src/cascadia/TerminalApp/Tab.h index 29cc3d1b5e..be6180586e 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -4,8 +4,8 @@ #pragma once #include "Pane.h" #include "ColorPickupFlyout.h" -#include "TabBase.h" -#include "TerminalTab.g.h" +#include "Tab.h" +#include "Tab.g.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -15,10 +15,10 @@ namespace TerminalAppLocalTests namespace winrt::TerminalApp::implementation { - struct TerminalTab : TerminalTabT + struct Tab : TabT { public: - TerminalTab(std::shared_ptr rootPane); + Tab(std::shared_ptr rootPane); // Called after construction to perform the necessary setup, which relies on weak_ptr void Initialize(); @@ -27,7 +27,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile() const noexcept; winrt::TerminalApp::IPaneContent GetActiveContent() const; - void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override; + void Focus(winrt::Windows::UI::Xaml::FocusState focusState); void Scroll(const int delta); @@ -61,7 +61,7 @@ namespace winrt::TerminalApp::implementation void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); void UpdateTitle(); - void Shutdown() override; + void Shutdown(); void ClosePane(); void SetTabText(winrt::hstring title); @@ -69,7 +69,7 @@ namespace winrt::TerminalApp::implementation void ResetTabText(); void ActivateTabRenamer(); - virtual std::optional GetTabColor() override; + std::optional GetTabColor(); void SetRuntimeTabColor(const winrt::Windows::UI::Color& color); void ResetRuntimeTabColor(); @@ -79,7 +79,7 @@ namespace winrt::TerminalApp::implementation void EnterZoom(); void ExitZoom(); - std::vector BuildStartupActions(BuildStartupKind kind) const override; + std::vector BuildStartupActions(BuildStartupKind kind) const; int GetLeafPaneCount() const noexcept; @@ -98,16 +98,62 @@ namespace winrt::TerminalApp::implementation return _tabStatus; } + void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); + + void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs); + void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); + + void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, + const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, + const til::color& tabRowColor); + + Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility CloseButtonVisibility(); + void CloseButtonVisibility(Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility visible); + + til::event> RequestFocusActiveControl; + + til::event> Closed; + til::event> CloseRequested; + til::property_changed_event PropertyChanged; + til::typed_event RestartTerminalRequested; - til::typed_event ActivePaneChanged; + til::typed_event ActivePaneChanged; til::event> TabRaiseVisualBell; til::typed_event TaskbarProgressChanged; + // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. + WINRT_PROPERTY(uint32_t, TabViewIndex, 0); + // The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector. + WINRT_PROPERTY(uint32_t, TabViewNumTabs, 0); + + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Title, PropertyChanged.raise); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Icon, PropertyChanged.raise); + WINRT_OBSERVABLE_PROPERTY(bool, ReadOnly, PropertyChanged.raise, false); + WINRT_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr); + + WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::FrameworkElement, Content, PropertyChanged.raise, nullptr); + private: static constexpr double HeaderRenameBoxWidthDefault{ 165 }; static constexpr double HeaderRenameBoxWidthTitleLength{ std::numeric_limits::infinity() }; + winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused }; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveToNewWindowMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveRightMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveLeftMenuItem{}; + winrt::TerminalApp::ShortcutActionDispatch _dispatch; + Microsoft::Terminal::Settings::Model::IActionMapView _actionMap{ nullptr }; + winrt::hstring _keyChord{}; + + winrt::Microsoft::Terminal::Settings::Model::ThemeColor _themeColor{ nullptr }; + winrt::Microsoft::Terminal::Settings::Model::ThemeColor _unfocusedThemeColor{ nullptr }; + til::color _tabRowColor; + + Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility _closeButtonVisibility{ Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always }; + std::shared_ptr _rootPane{ nullptr }; std::shared_ptr _activePane{ nullptr }; std::shared_ptr _zoomedPane{ nullptr }; @@ -163,12 +209,10 @@ namespace winrt::TerminalApp::implementation SafeDispatcherTimer _bellIndicatorTimer; void _BellIndicatorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); - void _MakeTabViewItem() override; - void _UpdateHeaderControlMaxWidth(); - void _CreateContextMenu() override; - virtual winrt::hstring _CreateToolTipTitle() override; + void _CreateContextMenu(); + winrt::hstring _CreateToolTipTitle(); void _DetachEventHandlersFromContent(const uint32_t paneId); void _AttachEventHandlersToContent(const uint32_t paneId, const winrt::TerminalApp::IPaneContent& content); @@ -187,7 +231,23 @@ namespace winrt::TerminalApp::implementation void _DuplicateTab(); - virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override; + winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush(); + + void _MakeTabViewItem(); + + void _AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); + winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); + void _EnableMenuItems(); + void _UpdateSwitchToTabKeyChord(); + void _UpdateToolTip(); + + void _RecalculateAndApplyTabColor(); + void _ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color); + void _ClearTabBackgroundColor(); + void _RefreshVisualState(); + + bool _focused() const noexcept; + void _updateIsClosable(); void _addBroadcastHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control, ContentEventTokens& events); diff --git a/src/cascadia/TerminalApp/TabBase.idl b/src/cascadia/TerminalApp/Tab.idl similarity index 78% rename from src/cascadia/TerminalApp/TabBase.idl rename to src/cascadia/TerminalApp/Tab.idl index 20ee8ffa9d..0f214133a6 100644 --- a/src/cascadia/TerminalApp/TabBase.idl +++ b/src/cascadia/TerminalApp/Tab.idl @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import "ShortcutActionDispatch.idl"; +import "TerminalTabStatus.idl"; namespace TerminalApp { - unsealed runtimeclass TabBase : Windows.UI.Xaml.Data.INotifyPropertyChanged + runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged { String Title { get; }; String Icon { get; }; @@ -14,6 +15,9 @@ namespace TerminalApp Microsoft.UI.Xaml.Controls.TabViewItem TabViewItem { get; }; Windows.UI.Xaml.FrameworkElement Content { get; }; + // May be Null + TerminalTabStatus TabStatus { get; }; + UInt32 TabViewIndex; UInt32 TabViewNumTabs; diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp deleted file mode 100644 index 60e2e94bc2..0000000000 --- a/src/cascadia/TerminalApp/TabBase.cpp +++ /dev/null @@ -1,753 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include -#include "TabBase.h" -#include "TabBase.g.cpp" -#include "Utils.h" -#include "ColorHelper.h" - -using namespace winrt; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Core; -using namespace winrt::Microsoft::Terminal::Control; -using namespace winrt::Microsoft::Terminal::Settings::Model; -using namespace winrt::Windows::System; - -namespace winrt -{ - namespace MUX = Microsoft::UI::Xaml; - namespace WUX = Windows::UI::Xaml; -} - -#define ASSERT_UI_THREAD() assert(TabViewItem().Dispatcher().HasThreadAccess()) - -namespace winrt::TerminalApp::implementation -{ - - // Method Description: - // - Prepares this tab for being removed from the UI hierarchy - void TabBase::Shutdown() - { - ASSERT_UI_THREAD(); - - // NOTE: `TerminalPage::_HandleCloseTabRequested` relies on the content being null after this call. - Content(nullptr); - } - - // Method Description: - // - Creates a context menu attached to the tab. - // Currently contains elements allowing the user to close the selected tab - // Arguments: - // - - // Return Value: - // - - void TabBase::_CreateContextMenu() - { - auto weakThis{ get_weak() }; - - // Build the menu - Controls::MenuFlyout contextMenuFlyout; - // GH#5750 - When the context menu is dismissed with ESC, toss the focus - // back to our control. - contextMenuFlyout.Closed([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->RequestFocusActiveControl.raise(); - } - }); - _AppendMoveMenuItems(contextMenuFlyout); - _AppendCloseMenuItems(contextMenuFlyout); - TabViewItem().ContextFlyout(contextMenuFlyout); - } - - void TabBase::_AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) - { - auto weakThis{ get_weak() }; - - // Move to new window - { - Controls::FontIcon moveTabToNewWindowTabSymbol; - moveTabToNewWindowTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); - moveTabToNewWindowTabSymbol.Glyph(L"\xE8A7"); - - _moveToNewWindowMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - MoveTabArgs args{ L"new", MoveTabDirection::Forward }; - ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; - tab->_dispatch.DoAction(*tab, actionAndArgs); - } - }); - _moveToNewWindowMenuItem.Text(RS_(L"MoveTabToNewWindowText")); - _moveToNewWindowMenuItem.Icon(moveTabToNewWindowTabSymbol); - - const auto moveTabToNewWindowToolTip = RS_(L"MoveTabToNewWindowToolTip"); - WUX::Controls::ToolTipService::SetToolTip(_moveToNewWindowMenuItem, box_value(moveTabToNewWindowToolTip)); - Automation::AutomationProperties::SetHelpText(_moveToNewWindowMenuItem, moveTabToNewWindowToolTip); - } - - // Move left - { - _moveLeftMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - MoveTabArgs args{ hstring{}, MoveTabDirection::Backward }; - ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; - tab->_dispatch.DoAction(*tab, actionAndArgs); - } - }); - _moveLeftMenuItem.Text(RS_(L"TabMoveLeft")); - } - - // Move right - { - _moveRightMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - MoveTabArgs args{ hstring{}, MoveTabDirection::Forward }; - ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; - tab->_dispatch.DoAction(*tab, actionAndArgs); - } - }); - _moveRightMenuItem.Text(RS_(L"TabMoveRight")); - } - - // Create a sub-menu for our extended move tab items. - Controls::MenuFlyoutSubItem moveSubMenu; - moveSubMenu.Text(RS_(L"TabMoveSubMenu")); - moveSubMenu.Items().Append(_moveToNewWindowMenuItem); - moveSubMenu.Items().Append(_moveRightMenuItem); - moveSubMenu.Items().Append(_moveLeftMenuItem); - flyout.Items().Append(moveSubMenu); - } - - // Method Description: - // - Append the close menu items to the context menu flyout - // Arguments: - // - flyout - the menu flyout to which the close items must be appended - // Return Value: - // - the sub-item that we use for all the nested "close" entries. This - // enables subclasses to add their own entries to this menu. - winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem TabBase::_AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) - { - auto weakThis{ get_weak() }; - - // Close tabs after - _closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - CloseTabsAfterArgs args{ tab->_TabViewIndex }; - ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args }; - tab->_dispatch.DoAction(*tab, closeTabsAfter); - } - }); - _closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter")); - const auto closeTabsAfterToolTip = RS_(L"TabCloseAfterToolTip"); - - WUX::Controls::ToolTipService::SetToolTip(_closeTabsAfterMenuItem, box_value(closeTabsAfterToolTip)); - Automation::AutomationProperties::SetHelpText(_closeTabsAfterMenuItem, closeTabsAfterToolTip); - - // Close other tabs - _closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - CloseOtherTabsArgs args{ tab->_TabViewIndex }; - ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args }; - tab->_dispatch.DoAction(*tab, closeOtherTabs); - } - }); - _closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther")); - const auto closeOtherTabsToolTip = RS_(L"TabCloseOtherToolTip"); - - WUX::Controls::ToolTipService::SetToolTip(_closeOtherTabsMenuItem, box_value(closeOtherTabsToolTip)); - Automation::AutomationProperties::SetHelpText(_closeOtherTabsMenuItem, closeOtherTabsToolTip); - - // Close - Controls::MenuFlyoutItem closeTabMenuItem; - Controls::FontIcon closeSymbol; - closeSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); - closeSymbol.Glyph(L"\xE711"); - - closeTabMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->CloseRequested.raise(nullptr, nullptr); - } - }); - closeTabMenuItem.Text(RS_(L"TabClose")); - closeTabMenuItem.Icon(closeSymbol); - const auto closeTabToolTip = RS_(L"TabCloseToolTip"); - - WUX::Controls::ToolTipService::SetToolTip(closeTabMenuItem, box_value(closeTabToolTip)); - Automation::AutomationProperties::SetHelpText(closeTabMenuItem, closeTabToolTip); - - // Create a sub-menu for our extended close items. - Controls::MenuFlyoutSubItem closeSubMenu; - closeSubMenu.Text(RS_(L"TabCloseSubMenu")); - closeSubMenu.Items().Append(_closeTabsAfterMenuItem); - closeSubMenu.Items().Append(_closeOtherTabsMenuItem); - flyout.Items().Append(closeSubMenu); - - flyout.Items().Append(closeTabMenuItem); - - return closeSubMenu; - } - - // Method Description: - // - Enable menu items based on tab index and total number of tabs - // Arguments: - // - - // Return Value: - // - - void TabBase::_EnableMenuItems() - { - const auto tabIndex = TabViewIndex(); - const auto numOfTabs = TabViewNumTabs(); - - // enabled if there are other tabs - _closeOtherTabsMenuItem.IsEnabled(numOfTabs > 1); - - // enabled if there are other tabs on the right - _closeTabsAfterMenuItem.IsEnabled(tabIndex < numOfTabs - 1); - - // enabled if not left-most tab - _moveLeftMenuItem.IsEnabled(tabIndex > 0); - - // enabled if not last tab - _moveRightMenuItem.IsEnabled(tabIndex < numOfTabs - 1); - } - - void TabBase::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs) - { - ASSERT_UI_THREAD(); - - TabViewIndex(idx); - TabViewNumTabs(numTabs); - _EnableMenuItems(); - _UpdateSwitchToTabKeyChord(); - } - - void TabBase::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) - { - ASSERT_UI_THREAD(); - - _dispatch = dispatch; - } - - void TabBase::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) - { - ASSERT_UI_THREAD(); - - _actionMap = actionMap; - _UpdateSwitchToTabKeyChord(); - } - - // Method Description: - // - Sets the key chord resulting in switch to the current tab. - // Updates tool tip if required - // Arguments: - // - keyChord - string representation of the key chord that switches to the current tab - // Return Value: - // - - void TabBase::_UpdateSwitchToTabKeyChord() - { - const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex); - const auto keyChord{ _actionMap.GetKeyBindingForAction(id) }; - const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L""; - - if (_keyChord == keyChordText) - { - return; - } - - _keyChord = keyChordText; - _UpdateToolTip(); - } - - // Method Description: - // - Creates a text for the title run in the tool tip by returning tab title - // Arguments: - // - - // Return Value: - // - The value to populate in the title run of the tool tip - winrt::hstring TabBase::_CreateToolTipTitle() - { - return _Title; - } - - // Method Description: - // - Sets tab tool tip to a concatenation of title and key chord - // Arguments: - // - - // Return Value: - // - - void TabBase::_UpdateToolTip() - { - auto titleRun = WUX::Documents::Run(); - titleRun.Text(_CreateToolTipTitle()); - - auto textBlock = WUX::Controls::TextBlock{}; - textBlock.TextWrapping(WUX::TextWrapping::Wrap); - textBlock.TextAlignment(WUX::TextAlignment::Center); - textBlock.Inlines().Append(titleRun); - - if (!_keyChord.empty()) - { - auto keyChordRun = WUX::Documents::Run(); - keyChordRun.Text(_keyChord); - keyChordRun.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic); - textBlock.Inlines().Append(WUX::Documents::LineBreak{}); - textBlock.Inlines().Append(keyChordRun); - } - - WUX::Controls::ToolTip toolTip{}; - toolTip.Content(textBlock); - WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip); - } - - // Method Description: - // - Initializes a TabViewItem for this Tab instance. - // Arguments: - // - - // Return Value: - // - - void TabBase::_MakeTabViewItem() - { - TabViewItem(::winrt::MUX::Controls::TabViewItem{}); - - // GH#3609 If the tab was tapped, and no one else was around to handle - // it, then ask our parent to toss focus into the active control. - TabViewItem().Tapped([weakThis{ get_weak() }](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->RequestFocusActiveControl.raise(); - } - }); - - // BODGY: When the tab is drag/dropped, the TabView gets a - // TabDragStarting. However, the way it is implemented[^1], the - // TabViewItem needs either an Item or a Content for the event to - // include the correct TabViewItem. Otherwise, it will just return the - // first TabViewItem in the TabView with the same Content as the dragged - // tab (which, if the Content is null, will be the _first_ tab). - // - // So here, we'll stick an empty border in, just so that every tab has a - // Content which is not equal to the others. - // - // [^1]: microsoft-ui-xaml/blob/92fbfcd55f05c92ac65569f5d284c5b36492091e/dev/TabView/TabView.cpp#L751-L758 - TabViewItem().Content(winrt::WUX::Controls::Border{}); - } - - std::optional TabBase::GetTabColor() - { - ASSERT_UI_THREAD(); - - return std::nullopt; - } - - void TabBase::ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, - const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, - const til::color& tabRowColor) - { - ASSERT_UI_THREAD(); - - _themeColor = focused; - _unfocusedThemeColor = unfocused; - _tabRowColor = tabRowColor; - _RecalculateAndApplyTabColor(); - } - - // Method Description: - // - This function dispatches a function to the UI thread to recalculate - // what this tab's current background color should be. If a color is set, - // it will apply the given color to the tab's background. Otherwise, it - // will clear the tab's background color. - // Arguments: - // - - // Return Value: - // - - void TabBase::_RecalculateAndApplyTabColor() - { - // GetTabColor will return the color set by the color picker, or the - // color specified in the profile. If neither of those were set, - // then look to _themeColor to see if there's a value there. - // Otherwise, clear our color, falling back to the TabView defaults. - const auto currentColor = GetTabColor(); - if (currentColor.has_value()) - { - _ApplyTabColorOnUIThread(currentColor.value()); - } - else if (_themeColor != nullptr) - { - // Safely get the active control's brush. - const Media::Brush terminalBrush{ _BackgroundBrush() }; - - if (const auto themeBrush{ _themeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) - { - // ThemeColor.Evaluate will get us a Brush (because the - // TermControl could have an acrylic BG, for example). Take - // that brush, and get the color out of it. We don't really - // want to have the tab items themselves be acrylic. - _ApplyTabColorOnUIThread(til::color{ ThemeColor::ColorFromBrush(themeBrush) }); - } - else - { - _ClearTabBackgroundColor(); - } - } - else - { - _ClearTabBackgroundColor(); - } - } - - // Method Description: - // - Applies the given color to the background of this tab's TabViewItem. - // - Sets the tab foreground color depending on the luminance of - // the background color - // - This method should only be called on the UI thread. - // Arguments: - // - color: the color the user picked for their tab - // Return Value: - // - - void TabBase::_ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color) - { - Media::SolidColorBrush selectedTabBrush{}; - Media::SolidColorBrush deselectedTabBrush{}; - Media::SolidColorBrush fontBrush{}; - Media::SolidColorBrush deselectedFontBrush{}; - Media::SolidColorBrush secondaryFontBrush{}; - Media::SolidColorBrush hoverTabBrush{}; - Media::SolidColorBrush subtleFillColorSecondaryBrush; - Media::SolidColorBrush subtleFillColorTertiaryBrush; - - // calculate the luminance of the current color and select a font - // color based on that - // see https://www.w3.org/TR/WCAG20/#relativeluminancedef - if (TerminalApp::ColorHelper::IsBrightColor(color)) - { - auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black(); - subtleFillColorSecondary.A = 0x09; - subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); - auto subtleFillColorTertiary = winrt::Windows::UI::Colors::Black(); - subtleFillColorTertiary.A = 0x06; - subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); - } - else - { - auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White(); - subtleFillColorSecondary.A = 0x0F; - subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); - auto subtleFillColorTertiary = winrt::Windows::UI::Colors::White(); - subtleFillColorTertiary.A = 0x0A; - subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); - } - - // The tab font should be based on the evaluated appearance of the tab color layered on tab row. - const auto layeredTabColor = til::color{ color }.layer_over(_tabRowColor); - if (TerminalApp::ColorHelper::IsBrightColor(layeredTabColor)) - { - fontBrush.Color(winrt::Windows::UI::Colors::Black()); - auto secondaryFontColor = winrt::Windows::UI::Colors::Black(); - // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269 - secondaryFontColor.A = 0x9E; - secondaryFontBrush.Color(secondaryFontColor); - } - else - { - fontBrush.Color(winrt::Windows::UI::Colors::White()); - auto secondaryFontColor = winrt::Windows::UI::Colors::White(); - // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14 - secondaryFontColor.A = 0xC5; - secondaryFontBrush.Color(secondaryFontColor); - } - - selectedTabBrush.Color(color); - - // Start with the current tab color, set to Opacity=.3 - til::color deselectedTabColor{ color }; - deselectedTabColor = deselectedTabColor.with_alpha(77); // 255 * .3 = 77 - - // If we DON'T have a color set from the color picker, or the profile's - // tabColor, but we do have a unfocused color in the theme, use the - // unfocused theme color here instead. - if (!GetTabColor().has_value() && - _unfocusedThemeColor != nullptr) - { - // Safely get the active control's brush. - const Media::Brush terminalBrush{ _BackgroundBrush() }; - - // Get the color of the brush. - if (const auto themeBrush{ _unfocusedThemeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) - { - // We did figure out the brush. Get the color out of it. If it - // was "accent" or "terminalBackground", then we're gonna set - // the alpha to .3 manually here. - // (ThemeColor::UnfocusedTabOpacity will do this for us). If the - // user sets both unfocused and focused tab.background to - // terminalBackground, this will allow for some differentiation - // (and is generally just sensible). - deselectedTabColor = til::color{ ThemeColor::ColorFromBrush(themeBrush) }.with_alpha(_unfocusedThemeColor.UnfocusedTabOpacity()); - } - } - - // currently if a tab has a custom color, a deselected state is - // signified by using the same color with a bit of transparency - deselectedTabBrush.Color(deselectedTabColor.with_alpha(255)); - deselectedTabBrush.Opacity(deselectedTabColor.a / 255.f); - - hoverTabBrush.Color(color); - hoverTabBrush.Opacity(0.6); - - // Account for the color of the tab row when setting the color of text - // on inactive tabs. Consider: - // * black active tabs - // * on a white tab row - // * with a transparent inactive tab color - // - // We don't want that to result in white text on a white tab row for - // inactive tabs. - const auto deselectedActualColor = deselectedTabColor.layer_over(_tabRowColor); - if (TerminalApp::ColorHelper::IsBrightColor(deselectedActualColor)) - { - deselectedFontBrush.Color(winrt::Windows::UI::Colors::Black()); - } - else - { - deselectedFontBrush.Color(winrt::Windows::UI::Colors::White()); - } - - // Add the empty theme dictionaries - const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; - ResourceDictionary lightThemeDictionary; - ResourceDictionary darkThemeDictionary; - ResourceDictionary highContrastThemeDictionary; - tabItemThemeResources.Insert(winrt::box_value(L"Light"), lightThemeDictionary); - tabItemThemeResources.Insert(winrt::box_value(L"Dark"), darkThemeDictionary); - tabItemThemeResources.Insert(winrt::box_value(L"HighContrast"), highContrastThemeDictionary); - - // Apply the color to the tab - TabViewItem().Background(deselectedTabBrush); - - // Now actually set the resources we want in them. - // Before, we used to put these on the ResourceDictionary directly. - // However, HighContrast mode may require some adjustments. So let's just add - // all three so we can make those adjustments on the HighContrast version. - for (const auto& [k, v] : tabItemThemeResources) - { - const bool isHighContrast = winrt::unbox_value(k) == L"HighContrast"; - const auto& currentDictionary = v.as(); - - // TabViewItem.Background - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), isHighContrast ? fontBrush : hoverTabBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); - - // TabViewItem.Foreground (aka text) - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForeground"), deselectedFontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), isHighContrast ? selectedTabBrush : fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); - - // TabViewItem.CloseButton.Foreground (aka X) - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForeground"), deselectedFontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPressed"), isHighContrast ? deselectedFontBrush : secondaryFontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPointerOver"), isHighContrast ? deselectedFontBrush : fontBrush); - - // TabViewItem.CloseButton.Foreground _when_ interacting with the tab - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPressedCloseButtonForeground"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPointerOverCloseButtonForeground"), isHighContrast ? selectedTabBrush : fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderSelectedCloseButtonForeground"), fontBrush); - - // TabViewItem.CloseButton.Background (aka X button) - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPressed"), isHighContrast ? selectedTabBrush : subtleFillColorTertiaryBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPointerOver"), isHighContrast ? selectedTabBrush : subtleFillColorSecondaryBrush); - - // A few miscellaneous resources that WinUI said may be removed in the future - currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPressed"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPointerOver"), fontBrush); - - // Add a few extra ones for high contrast mode - // BODGY: contrary to the docs, Insert() seems to throw if the value already exists - // Make sure you don't touch any that already exist here! - if (isHighContrast) - { - // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPressed"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPointerOver"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushSelected"), fontBrush); - } - } - - _RefreshVisualState(); - } - - // Method Description: - // - Clear out any color we've set for the TabViewItem. - // - This method should only be called on the UI thread. - // Arguments: - // - - // Return Value: - // - - void TabBase::_ClearTabBackgroundColor() - { - static const winrt::hstring keys[] = { - // TabViewItem.Background - L"TabViewItemHeaderBackground", - L"TabViewItemHeaderBackgroundSelected", - L"TabViewItemHeaderBackgroundPointerOver", - L"TabViewItemHeaderBackgroundPressed", - - // TabViewItem.Foreground (aka text) - L"TabViewItemHeaderForeground", - L"TabViewItemHeaderForegroundSelected", - L"TabViewItemHeaderForegroundPointerOver", - L"TabViewItemHeaderForegroundPressed", - - // TabViewItem.CloseButton.Foreground (aka X) - L"TabViewItemHeaderCloseButtonForeground", - L"TabViewItemHeaderForegroundSelected", - L"TabViewItemHeaderCloseButtonForegroundPointerOver", - L"TabViewItemHeaderCloseButtonForegroundPressed", - - // TabViewItem.CloseButton.Foreground _when_ interacting with the tab - L"TabViewItemHeaderPressedCloseButtonForeground", - L"TabViewItemHeaderPointerOverCloseButtonForeground", - L"TabViewItemHeaderSelectedCloseButtonForeground", - - // TabViewItem.CloseButton.Background (aka X button) - L"TabViewItemHeaderCloseButtonBackground", - L"TabViewItemHeaderCloseButtonBackgroundPressed", - L"TabViewItemHeaderCloseButtonBackgroundPointerOver", - - // A few miscellaneous resources that WinUI said may be removed in the future - L"TabViewButtonForegroundActiveTab", - L"TabViewButtonForegroundPressed", - L"TabViewButtonForegroundPointerOver", - - // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible - L"TabViewItemHeaderCloseButtonBorderBrushPressed", - L"TabViewItemHeaderCloseButtonBorderBrushPointerOver", - L"TabViewItemHeaderCloseButtonBorderBrushSelected" - }; - - const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; - - // simply clear any of the colors in the tab's dict - for (const auto& keyString : keys) - { - const auto key = winrt::box_value(keyString); - for (const auto& [_, v] : tabItemThemeResources) - { - const auto& themeDictionary = v.as(); - themeDictionary.Remove(key); - } - } - - // GH#11382 DON'T set the background to null. If you do that, then the - // tab won't be hit testable at all. Transparent, however, is a totally - // valid hit test target. That makes sense. - TabViewItem().Background(WUX::Media::SolidColorBrush{ Windows::UI::Colors::Transparent() }); - - _RefreshVisualState(); - } - - // Method Description: - // BODGY - // - Toggles the requested theme of the tab view item, - // so that changes to the tab color are reflected immediately - // - Prior to MUX 2.8, we only toggled the visual state here, but that seemingly - // doesn't work in 2.8. - // - Just changing the Theme also doesn't seem to work by itself - there - // seems to be a way for the tab to set the deselected foreground onto - // itself as it becomes selected. If the mouse isn't over the tab, that - // can result in mismatched fg/bg's (see GH#15184). So that's right, we - // need to do both. - // Arguments: - // - - // Return Value: - // - - void TabBase::_RefreshVisualState() - { - const auto& item{ TabViewItem() }; - - const auto& reqTheme = TabViewItem().RequestedTheme(); - item.RequestedTheme(ElementTheme::Light); - item.RequestedTheme(ElementTheme::Dark); - item.RequestedTheme(reqTheme); - - if (TabViewItem().IsSelected()) - { - VisualStateManager::GoToState(item, L"Normal", true); - VisualStateManager::GoToState(item, L"Selected", true); - } - else - { - VisualStateManager::GoToState(item, L"Selected", true); - VisualStateManager::GoToState(item, L"Normal", true); - } - } - - TabCloseButtonVisibility TabBase::CloseButtonVisibility() - { - return _closeButtonVisibility; - } - - // Method Description: - // - set our internal state to track if we were requested to have a visible - // tab close button or not. - // - This is called every time the active tab changes. That way, the changes - // in focused tab can be reflected for the "ActiveOnly" state. - void TabBase::CloseButtonVisibility(TabCloseButtonVisibility visibility) - { - _closeButtonVisibility = visibility; - _updateIsClosable(); - } - - // Method Description: - // - Update our close button's visibility, to reflect both the ReadOnly - // state of the tab content, and also if if we were told to have a visible - // close button at all. - // - the tab being read-only takes precedence. That will always suppress - // the close button. - // - Otherwise we'll use the state set in CloseButtonVisibility to control - // the tab's visibility. - void TabBase::_updateIsClosable() - { - bool isClosable = true; - - if (ReadOnly()) - { - isClosable = false; - } - else - { - switch (_closeButtonVisibility) - { - case TabCloseButtonVisibility::Never: - isClosable = false; - break; - case TabCloseButtonVisibility::Hover: - isClosable = true; - break; - case TabCloseButtonVisibility::ActiveOnly: - isClosable = _focused(); - break; - default: - isClosable = true; - break; - } - } - TabViewItem().IsClosable(isClosable); - } - - bool TabBase::_focused() const noexcept - { - return _focusState != FocusState::Unfocused; - } - -} diff --git a/src/cascadia/TerminalApp/TabBase.h b/src/cascadia/TerminalApp/TabBase.h deleted file mode 100644 index aa4928c669..0000000000 --- a/src/cascadia/TerminalApp/TabBase.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once -#include "TabBase.g.h" - -// fwdecl unittest classes -namespace TerminalAppLocalTests -{ - class TabTests; -}; - -namespace winrt::TerminalApp::implementation -{ - struct TabBase : TabBaseT - { - public: - virtual void Focus(winrt::Windows::UI::Xaml::FocusState focusState) = 0; - - virtual void Shutdown(); - void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); - - void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs); - void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); - virtual std::vector BuildStartupActions(BuildStartupKind kind) const = 0; - - virtual std::optional GetTabColor(); - void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, - const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, - const til::color& tabRowColor); - - Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility CloseButtonVisibility(); - void CloseButtonVisibility(Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility visible); - - til::event> RequestFocusActiveControl; - - til::event> Closed; - til::event> CloseRequested; - til::property_changed_event PropertyChanged; - - // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. - WINRT_PROPERTY(uint32_t, TabViewIndex, 0); - // The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector. - WINRT_PROPERTY(uint32_t, TabViewNumTabs, 0); - - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Title, PropertyChanged.raise); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Icon, PropertyChanged.raise); - WINRT_OBSERVABLE_PROPERTY(bool, ReadOnly, PropertyChanged.raise, false); - WINRT_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr); - - WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::FrameworkElement, Content, PropertyChanged.raise, nullptr); - - protected: - winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused }; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveToNewWindowMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveRightMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveLeftMenuItem{}; - winrt::TerminalApp::ShortcutActionDispatch _dispatch; - Microsoft::Terminal::Settings::Model::IActionMapView _actionMap{ nullptr }; - winrt::hstring _keyChord{}; - - winrt::Microsoft::Terminal::Settings::Model::ThemeColor _themeColor{ nullptr }; - winrt::Microsoft::Terminal::Settings::Model::ThemeColor _unfocusedThemeColor{ nullptr }; - til::color _tabRowColor; - - Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility _closeButtonVisibility{ Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always }; - - virtual void _CreateContextMenu(); - virtual winrt::hstring _CreateToolTipTitle(); - - virtual void _MakeTabViewItem(); - - void _AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); - winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); - void _EnableMenuItems(); - void _UpdateSwitchToTabKeyChord(); - void _UpdateToolTip(); - - void _RecalculateAndApplyTabColor(); - void _ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color); - void _ClearTabBackgroundColor(); - void _RefreshVisualState(); - virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() = 0; - - bool _focused() const noexcept; - void _updateIsClosable(); - - friend class ::TerminalAppLocalTests::TabTests; - }; -} diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 3b2691df93..b7a3e29e5a 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -99,7 +99,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - newTabImpl: the uninitialized tab. // - insertPosition: Optional parameter to indicate the position of tab. - void TerminalPage::_InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition) + void TerminalPage::_InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition) { newTabImpl->Initialize(); @@ -206,11 +206,11 @@ namespace winrt::TerminalApp::implementation // Arguments: // - pane: The pane to use as the root. // - insertPosition: Optional parameter to indicate the position of tab. - TerminalApp::TerminalTab TerminalPage::_CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition) + TerminalApp::Tab TerminalPage::_CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition) { if (pane) { - auto newTabImpl = winrt::make_self(pane); + auto newTabImpl = winrt::make_self(pane); _InitializeTab(newTabImpl, insertPosition); return *newTabImpl; } @@ -222,7 +222,7 @@ namespace winrt::TerminalApp::implementation // tab's icon to that icon. // Arguments: // - tab: the Tab to update the title for. - void TerminalPage::_UpdateTabIcon(TerminalTab& tab) + void TerminalPage::_UpdateTabIcon(Tab& tab) { if (const auto content{ tab.GetActiveContent() }) { @@ -272,9 +272,9 @@ namespace winrt::TerminalApp::implementation // - Duplicates the current focused tab void TerminalPage::_DuplicateFocusedTab() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto activeTab{ _GetFocusedTabImpl() }) { - _DuplicateTab(*terminalTab); + _DuplicateTab(*activeTab); } } @@ -282,7 +282,7 @@ namespace winrt::TerminalApp::implementation // - Duplicates specified tab // Arguments: // - tab: tab to duplicate - void TerminalPage::_DuplicateTab(const TerminalTab& tab) + void TerminalPage::_DuplicateTab(const Tab& tab) { try { @@ -315,7 +315,7 @@ namespace winrt::TerminalApp::implementation // - Exports the content of the Terminal Buffer inside the tab // Arguments: // - tab: tab to export - safe_void_coroutine TerminalPage::_ExportTab(const TerminalTab& tab, winrt::hstring filepath) + safe_void_coroutine TerminalPage::_ExportTab(const Tab& tab, winrt::hstring filepath) { // This will be used to set up the file picker "filter", to select .txt // files by default. @@ -401,7 +401,7 @@ namespace winrt::TerminalApp::implementation // - Removes the tab (both TerminalControl and XAML) after prompting for approval // Arguments: // - tab: the tab to remove - winrt::Windows::Foundation::IAsyncAction TerminalPage::_HandleCloseTabRequested(winrt::TerminalApp::TabBase tab) + winrt::Windows::Foundation::IAsyncAction TerminalPage::_HandleCloseTabRequested(winrt::TerminalApp::Tab tab) { if (tab.ReadOnly()) { @@ -414,7 +414,7 @@ namespace winrt::TerminalApp::implementation } } - auto t = winrt::get_self(tab); + auto t = winrt::get_self(tab); auto actions = t->BuildStartupActions(BuildStartupKind::None); _AddPreviouslyClosedPaneOrTab(std::move(actions)); @@ -425,7 +425,7 @@ namespace winrt::TerminalApp::implementation // - Removes the tab (both TerminalControl and XAML) // Arguments: // - tab: the tab to remove - void TerminalPage::_RemoveTab(const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_RemoveTab(const winrt::TerminalApp::Tab& tab) { uint32_t tabIndex{}; if (!_tabs.IndexOf(tab, tabIndex)) @@ -597,7 +597,7 @@ namespace winrt::TerminalApp::implementation // - tab - tab to select // Return Value: // - - void TerminalPage::_OnSwitchToTabRequested(const IInspectable& /*sender*/, const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_OnSwitchToTabRequested(const IInspectable& /*sender*/, const winrt::TerminalApp::Tab& tab) { uint32_t index{}; if (_tabs.IndexOf(tab, index)) @@ -628,7 +628,7 @@ namespace winrt::TerminalApp::implementation // no tab is currently selected, returns nullopt. // Return Value: // - the index of the currently focused tab if there is one, else nullopt - std::optional TerminalPage::_GetTabIndex(const TerminalApp::TabBase& tab) const noexcept + std::optional TerminalPage::_GetTabIndex(const TerminalApp::Tab& tab) const noexcept { uint32_t i; if (_tabs.IndexOf(tab, i)) @@ -641,7 +641,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - returns the currently focused tab. This might return null, // so make sure to check the result! - winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab() const noexcept + winrt::TerminalApp::Tab TerminalPage::_GetFocusedTab() const noexcept { if (auto index{ _GetFocusedTabIndex() }) { @@ -653,11 +653,11 @@ namespace winrt::TerminalApp::implementation // Method Description: // - returns a com_ptr to the currently focused tab implementation. This might return null, // so make sure to check the result! - winrt::com_ptr TerminalPage::_GetFocusedTabImpl() const noexcept + winrt::com_ptr TerminalPage::_GetFocusedTabImpl() const noexcept { if (auto tab{ _GetFocusedTab() }) { - return _GetTerminalTabImpl(tab); + return _GetTabImpl(tab); } return nullptr; } @@ -665,7 +665,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - returns a tab corresponding to a view item. This might return null, // so make sure to check the result! - winrt::TerminalApp::TabBase TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept + winrt::TerminalApp::Tab TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept { uint32_t tabIndexFromControl{}; const auto items{ _tabView.TabItems() }; @@ -687,7 +687,7 @@ namespace winrt::TerminalApp::implementation // - tab: tab to focus. // Return Value: // - - safe_void_coroutine TerminalPage::_SetFocusedTab(const winrt::TerminalApp::TabBase tab) + safe_void_coroutine TerminalPage::_SetFocusedTab(const winrt::TerminalApp::Tab tab) { // GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex) // sometimes set focus to an incorrect tab after removing some tabs @@ -774,11 +774,11 @@ namespace winrt::TerminalApp::implementation // tab's Closed event. safe_void_coroutine TerminalPage::_CloseFocusedPane() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto activeTab{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - if (const auto pane{ terminalTab->GetActivePane() }) + if (const auto pane{ activeTab->GetActivePane() }) { if (co_await _PaneConfirmCloseReadOnly(pane)) { @@ -793,7 +793,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - weakTab: weak reference to the tab that the pane belongs to. // - paneIds: collection of the IDs of the panes that are marked for removal. - void TerminalPage::_ClosePanes(weak_ref weakTab, std::vector paneIds) + void TerminalPage::_ClosePanes(weak_ref weakTab, std::vector paneIds) { if (auto strongTab{ weakTab.get() }) { @@ -838,7 +838,7 @@ namespace winrt::TerminalApp::implementation // - Closes provided tabs one by one // Arguments: // - tabs - tabs to remove - safe_void_coroutine TerminalPage::_RemoveTabs(const std::vector tabs) + safe_void_coroutine TerminalPage::_RemoveTabs(const std::vector tabs) { for (auto& tab : tabs) { @@ -926,7 +926,7 @@ namespace winrt::TerminalApp::implementation } // WinUI asynchronously updates its tab view items, so it may happen that we're given a - // `TabViewItem` that still contains a `TabBase` which has actually already been removed. + // `TabViewItem` that still contains a `Tab` which has actually already been removed. // First we must yield once, to flush out whatever TabView is currently doing. const auto strong = get_strong(); co_await wil::resume_foreground(Dispatcher()); @@ -966,7 +966,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab) { // Unfocus all the tabs. for (const auto& tab : _tabs) @@ -1007,10 +1007,10 @@ namespace winrt::TerminalApp::implementation _updateThemeColors(); - auto tab_impl = _GetTerminalTabImpl(tab); - if (tab_impl) + auto tabImpl = _GetTabImpl(tab); + if (tabImpl) { - auto profile = tab_impl->GetFocusedProfile(); + auto profile = tabImpl->GetFocusedProfile(); _UpdateBackground(profile); } } @@ -1057,7 +1057,7 @@ namespace winrt::TerminalApp::implementation for (uint32_t i = 0; i < size; ++i) { auto tab{ _tabs.GetAt(i) }; - auto tabImpl{ winrt::get_self(tab) }; + auto tabImpl{ winrt::get_self(tab) }; tabImpl->UpdateTabViewIndex(i, size); } } @@ -1068,7 +1068,7 @@ namespace winrt::TerminalApp::implementation // - tab: tab to bump. // Return Value: // - - void TerminalPage::_UpdateMRUTab(const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_UpdateMRUTab(const winrt::TerminalApp::Tab& tab) { uint32_t mruIndex; if (_mruTabs.IndexOf(tab, mruIndex)) diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 5012430884..7e5028167b 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -99,15 +99,12 @@ Code - - TabBase.idl + + Tab.idl TaskbarState.idl - - TerminalTab.idl - TerminalPage.xaml Code @@ -206,16 +203,13 @@ PaletteItemTemplateSelector.idl Code - - TabBase.idl + + Tab.idl TaskbarState.idl - - TerminalTab.idl - TerminalPage.xaml Code @@ -333,9 +327,8 @@ MinMaxCloseControl.xaml Code - + - TerminalPage.xaml Code diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 52b5113bdb..68c7f8c7a3 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -81,9 +81,6 @@ settings - - tab - commandPalette @@ -91,7 +88,7 @@ tab - + diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 87ccb19a32..ce8e8762e4 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -59,8 +59,8 @@ namespace winrt namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage(TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) : - _tabs{ winrt::single_threaded_observable_vector() }, - _mruTabs{ winrt::single_threaded_observable_vector() }, + _tabs{ winrt::single_threaded_observable_vector() }, + _mruTabs{ winrt::single_threaded_observable_vector() }, _manager{ manager }, _hostingHwnd{}, _WindowProperties{ std::move(properties) } @@ -84,9 +84,9 @@ namespace winrt::TerminalApp::implementation // GH#13211 - if we haven't yet set the owning hwnd, reparent all the controls now. for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { - terminalTab->GetRootPane()->WalkTree([&](auto&& pane) { + tabImpl->GetRootPane()->WalkTree([&](auto&& pane) { if (const auto& term{ pane->GetTerminalControl() }) { term.OwningHwnd(reinterpret_cast(hwnd)); @@ -566,9 +566,9 @@ namespace winrt::TerminalApp::implementation // GH#6586: now that we're done processing all startup commands, // focus the active control. This will work as expected for both // commandline invocations and for `wt` action invocations. - if (const auto& terminalTab{ _GetFocusedTabImpl() }) + if (const auto& tabImpl{ _GetFocusedTabImpl() }) { - if (const auto& content{ terminalTab->GetActiveContent() }) + if (const auto& content{ tabImpl->GetActiveContent() }) { content.Focus(FocusState::Programmatic); } @@ -1755,7 +1755,7 @@ namespace winrt::TerminalApp::implementation // TitleChanged event. // Arguments: // - tab: the Tab to update the title for. - void TerminalPage::_UpdateTitle(const TerminalTab& tab) + void TerminalPage::_UpdateTitle(const Tab& tab) { auto newTabTitle = tab.Title(); @@ -1833,13 +1833,13 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Connects event handlers to the TerminalTab for events that we want to + // - Connects event handlers to the Tab for events that we want to // handle. This includes: // * the TitleChanged event, for changing the text of the tab // * the Color{Selected,Cleared} events to change the color of a tab. // Arguments: // - hostingTab: The Tab that's hosting this TermControl instance - void TerminalPage::_RegisterTabEvents(TerminalTab& hostingTab) + void TerminalPage::_RegisterTabEvents(Tab& hostingTab) { auto weakTab{ hostingTab.get_weak() }; auto weakThis{ get_weak() }; @@ -1922,9 +1922,9 @@ namespace winrt::TerminalApp::implementation // to the terminal when no other panes are present (GH#6219) bool TerminalPage::_MoveFocus(const FocusDirection& direction) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - return terminalTab->NavigateFocus(direction); + return tabImpl->NavigateFocus(direction); } return false; } @@ -1938,19 +1938,19 @@ namespace winrt::TerminalApp::implementation // - true if panes were swapped. bool TerminalPage::_SwapPane(const FocusDirection& direction) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - return terminalTab->SwapPane(direction); + return tabImpl->SwapPane(direction); } return false; } TermControl TerminalPage::_GetActiveControl() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - return terminalTab->GetActiveTerminalControl(); + return tabImpl->GetActiveTerminalControl(); } return nullptr; } @@ -2057,7 +2057,7 @@ namespace winrt::TerminalApp::implementation for (auto tab : _tabs) { - auto t = winrt::get_self(tab); + auto t = winrt::get_self(tab); auto tabActions = t->BuildStartupActions(serializeBuffer ? BuildStartupKind::PersistAll : BuildStartupKind::PersistLayout); actions.insert(actions.end(), std::make_move_iterator(tabActions.begin()), std::make_move_iterator(tabActions.end())); } @@ -2152,14 +2152,14 @@ namespace winrt::TerminalApp::implementation // - rowsToScroll: a number of lines to move the viewport. If not provided we will use a system default. void TerminalPage::_Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { uint32_t realRowsToScroll; if (rowsToScroll == nullptr) { // The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ? - terminalTab->GetActiveTerminalControl().ViewHeight() : + tabImpl->GetActiveTerminalControl().ViewHeight() : _systemRowsToScroll; } else @@ -2168,7 +2168,7 @@ namespace winrt::TerminalApp::implementation realRowsToScroll = rowsToScroll.Value(); } auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll); - terminalTab->Scroll(scrollDelta); + tabImpl->Scroll(scrollDelta); } } @@ -2199,9 +2199,9 @@ namespace winrt::TerminalApp::implementation // specified window instead of moving it in our tab row. if (!windowId.empty()) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - if (const auto pane{ terminalTab->GetActivePane() }) + if (const auto pane{ tabImpl->GetActivePane() }) { auto startupActions = pane->BuildStartupActions(0, 1, BuildStartupKind::MovePane); _DetachPaneFromWindow(pane); @@ -2240,7 +2240,7 @@ namespace winrt::TerminalApp::implementation // tab before its index changes. if (tabIdx < _tabs.Size()) { - auto targetTab = _GetTerminalTabImpl(_tabs.GetAt(tabIdx)); + auto targetTab = _GetTabImpl(_tabs.GetAt(tabIdx)); // if the selected tab is not a host of terminals (e.g. settings) // don't attempt to add a pane to it. if (!targetTab) @@ -2288,15 +2288,12 @@ namespace winrt::TerminalApp::implementation }); } - void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr& tab) + void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr& tab) { - if (const auto terminalTab = tab.try_as()) + // Detach the root pane, which will act like the whole tab got detached. + if (const auto rootPane = tab->GetRootPane()) { - // Detach the root pane, which will act like the whole tab got detached. - if (const auto rootPane = terminalTab->GetRootPane()) - { - _DetachPaneFromWindow(rootPane); - } + _DetachPaneFromWindow(rootPane); } } @@ -2324,7 +2321,7 @@ namespace winrt::TerminalApp::implementation RequestMoveContent.raise(*this, *request); } - bool TerminalPage::_MoveTab(winrt::com_ptr tab, MoveTabArgs args) + bool TerminalPage::_MoveTab(winrt::com_ptr tab, MoveTabArgs args) { if (!tab) { @@ -2392,10 +2389,10 @@ namespace winrt::TerminalApp::implementation // When the tab's active pane changes, we'll want to lookup a new icon // for it. The Title change will be propagated upwards through the tab's // PropertyChanged event handler. - void TerminalPage::_activePaneChanged(winrt::TerminalApp::TerminalTab sender, + void TerminalPage::_activePaneChanged(winrt::TerminalApp::Tab sender, Windows::Foundation::IInspectable /*args*/) { - if (const auto tab{ _GetTerminalTabImpl(sender) }) + if (const auto tab{ _GetTabImpl(sender) }) { // Possibly update the icon of the tab. _UpdateTabIcon(*tab); @@ -2484,7 +2481,7 @@ namespace winrt::TerminalApp::implementation // - splitDirection: one value from the TerminalApp::SplitDirection enum, indicating how the // new pane should be split from its parent. // - splitSize: the size of the split - void TerminalPage::_SplitPane(const winrt::com_ptr& tab, + void TerminalPage::_SplitPane(const winrt::com_ptr& tab, const SplitDirection splitDirection, const float splitSize, std::shared_ptr newPane) @@ -2565,10 +2562,10 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_ToggleSplitOrientation() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - terminalTab->ToggleSplitOrientation(); + tabImpl->ToggleSplitOrientation(); } } @@ -2582,10 +2579,10 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_ResizePane(const ResizeDirection& direction) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - terminalTab->ResizePane(direction); + tabImpl->ResizePane(direction); } } @@ -2597,23 +2594,23 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_ScrollPage(ScrollDirection scrollDirection) { // Do nothing if for some reason, there's no terminal tab in focus. We don't want to crash. - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { if (const auto& control{ _GetActiveControl() }) { const auto termHeight = control.ViewHeight(); auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight); - terminalTab->Scroll(scrollDelta); + tabImpl->Scroll(scrollDelta); } } } void TerminalPage::_ScrollToBufferEdge(ScrollDirection scrollDirection) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { auto scrollDelta = _ComputeScrollDelta(scrollDirection, INT_MAX); - terminalTab->Scroll(scrollDelta); + tabImpl->Scroll(scrollDelta); } } @@ -2725,9 +2722,9 @@ namespace winrt::TerminalApp::implementation { if (_settings && _settings.GlobalSettings().SnapToGridOnResize()) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - return terminalTab->CalcSnappedDimension(widthOrHeight, dimension); + return tabImpl->CalcSnappedDimension(widthOrHeight, dimension); } } return dimension; @@ -3343,7 +3340,7 @@ namespace winrt::TerminalApp::implementation // connection, then we'll return nullptr. Otherwise, we'll return a new // Pane for this connection. std::shared_ptr TerminalPage::_MakeTerminalPane(const NewTerminalArgs& newTerminalArgs, - const winrt::TerminalApp::TabBase& sourceTab, + const winrt::TerminalApp::Tab& sourceTab, TerminalConnection::ITerminalConnection existingConnection) { // First things first - Check for making a pane from content ID. @@ -3361,15 +3358,15 @@ namespace winrt::TerminalApp::implementation TerminalSettingsCreateResult controlSettings{ nullptr }; Profile profile{ nullptr }; - if (const auto& terminalTab{ _GetTerminalTabImpl(sourceTab) }) + if (const auto& tabImpl{ _GetTabImpl(sourceTab) }) { - profile = terminalTab->GetFocusedProfile(); + profile = tabImpl->GetFocusedProfile(); if (profile) { // TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this. profile = GetClosestProfileForDuplicationOfProfile(profile); controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings); - const auto workingDirectory = terminalTab->GetActiveTerminalControl().WorkingDirectory(); + const auto workingDirectory = tabImpl->GetActiveTerminalControl().WorkingDirectory(); const auto validWorkingDirectory = !workingDirectory.empty(); if (validWorkingDirectory) { @@ -3433,7 +3430,7 @@ namespace winrt::TerminalApp::implementation auto debugContent{ winrt::make(profile, _terminalSettingsCache, newControl) }; auto debugPane = std::make_shared(debugContent); - // Since we're doing this split directly on the pane (instead of going through TerminalTab, + // Since we're doing this split directly on the pane (instead of going through Tab, // we need to handle the panes 'active' states // Set the pane we're splitting to active (otherwise Split will not do anything) @@ -3451,7 +3448,7 @@ namespace winrt::TerminalApp::implementation // NOTE: callers of _MakePane should be able to accept nullptr as a return // value gracefully. std::shared_ptr TerminalPage::_MakePane(const INewContentArgs& contentArgs, - const winrt::TerminalApp::TabBase& sourceTab, + const winrt::TerminalApp::Tab& sourceTab, TerminalConnection::ITerminalConnection existingConnection) { @@ -3485,9 +3482,9 @@ namespace winrt::TerminalApp::implementation // Prevent the user from opening a bunch of snippets panes. // // Look at the focused tab, and if it already has one, then just focus it. - if (const auto& focusedTab{ _GetFocusedTab() }) + if (const auto& focusedTab{ _GetFocusedTabImpl() }) { - const auto rootPane{ focusedTab.try_as()->GetRootPane() }; + const auto rootPane{ focusedTab->GetRootPane() }; const bool found = rootPane == nullptr ? false : rootPane->WalkTree([](const auto& p) -> bool { if (const auto& snippets{ p->GetContent().try_as() }) { @@ -3643,21 +3640,21 @@ namespace winrt::TerminalApp::implementation for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { // Let the tab know that there are new settings. It's up to each content to decide what to do with them. - terminalTab->UpdateSettings(_settings); + tabImpl->UpdateSettings(_settings); // Update the icon of the tab for the currently focused profile in that tab. // Only do this for TerminalTabs. Other types of tabs won't have multiple panes // and profiles so the Title and Icon will be set once and only once on init. - _UpdateTabIcon(*terminalTab); + _UpdateTabIcon(*tabImpl); // Force the TerminalTab to re-grab its currently active control's title. - terminalTab->UpdateTitle(); + tabImpl->UpdateTitle(); } - auto tabImpl{ winrt::get_self(tab) }; + auto tabImpl{ winrt::get_self(tab) }; tabImpl->SetActionMap(_settings.ActionMap()); } @@ -3776,7 +3773,7 @@ namespace winrt::TerminalApp::implementation for (const auto& tab : _tabs) { - if (auto tabImpl{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { auto tabState{ tabImpl->GetCombinedTaskbarState() }; // lowest priority wins @@ -3819,11 +3816,11 @@ namespace winrt::TerminalApp::implementation _visible = showOrHide; for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { // Manually enumerate the panes in each tab; this will let us recycle TerminalSettings // objects but only have to iterate one time. - terminalTab->GetRootPane()->WalkTree([&](auto&& pane) { + tabImpl->GetRootPane()->WalkTree([&](auto&& pane) { if (auto control = pane->GetTerminalControl()) { control.WindowVisibilityChanged(showOrHide); @@ -3841,7 +3838,7 @@ namespace winrt::TerminalApp::implementation // - tab: the tab where the search box should be created // Return Value: // - - void TerminalPage::_Find(const TerminalTab& tab) + void TerminalPage::_Find(const Tab& tab) { if (const auto& control{ tab.GetActiveTerminalControl() }) { @@ -3959,7 +3956,7 @@ namespace winrt::TerminalApp::implementation _newTabButton.Background(backgroundBrush); _newTabButton.Foreground(foregroundBrush); - // This is just like what we do in TabBase::_RefreshVisualState. We need + // This is just like what we do in Tab::_RefreshVisualState. We need // to manually toggle the visual state, so the setters in the visual // state group will re-apply, and set our currently selected colors in // the resources. @@ -4210,25 +4207,18 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Returns a com_ptr to the implementation type of the given tab if it's a TerminalTab. + // - Returns a com_ptr to the implementation type of the given tab if it's a Tab. // If the tab is not a TerminalTab, returns nullptr. // Arguments: // - tab: the projected type of a Tab // Return Value: // - If the tab is a TerminalTab, a com_ptr to the implementation type. // If the tab is not a TerminalTab, nullptr - winrt::com_ptr TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab) + winrt::com_ptr TerminalPage::_GetTabImpl(const TerminalApp::Tab& tab) { - if (auto terminalTab = tab.try_as()) - { - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(terminalTab)); - return tabImpl; - } - else - { - return nullptr; - } + winrt::com_ptr tabImpl; + tabImpl.copy_from(winrt::get_self(tab)); + return tabImpl; } // Method Description: @@ -4715,10 +4705,10 @@ namespace winrt::TerminalApp::implementation for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { // The root pane will propagate the theme change to all its children. - if (const auto& rootPane{ terminalTab->GetRootPane() }) + if (const auto& rootPane{ tabImpl->GetRootPane() }) { rootPane->UpdateResources(_paneResources); } @@ -4779,7 +4769,7 @@ namespace winrt::TerminalApp::implementation } // Second: Update the colors of our individual TabViewItems. This - // applies tab.background to the tabs via TerminalTab::ThemeColor. + // applies tab.background to the tabs via Tab::ThemeColor. // // Do this second, so that we already know the bgColor of the titlebar. { @@ -4787,8 +4777,8 @@ namespace winrt::TerminalApp::implementation const auto tabUnfocusedBackground = theme.Tab() ? theme.Tab().UnfocusedBackground() : nullptr; for (const auto& tab : _tabs) { - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(tab)); + winrt::com_ptr tabImpl; + tabImpl.copy_from(winrt::get_self(tab)); tabImpl->ThemeColor(tabBackground, tabUnfocusedBackground, bgColor); } } @@ -5257,8 +5247,8 @@ namespace winrt::TerminalApp::implementation // Get the tab impl from this event. const auto eventTab = e.Tab(); const auto tabBase = _GetTabByTabViewItem(eventTab); - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(tabBase)); + winrt::com_ptr tabImpl; + tabImpl.copy_from(winrt::get_self(tabBase)); if (tabImpl) { // First: stash the tab we started dragging. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 4879f65fc6..e4bf6efa1e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -4,7 +4,7 @@ #pragma once #include "TerminalPage.g.h" -#include "TerminalTab.h" +#include "Tab.h" #include "AppKeyBindings.h" #include "AppCommandlineArgs.h" #include "RenameWindowRequestedArgs.g.h" @@ -219,13 +219,13 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; - Windows::Foundation::Collections::IObservableVector _tabs; - Windows::Foundation::Collections::IObservableVector _mruTabs; - static winrt::com_ptr _GetTerminalTabImpl(const TerminalApp::TabBase& tab); + Windows::Foundation::Collections::IObservableVector _tabs; + Windows::Foundation::Collections::IObservableVector _mruTabs; + static winrt::com_ptr _GetTabImpl(const TerminalApp::Tab& tab); void _UpdateTabIndices(); - TerminalApp::TerminalTab _settingsTab{ nullptr }; + TerminalApp::Tab _settingsTab{ nullptr }; bool _isInFocusMode{ false }; bool _isFullscreen{ false }; @@ -277,7 +277,7 @@ namespace winrt::TerminalApp::implementation struct StashedDragData { - winrt::com_ptr draggedTab{ nullptr }; + winrt::com_ptr draggedTab{ nullptr }; winrt::Windows::Foundation::Point dragOffset{ 0, 0 }; } _stashed; @@ -305,7 +305,7 @@ namespace winrt::TerminalApp::implementation void _OpenNewTabDropdown(); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs); - TerminalApp::TerminalTab _CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition = -1); + TerminalApp::Tab _CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition = -1); std::wstring _evaluatePathForCwd(std::wstring_view path); @@ -328,25 +328,25 @@ namespace winrt::TerminalApp::implementation void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) noexcept; void _RegisterActionCallbacks(); - void _UpdateTitle(const TerminalTab& tab); - void _UpdateTabIcon(TerminalTab& tab); + void _UpdateTitle(const Tab& tab); + void _UpdateTabIcon(Tab& tab); void _UpdateTabView(); void _UpdateTabWidthMode(); void _SetBackgroundImage(const winrt::Microsoft::Terminal::Settings::Model::IAppearanceConfig& newAppearance); void _DuplicateFocusedTab(); - void _DuplicateTab(const TerminalTab& tab); + void _DuplicateTab(const Tab& tab); - safe_void_coroutine _ExportTab(const TerminalTab& tab, winrt::hstring filepath); + safe_void_coroutine _ExportTab(const Tab& tab, winrt::hstring filepath); - winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab); + winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::Tab tab); void _CloseTabAtIndex(uint32_t index); - void _RemoveTab(const winrt::TerminalApp::TabBase& tab); - safe_void_coroutine _RemoveTabs(const std::vector tabs); + void _RemoveTab(const winrt::TerminalApp::Tab& tab); + safe_void_coroutine _RemoveTabs(const std::vector tabs); - void _InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition = -1); + void _InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition = -1); void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term); - void _RegisterTabEvents(TerminalTab& hostingTab); + void _RegisterTabEvents(Tab& hostingTab); void _DismissTabContextMenus(); void _FocusCurrentTab(const bool focusAlways); @@ -357,7 +357,7 @@ namespace winrt::TerminalApp::implementation bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args); - bool _MoveTab(winrt::com_ptr tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args); + bool _MoveTab(winrt::com_ptr tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args); template bool _ApplyToActiveControls(F f) @@ -381,21 +381,21 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl(); std::optional _GetFocusedTabIndex() const noexcept; - std::optional _GetTabIndex(const TerminalApp::TabBase& tab) const noexcept; - TerminalApp::TabBase _GetFocusedTab() const noexcept; - winrt::com_ptr _GetFocusedTabImpl() const noexcept; - TerminalApp::TabBase _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept; + std::optional _GetTabIndex(const TerminalApp::Tab& tab) const noexcept; + TerminalApp::Tab _GetFocusedTab() const noexcept; + winrt::com_ptr _GetFocusedTabImpl() const noexcept; + TerminalApp::Tab _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept; void _HandleClosePaneRequested(std::shared_ptr pane); - safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::TabBase tab); + safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::Tab tab); safe_void_coroutine _CloseFocusedPane(); - void _ClosePanes(weak_ref weakTab, std::vector paneIds); + void _ClosePanes(weak_ref weakTab, std::vector paneIds); winrt::Windows::Foundation::IAsyncOperation _PaneConfirmCloseReadOnly(std::shared_ptr pane); void _AddPreviouslyClosedPaneOrTab(std::vector&& args); void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll); - void _SplitPane(const winrt::com_ptr& tab, + void _SplitPane(const winrt::com_ptr& tab, const Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, std::shared_ptr newPane); @@ -439,14 +439,14 @@ namespace winrt::TerminalApp::implementation void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs); void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs); void _OnFirstLayout(const IInspectable& sender, const IInspectable& eventArgs); - void _UpdatedSelectedTab(const winrt::TerminalApp::TabBase& tab); + void _UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab); void _UpdateBackground(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile); void _OnDispatchCommandRequested(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& command); void _OnCommandLineExecutionRequested(const IInspectable& sender, const winrt::hstring& commandLine); - void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::TabBase& tab); + void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::Tab& tab); - void _Find(const TerminalTab& tab); + void _Find(const Tab& tab); winrt::Microsoft::Terminal::Control::TermControl _CreateNewControlAndContent(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection); @@ -455,10 +455,10 @@ namespace winrt::TerminalApp::implementation TerminalApp::IPaneContent _makeSettingsContent(); std::shared_ptr _MakeTerminalPane(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr, - const winrt::TerminalApp::TabBase& sourceTab = nullptr, + const winrt::TerminalApp::Tab& sourceTab = nullptr, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); std::shared_ptr _MakePane(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs = nullptr, - const winrt::TerminalApp::TabBase& sourceTab = nullptr, + const winrt::TerminalApp::Tab& sourceTab = nullptr, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); void _RefreshUIForSettingsReload(); @@ -475,7 +475,7 @@ namespace winrt::TerminalApp::implementation static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll); static uint32_t _ReadSystemRowsToScroll(); - void _UpdateMRUTab(const winrt::TerminalApp::TabBase& tab); + void _UpdateMRUTab(const winrt::TerminalApp::Tab& tab); void _TryMoveTab(const uint32_t currentTabIndex, const int32_t suggestedNewTabIndex); @@ -534,7 +534,7 @@ namespace winrt::TerminalApp::implementation void _onTabDroppedOutside(winrt::Windows::Foundation::IInspectable sender, winrt::Microsoft::UI::Xaml::Controls::TabViewTabDroppedOutsideEventArgs e); void _DetachPaneFromWindow(std::shared_ptr pane); - void _DetachTabFromWindow(const winrt::com_ptr& terminalTab); + void _DetachTabFromWindow(const winrt::com_ptr& tabImpl); void _MoveContent(std::vector&& actions, const winrt::hstring& windowName, const uint32_t tabIndex, @@ -546,9 +546,9 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex); winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender); - winrt::com_ptr _senderOrFocusedTab(const IInspectable& sender); + winrt::com_ptr _senderOrFocusedTab(const IInspectable& sender); - void _activePaneChanged(winrt::TerminalApp::TerminalTab tab, Windows::Foundation::IInspectable args); + void _activePaneChanged(winrt::TerminalApp::Tab tab, Windows::Foundation::IInspectable args); safe_void_coroutine _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs); #pragma region ActionHandlers diff --git a/src/cascadia/TerminalApp/TerminalTab.idl b/src/cascadia/TerminalApp/TerminalTab.idl deleted file mode 100644 index 5dbd845d49..0000000000 --- a/src/cascadia/TerminalApp/TerminalTab.idl +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import "TabBase.idl"; -import "TerminalTabStatus.idl"; - -namespace TerminalApp -{ - [default_interface] runtimeclass TerminalTab : TabBase - { - TerminalTabStatus TabStatus { get; }; - } -} diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 6a20f0ac9b..282f68a589 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -36,7 +36,6 @@ - From 48b796f102420dea6dbf605011ff6cd5a1a666cf Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Wed, 23 Jul 2025 19:07:11 +0200 Subject: [PATCH 114/177] [UX] Settings UI refinements (#19001) This PR is a (first) design pass on the Terminal settings UX to bring consistency and alignment with the rest of the OS and other settings surfaces. High-level changes include: - Using WinUI brushes vs. UWP/OS brushes. [See brushes overview in the WinUI 3 Gallery:](winui3gallery://item/Color). - Updating pixel values for margins, fontsizes, paddings etc. using units of 4. See [the design guidelines for more info](https://learn.microsoft.com/en-us/windows/apps/design/basics/content-basics). - Adding rounded corners on various elements to be consistent with the Windows 11 look and feel (using the ControlCornerRadius or OverlayCornerRadius WinUI resources as much as possible). - Decreasing the page header titles / breadcrumb so it feels a bit less cramped. - Fixing a bug where the title of the page was not aligned with the content when resizing the window (note to self: should fix this in PowerToys too): - Ensuring the subheader texts for settings categories are inline with the rest of the OS (== decreasing the fontsize). --------- Co-authored-by: Carlos Zamora --- .../TerminalSettingsEditor/Actions.xaml | 28 +++++---- .../TerminalSettingsEditor/ColorSchemes.xaml | 26 ++++----- .../CommonResources.xaml | 57 ++++++++++--------- .../EditColorScheme.xaml | 17 +++--- .../TerminalSettingsEditor/Extensions.xaml | 21 ++++--- .../TerminalSettingsEditor/Launch.xaml | 6 +- .../TerminalSettingsEditor/MainPage.xaml | 57 ++++++++++--------- .../TerminalSettingsEditor/NewTabMenu.xaml | 13 ++--- .../NullableColorPicker.xaml | 6 +- .../Profiles_Advanced.xaml | 10 ++-- .../Profiles_Appearance.xaml | 27 ++++----- .../TerminalSettingsEditor/Profiles_Base.xaml | 17 +++--- .../Profiles_Base_Orphaned.xaml | 2 +- .../Profiles_Terminal.xaml | 2 - .../SettingContainerStyle.xaml | 26 ++++----- 15 files changed, 160 insertions(+), 155 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 5eb09daf42..b712170a1c 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -56,13 +56,14 @@ @@ -97,13 +98,15 @@ @@ -138,7 +141,7 @@ 32 - 15 + 14 - -