From 0a7310dee4c36f1c423c58b958ffbdbb164ce140 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Fri, 17 Sep 2021 13:58:08 -0700 Subject: [PATCH 1/7] Adjust tools version for folks running on 2022 (#11266) Adjust tools version for folks running on 2022 ## PR Checklist * [x] Closes annoyance that @lhecker and I have selfhosting VS2022 * [x] I work here * [x] Solution built * [x] @dhowett said something like "lol sure" in Teams. --- src/common.build.pre.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common.build.pre.props b/src/common.build.pre.props index e91120e9f..84ce565c0 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -83,7 +83,8 @@ - v142 + v142 + v143 Unicode false x64 From 74f11b8203a3f297372630e164b7b4d51f82d83e Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 20 Sep 2021 12:08:13 -0500 Subject: [PATCH 2/7] Enable Vintage Opacity (#11180) ## Summary of the Pull Request ![603-final](https://user-images.githubusercontent.com/18356694/132585665-afed3210-257a-4fee-9b43-4273a0f5cf69.gif) Adds support for vintage style opacity, on Windows 11+. The API we're using for this exists since the time immemorial, but there's a bug in XAML Islands that prevents it from working right until Windows 11 (which we're working on backporting). Replaces the `acrylicOpacity` setting with `opacity`, which is a uint between 0 and 100 (inclusive), default to 100. `useAcrylic` now controls whether acrylic is used or not. Setting an opacity < 100 with `"useAcrylic": false` will use vintage style opacity. Mouse wheeling adjusts opacity. Whether acrylic is used or not is dependent upon `useAcrylic`. `opacity` will stealthily default to 50 if `useAcrylic:true` is set. ## PR Checklist * [x] Closes #603 * [x] I work here * [x] Tests added/passed * [x] https://github.com/MicrosoftDocs/terminal/pull/416 ## Detailed Description of the Pull Request / Additional comments Opacity was moved to AppearanceConfig. In the future, I have a mind to allow unfocused acrylic, so that'll be important then. ## Validation Steps Performed _just look at it_ --- doc/cascadia/profiles.schema.json | 10 +++- .../ScratchIslandApp/SampleApp/MySettings.h | 2 +- src/cascadia/TerminalApp/Pane.cpp | 25 +++++++--- src/cascadia/TerminalApp/TabManagement.cpp | 2 +- src/cascadia/TerminalApp/TerminalPage.xaml | 3 +- src/cascadia/TerminalControl/ControlCore.cpp | 38 +++------------ .../TerminalControl/IControlAppearance.idl | 1 + .../TerminalControl/IControlSettings.idl | 1 - src/cascadia/TerminalControl/TermControl.cpp | 10 ++-- src/cascadia/TerminalControl/TermControl.xaml | 1 + .../TerminalSettingsEditor/MainPage.xaml | 1 + .../TerminalSettingsEditor/Profiles.h | 4 +- .../TerminalSettingsEditor/Profiles.idl | 4 +- .../TerminalSettingsEditor/Profiles.xaml | 48 +++++++++---------- .../Resources/en-US/Resources.resw | 12 +++++ .../AppearanceConfig.cpp | 6 +++ .../TerminalSettingsModel/AppearanceConfig.h | 1 + .../CascadiaSettings.cpp | 2 +- .../IAppearanceConfig.idl | 1 + .../TerminalSettingsModel/Profile.cpp | 4 -- .../TerminalSettingsModel/Profile.idl | 1 - .../TerminalSettings.cpp | 11 ++++- .../TerminalSettingsModel/TerminalSettings.h | 2 +- .../TerminalSettingsSerializationHelpers.h | 28 +++++++++++ .../UnitTests_Control/ControlCoreTests.cpp | 19 ++++---- .../ControlInteractivityTests.cpp | 25 +++++----- .../UnitTests_Control/MockControlSettings.h | 2 +- src/cascadia/WindowsTerminal/IslandWindow.cpp | 26 +++++++--- src/cascadia/WindowsTerminal/IslandWindow.h | 2 + .../WindowsTerminal/NonClientIslandWindow.cpp | 17 ++++++- .../WindowsTerminal/WindowsTerminal.vcxproj | 6 +-- src/cascadia/WindowsTerminal/packages.config | 4 +- src/cascadia/WindowsTerminal/pch.h | 1 + 33 files changed, 205 insertions(+), 115 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 07e584c6b..319e0bae7 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -1401,7 +1401,8 @@ "properties": { "acrylicOpacity": { "default": 0.5, - "description": "When useAcrylic is set to true, it sets the transparency of the window for the profile. Accepts floating point values from 0-1 (default 0.5).", + "description": "[deprecated] Please use `opacity` instead.", + "deprecated": true, "maximum": 1, "minimum": 0, "type": "number" @@ -1614,6 +1615,13 @@ "minLength": 1, "type": "string" }, + "opacity": { + "default": 100, + "description": "Sets the opacity of the window for the profile. Accepts values from 0-100. Defaults to 50 when useAcrylic is set to true.", + "maximum": 100, + "minimum": 0, + "type": "number" + }, "padding": { "default": "8, 8, 8, 8", "description": "Sets the padding around the text within the window. Can have three different formats:\n -\"#\" sets the same padding for all sides \n -\"#, #\" sets the same padding for left-right and top-bottom\n -\"#, #, #, #\" sets the padding individually for left, top, right, and bottom.", diff --git a/scratch/ScratchIslandApp/SampleApp/MySettings.h b/scratch/ScratchIslandApp/SampleApp/MySettings.h index 34643f7fe..4d34b8624 100644 --- a/scratch/ScratchIslandApp/SampleApp/MySettings.h +++ b/scratch/ScratchIslandApp/SampleApp/MySettings.h @@ -45,7 +45,7 @@ namespace winrt::SampleApp::implementation WINRT_PROPERTY(winrt::hstring, ProfileName); WINRT_PROPERTY(bool, UseAcrylic, false); - WINRT_PROPERTY(double, TintOpacity, 0.5); + WINRT_PROPERTY(double, Opacity, .5); WINRT_PROPERTY(winrt::hstring, Padding, DEFAULT_PADDING); WINRT_PROPERTY(winrt::hstring, FontFace, L"Consolas"); WINRT_PROPERTY(int32_t, FontSize, DEFAULT_FONT_SIZE); diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index f3940e563..0c5705a7b 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -53,10 +53,6 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo _SetupResources(); } - // Use the unfocused border color as the pane background, so an actual color - // appears behind panes as we animate them sliding in. - _root.Background(s_unfocusedBorderBrush); - // Register an event with the control to have it inform us when it gains focus. _gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler }); _lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler }); @@ -1616,6 +1612,8 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst) // Create the dummy grid. This grid will be the one we actually animate, // in the place of the closed pane. Controls::Grid dummyGrid; + // GH#603 - we can safely add a BG here, as the control is gone right + // away, to fill the space as the rest of the pane expands. dummyGrid.Background(s_unfocusedBorderBrush); // It should be the size of the closed pane. dummyGrid.Width(removedOriginalSize.Width); @@ -1871,6 +1869,19 @@ void Pane::_SetupEntranceAnimation() return; } + // Use the unfocused border color as the pane background, so an actual color + // appears behind panes as we animate them sliding in. + // + // GH#603 - We set only the background of the new pane, while it animates + // in. Once the animation is done, we'll remove that background, so if the + // user wants vintage opacity, they'll be able to see what's under the + // window. + // * If we don't give it a background, then the BG will be entirely transparent. + // * If we give the parent (us) root BG a color, then a transparent pane + // will flash opaque during the animation, then back to transparent, which + // looks bad. + _secondChild->_root.Background(s_unfocusedBorderBrush); + const auto [firstSize, secondSize] = _CalcChildrenSizes(::base::saturated_cast(totalSize)); // This is safe to capture this, because it's only being called in the @@ -1938,11 +1949,12 @@ void Pane::_SetupEntranceAnimation() // When the animation is completed, undo the trickiness from before, to // restore the controls to the behavior they'd usually have. - animation.Completed([childGrid, control](auto&&, auto&&) { + animation.Completed([childGrid, control, root = _secondChild->_root](auto&&, auto&&) { control.Width(NAN); childGrid.Width(NAN); childGrid.HorizontalAlignment(HorizontalAlignment::Stretch); control.HorizontalAlignment(HorizontalAlignment::Stretch); + root.Background(nullptr); }); } else @@ -1956,11 +1968,12 @@ void Pane::_SetupEntranceAnimation() // When the animation is completed, undo the trickiness from before, to // restore the controls to the behavior they'd usually have. - animation.Completed([childGrid, control](auto&&, auto&&) { + animation.Completed([childGrid, control, root = _secondChild->_root](auto&&, auto&&) { control.Height(NAN); childGrid.Height(NAN); childGrid.VerticalAlignment(VerticalAlignment::Stretch); control.VerticalAlignment(VerticalAlignment::Stretch); + root.Background(nullptr); }); } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 3730f7ac7..f57e3fe9b 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -87,7 +87,7 @@ namespace winrt::TerminalApp::implementation TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"), TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The GUID of the profile spawned in the new tab"), TraceLoggingBool(settings.DefaultSettings().UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"), - TraceLoggingFloat64(settings.DefaultSettings().TintOpacity(), "TintOpacity", "Opacity preference from the settings"), + TraceLoggingFloat64(settings.DefaultSettings().Opacity(), "TintOpacity", "Opacity preference from the settings"), TraceLoggingWideString(settings.DefaultSettings().FontFace().c_str(), "FontFace", "Font face chosen in the settings"), TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index eea2039f6..b6a4676dd 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -9,10 +9,11 @@ xmlns:local="using:TerminalApp" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mux="using:Microsoft.UI.Xaml.Controls" + Background="Transparent" mc:Ignorable="d"> + Background="Transparent"> diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index eac350c2a..0231c1acb 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -276,7 +276,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // GH#5098: Inform the engine of the opacity of the default text background. if (_settings.UseAcrylic()) { - _renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast(_settings.TintOpacity())); + _renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast(_settings.Opacity())); } THROW_IF_FAILED(_renderEngine->Enable()); @@ -425,41 +425,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - auto newOpacity = std::clamp(_settings.TintOpacity() + adjustment, + auto newOpacity = std::clamp(_settings.Opacity() + adjustment, 0.0, 1.0); - if (_settings.UseAcrylic()) - { - try - { - _settings.TintOpacity(newOpacity); - if (newOpacity >= 1.0) - { - _settings.UseAcrylic(false); - } - else - { - // GH#5098: Inform the engine of the new opacity of the default text background. - SetBackgroundOpacity(::base::saturated_cast(newOpacity)); - } + // GH#5098: Inform the engine of the new opacity of the default text background. + SetBackgroundOpacity(::base::saturated_cast(newOpacity)); - auto eventArgs = winrt::make_self(newOpacity); - _TransparencyChangedHandlers(*this, *eventArgs); - } - CATCH_LOG(); - } - else if (adjustment < 0) - { - _settings.UseAcrylic(true); + _settings.Opacity(newOpacity); - //Setting initial opacity set to 1 to ensure smooth transition to acrylic during mouse scroll - newOpacity = std::clamp(1.0 + adjustment, 0.0, 1.0); - _settings.TintOpacity(newOpacity); - - auto eventArgs = winrt::make_self(newOpacity); - _TransparencyChangedHandlers(*this, *eventArgs); - } + auto eventArgs = winrt::make_self(newOpacity); + _TransparencyChangedHandlers(*this, *eventArgs); } void ControlCore::ToggleShaderEffects() diff --git a/src/cascadia/TerminalControl/IControlAppearance.idl b/src/cascadia/TerminalControl/IControlAppearance.idl index a0fa0a0ad..fd06bc853 100644 --- a/src/cascadia/TerminalControl/IControlAppearance.idl +++ b/src/cascadia/TerminalControl/IControlAppearance.idl @@ -13,6 +13,7 @@ namespace Microsoft.Terminal.Control Windows.UI.Xaml.VerticalAlignment BackgroundImageVerticalAlignment; Boolean IntenseIsBold; // IntenseIsBright is in Core Appearance + Double Opacity; // Experimental settings Boolean RetroTerminalEffect; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 56aab7395..1b67df91f 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -29,7 +29,6 @@ namespace Microsoft.Terminal.Control String ProfileName; Boolean UseAcrylic; - Double TintOpacity; ScrollbarState ScrollState; String FontFace; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 115f9afc5..fc74dd0a9 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -428,6 +428,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void TermControl::_InitializeBackgroundBrush() { + auto appearance = _settings.try_as(); if (_settings.UseAcrylic()) { // See if we've already got an acrylic background brush @@ -449,7 +450,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation acrylic.TintColor(bgColor); // Apply brush settings - acrylic.TintOpacity(_settings.TintOpacity()); + acrylic.TintOpacity(appearance.Opacity()); // Apply brush to control if it's not already there if (RootGrid().Background() != acrylic) @@ -458,15 +459,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // GH#5098: Inform the engine of the new opacity of the default text background. - _core.SetBackgroundOpacity(::base::saturated_cast(_settings.TintOpacity())); + _core.SetBackgroundOpacity(::base::saturated_cast(appearance.Opacity())); } else { Media::SolidColorBrush solidColor{}; + solidColor.Opacity(_settings.Opacity()); RootGrid().Background(solidColor); // GH#5098: Inform the engine of the new opacity of the default text background. - _core.SetBackgroundOpacity(1.0f); + _core.SetBackgroundOpacity(appearance.Opacity()); } } @@ -497,7 +499,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation } else if (auto solidColor = RootGrid().Background().try_as()) { + const auto originalOpacity = solidColor.Opacity(); solidColor.Color(bg); + solidColor.Opacity(originalOpacity); } } } diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index 94aa92c5f..663ccf7ce 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -14,6 +14,7 @@ d:DesignWidth="1024" AllowDrop="True" AllowFocusOnInteraction="True" + Background="Transparent" CharacterReceived="_CharacterHandler" DragOver="_DragOverHandler" Drop="_DragDropHandler" diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 9576102a1..9d24ac109 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -8,6 +8,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" + Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" mc:Ignorable="d"> diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.h b/src/cascadia/TerminalSettingsEditor/Profiles.h index e1ceba5d0..54d7d4108 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.h +++ b/src/cascadia/TerminalSettingsEditor/Profiles.h @@ -22,7 +22,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void SetAcrylicOpacityPercentageValue(double value) { - AcrylicOpacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(value)); + _profile.DefaultAppearance().Opacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(value)); }; void SetPadding(double value) @@ -66,7 +66,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_profile, TabColor); OBSERVABLE_PROJECTED_SETTING(_profile, SuppressApplicationTitle); OBSERVABLE_PROJECTED_SETTING(_profile, UseAcrylic); - OBSERVABLE_PROJECTED_SETTING(_profile, AcrylicOpacity); OBSERVABLE_PROJECTED_SETTING(_profile, ScrollState); OBSERVABLE_PROJECTED_SETTING(_profile, Padding); OBSERVABLE_PROJECTED_SETTING(_profile, Commandline); @@ -78,6 +77,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Background); OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), SelectionBackground); OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), CursorColor); + OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Opacity); OBSERVABLE_PROJECTED_SETTING(_profile, HistorySize); OBSERVABLE_PROJECTED_SETTING(_profile, SnapOnInput); OBSERVABLE_PROJECTED_SETTING(_profile, AltGrAliasing); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.idl b/src/cascadia/TerminalSettingsEditor/Profiles.idl index 4ecc48899..482a19671 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.idl +++ b/src/cascadia/TerminalSettingsEditor/Profiles.idl @@ -47,7 +47,7 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, TabColor); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SuppressApplicationTitle); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, UseAcrylic); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Double, AcrylicOpacity); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Double, Opacity); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.ScrollbarState, ScrollState); OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Padding); OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Commandline); @@ -109,6 +109,6 @@ namespace Microsoft.Terminal.Settings.Editor IInspectable CurrentScrollState; Windows.Foundation.Collections.IObservableVector ScrollStateList { get; }; - Windows.UI.Xaml.Controls.Slider AcrylicOpacitySlider { get; }; + Windows.UI.Xaml.Controls.Slider OpacitySlider { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.xaml b/src/cascadia/TerminalSettingsEditor/Profiles.xaml index 5e11e2450..24e6a7fe1 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles.xaml @@ -238,11 +238,33 @@ - + - + + + + + + + + + + + + + + - - - - - - - - - - - - - diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 4c39ba4d1..a21ae08e3 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -495,6 +495,14 @@ Sets the transparency of the window. A description for what the "acrylic opacity" setting does. Presented near "Profile_AcrylicOpacity.Header". + + Background Opacity + Header 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. + + + Sets the transparency of the window. + A description for what the "opacity" setting does. Presented near "Profile_Opacity.Header". + Advanced Header for a sub-page of profile settings focused on more advanced scenarios. @@ -986,6 +994,10 @@ Acrylic Header for a group of settings related to the acrylic texture rendering on the background of the app. + + Transparency + Header for a group of settings related to transparency, including the acrylic texture rendering on the background of the app. + Background image Header for a group of settings that control the image presented on the background of the app. Presented near "Profile_BackgroundImage" and other keys starting with "Profile_BackgroundImage". diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp index 67eb279ea..1f8c88758 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp @@ -26,6 +26,8 @@ static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageA static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" }; static constexpr std::string_view PixelShaderPathKey{ "experimental.pixelShaderPath" }; static constexpr std::string_view IntenseTextStyleKey{ "intenseTextStyle" }; +static constexpr std::string_view LegacyAcrylicTransparencyKey{ "acrylicOpacity" }; +static constexpr std::string_view OpacityKey{ "opacity" }; winrt::Microsoft::Terminal::Settings::Model::implementation::AppearanceConfig::AppearanceConfig(const winrt::weak_ref sourceProfile) : _sourceProfile(sourceProfile) @@ -50,6 +52,7 @@ winrt::com_ptr AppearanceConfig::CopyAppearance(const winrt::c appearance->_RetroTerminalEffect = sourceAppearance->_RetroTerminalEffect; appearance->_PixelShaderPath = sourceAppearance->_PixelShaderPath; appearance->_IntenseTextStyle = sourceAppearance->_IntenseTextStyle; + appearance->_Opacity = sourceAppearance->_Opacity; return appearance; } @@ -71,6 +74,7 @@ Json::Value AppearanceConfig::ToJson() const JsonUtils::SetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect); JsonUtils::SetValueForKey(json, PixelShaderPathKey, _PixelShaderPath); JsonUtils::SetValueForKey(json, IntenseTextStyleKey, _IntenseTextStyle); + JsonUtils::SetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter{}); return json; } @@ -102,6 +106,8 @@ void AppearanceConfig::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect); JsonUtils::GetValueForKey(json, PixelShaderPathKey, _PixelShaderPath); JsonUtils::GetValueForKey(json, IntenseTextStyleKey, _IntenseTextStyle); + JsonUtils::GetValueForKey(json, LegacyAcrylicTransparencyKey, _Opacity); + JsonUtils::GetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter{}); } winrt::Microsoft::Terminal::Settings::Model::Profile AppearanceConfig::SourceProfile() diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h index 7a0c47985..d28b9a92c 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h @@ -53,6 +53,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::IAppearanceConfig, bool, RetroTerminalEffect, false); INHERITABLE_SETTING(Model::IAppearanceConfig, hstring, PixelShaderPath, L""); INHERITABLE_SETTING(Model::IAppearanceConfig, Model::IntenseStyle, IntenseTextStyle, Model::IntenseStyle::Bright); + INHERITABLE_SETTING(Model::IAppearanceConfig, double, Opacity, 1.0); private: winrt::weak_ref _sourceProfile; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index e98592ce4..c2b2fceb2 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -307,7 +307,6 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate DUPLICATE_SETTING_MACRO(TabColor); DUPLICATE_SETTING_MACRO(SuppressApplicationTitle); DUPLICATE_SETTING_MACRO(UseAcrylic); - DUPLICATE_SETTING_MACRO(AcrylicOpacity); DUPLICATE_SETTING_MACRO(ScrollState); DUPLICATE_SETTING_MACRO(Padding); DUPLICATE_SETTING_MACRO(Commandline); @@ -347,6 +346,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate DUPLICATE_SETTING_MACRO_SUB(appearance, target, RetroTerminalEffect); DUPLICATE_SETTING_MACRO_SUB(appearance, target, CursorShape); DUPLICATE_SETTING_MACRO_SUB(appearance, target, CursorHeight); + DUPLICATE_SETTING_MACRO_SUB(appearance, target, Opacity); } // UnfocusedAppearance is treated as a single setting, diff --git a/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl b/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl index b33cf23e7..af89f55e8 100644 --- a/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl +++ b/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl @@ -51,5 +51,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_APPEARANCE_SETTING(Boolean, RetroTerminalEffect); INHERITABLE_APPEARANCE_SETTING(String, PixelShaderPath); INHERITABLE_APPEARANCE_SETTING(IntenseStyle, IntenseTextStyle); + INHERITABLE_APPEARANCE_SETTING(Double, Opacity); }; } diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index 338de21ac..c76247a9f 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -36,7 +36,6 @@ static constexpr std::string_view AltGrAliasingKey{ "altGrAliasing" }; static constexpr std::string_view ConnectionTypeKey{ "connectionType" }; static constexpr std::string_view CommandlineKey{ "commandline" }; static constexpr std::string_view FontInfoKey{ "font" }; -static constexpr std::string_view AcrylicTransparencyKey{ "acrylicOpacity" }; static constexpr std::string_view UseAcrylicKey{ "useAcrylic" }; static constexpr std::string_view ScrollbarStateKey{ "scrollbarState" }; static constexpr std::string_view CloseOnExitKey{ "closeOnExit" }; @@ -95,7 +94,6 @@ winrt::com_ptr Profile::CopySettings(winrt::com_ptr source) profile->_TabColor = source->_TabColor; profile->_SuppressApplicationTitle = source->_SuppressApplicationTitle; profile->_UseAcrylic = source->_UseAcrylic; - profile->_AcrylicOpacity = source->_AcrylicOpacity; profile->_ScrollState = source->_ScrollState; profile->_Padding = source->_Padding; profile->_Commandline = source->_Commandline; @@ -356,7 +354,6 @@ void Profile::LayerJson(const Json::Value& json) // Control Settings JsonUtils::GetValueForKey(json, ConnectionTypeKey, _ConnectionType); JsonUtils::GetValueForKey(json, CommandlineKey, _Commandline); - JsonUtils::GetValueForKey(json, AcrylicTransparencyKey, _AcrylicOpacity); JsonUtils::GetValueForKey(json, UseAcrylicKey, _UseAcrylic); JsonUtils::GetValueForKey(json, SuppressApplicationTitleKey, _SuppressApplicationTitle); JsonUtils::GetValueForKey(json, CloseOnExitKey, _CloseOnExit); @@ -548,7 +545,6 @@ Json::Value Profile::ToJson() const // Control Settings JsonUtils::SetValueForKey(json, ConnectionTypeKey, _ConnectionType); JsonUtils::SetValueForKey(json, CommandlineKey, _Commandline); - JsonUtils::SetValueForKey(json, AcrylicTransparencyKey, _AcrylicOpacity); JsonUtils::SetValueForKey(json, UseAcrylicKey, _UseAcrylic); JsonUtils::SetValueForKey(json, SuppressApplicationTitleKey, _SuppressApplicationTitle); JsonUtils::SetValueForKey(json, CloseOnExitKey, _CloseOnExit); diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 5ad1ae933..c69d57280 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -67,7 +67,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Windows.Foundation.IReference, TabColor); INHERITABLE_PROFILE_SETTING(Boolean, SuppressApplicationTitle); INHERITABLE_PROFILE_SETTING(Boolean, UseAcrylic); - INHERITABLE_PROFILE_SETTING(Double, AcrylicOpacity); INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.ScrollbarState, ScrollState); INHERITABLE_PROFILE_SETTING(String, Padding); INHERITABLE_PROFILE_SETTING(String, Commandline); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index ff46d81b6..3d7b2f11e 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -201,6 +201,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _IntenseIsBold = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bold); _IntenseIsBright = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bright); + + // If the user set an opacity, then just use that. Otherwise, change the + // default value based off of whether useAcrylic was set or not. If they + // want acrylic, then default to 50%. Otherwise, default to 100% (fully + // opaque) + _Opacity = appearance.HasOpacity() ? + appearance.Opacity() : + UseAcrylic() ? + .5 : + 1.0; } // Method Description: @@ -273,7 +283,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Fill in the remaining properties from the profile _ProfileName = profile.Name(); _UseAcrylic = profile.UseAcrylic(); - _TintOpacity = profile.AcrylicOpacity(); _FontFace = profile.FontInfo().FontFace(); _FontSize = profile.FontInfo().FontSize(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 8525668ab..1c27f689e 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -118,7 +118,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, hstring, ProfileName); INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAcrylic, false); - INHERITABLE_SETTING(Model::TerminalSettings, double, TintOpacity, 0.5); + INHERITABLE_SETTING(Model::TerminalSettings, double, Opacity, UseAcrylic() ? 0.5 : 1.0); INHERITABLE_SETTING(Model::TerminalSettings, hstring, Padding, DEFAULT_PADDING); INHERITABLE_SETTING(Model::TerminalSettings, hstring, FontFace, DEFAULT_FONT_FACE); INHERITABLE_SETTING(Model::TerminalSettings, int32_t, FontSize, DEFAULT_FONT_SIZE); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 83c791f95..a4503efb7 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -342,6 +342,34 @@ struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winr } }; +struct IntAsFloatPercentConversionTrait : ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait +{ + double FromJson(const Json::Value& json) + { + return ::base::saturated_cast(json.asUInt()) / 100.0; + } + + bool CanConvert(const Json::Value& json) + { + if (!json.isUInt()) + { + return false; + } + const auto value = json.asUInt(); + return value >= 0 && value <= 100; + } + + Json::Value ToJson(const double& val) + { + return std::clamp(::base::saturated_cast(std::round(val * 100.0)), 0u, 100u); + } + + std::string TypeDescription() const + { + return "number (>= 0, <=100)"; + } +}; + // Possible FocusDirection values JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FocusDirection) { diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index 9e8599032..9e4a19e7b 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -119,7 +119,7 @@ namespace ControlUnitTests auto [settings, conn] = _createSettingsAndConnection(); settings->UseAcrylic(true); - settings->TintOpacity(0.5f); + settings->Opacity(0.5f); auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); @@ -128,16 +128,19 @@ namespace ControlUnitTests double expectedOpacity = 0.5; auto opacityCallback = [&](auto&&, Control::TransparencyChangedEventArgs args) mutable { VERIFY_ARE_EQUAL(expectedOpacity, args.Opacity()); - VERIFY_ARE_EQUAL(expectedOpacity, settings->TintOpacity()); - VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.TintOpacity()); + VERIFY_ARE_EQUAL(expectedOpacity, settings->Opacity()); + VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.Opacity()); if (expectedOpacity < 1.0) { VERIFY_IS_TRUE(settings->UseAcrylic()); VERIFY_IS_TRUE(core->_settings.UseAcrylic()); } - VERIFY_ARE_EQUAL(expectedOpacity < 1.0, settings->UseAcrylic()); - VERIFY_ARE_EQUAL(expectedOpacity < 1.0, core->_settings.UseAcrylic()); + + // GH#603: Adjusting opacity shouldn't change whether or not we + // requested acrylic. + VERIFY_IS_TRUE(settings->UseAcrylic()); + VERIFY_IS_TRUE(core->_settings.UseAcrylic()); }; core->TransparencyChanged(opacityCallback); @@ -220,7 +223,7 @@ namespace ControlUnitTests { auto [settings, conn] = _createSettingsAndConnection(); Log::Comment(L"Create ControlCore object"); - auto core = winrt::make_self(*settings, *conn); + auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); _standardInit(core); @@ -259,7 +262,7 @@ namespace ControlUnitTests { auto [settings, conn] = _createSettingsAndConnection(); Log::Comment(L"Create ControlCore object"); - auto core = winrt::make_self(*settings, *conn); + auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); _standardInit(core); @@ -298,7 +301,7 @@ namespace ControlUnitTests { auto [settings, conn] = _createSettingsAndConnection(); Log::Comment(L"Create ControlCore object"); - auto core = winrt::make_self(*settings, *conn); + auto core = createCore(*settings, *conn); VERIFY_IS_NOT_NULL(core); _standardInit(core); diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp index dba09c9e4..3cffb107c 100644 --- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp @@ -99,10 +99,16 @@ namespace ControlUnitTests WEX::TestExecution::SetVerifyOutput verifyOutputScope{ WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures }; + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:useAcrylic", L"{true, false}") + END_TEST_METHOD_PROPERTIES() + bool useAcrylic; + VERIFY_SUCCEEDED(TestData::TryGetValue(L"useAcrylic", useAcrylic), L"whether or not we should enable acrylic"); + auto [settings, conn] = _createSettingsAndConnection(); - settings->UseAcrylic(true); - settings->TintOpacity(0.5f); + settings->UseAcrylic(useAcrylic); + settings->Opacity(0.5f); auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn); @@ -110,16 +116,11 @@ namespace ControlUnitTests double expectedOpacity = 0.5; auto opacityCallback = [&](auto&&, Control::TransparencyChangedEventArgs args) mutable { VERIFY_ARE_EQUAL(expectedOpacity, args.Opacity()); - VERIFY_ARE_EQUAL(expectedOpacity, settings->TintOpacity()); - VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.TintOpacity()); + VERIFY_ARE_EQUAL(expectedOpacity, settings->Opacity()); + VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.Opacity()); - if (expectedOpacity < 1.0) - { - VERIFY_IS_TRUE(settings->UseAcrylic()); - VERIFY_IS_TRUE(core->_settings.UseAcrylic()); - } - VERIFY_ARE_EQUAL(expectedOpacity < 1.0, settings->UseAcrylic()); - VERIFY_ARE_EQUAL(expectedOpacity < 1.0, core->_settings.UseAcrylic()); + VERIFY_ARE_EQUAL(useAcrylic, settings->UseAcrylic()); + VERIFY_ARE_EQUAL(useAcrylic, core->_settings.UseAcrylic()); }; core->TransparencyChanged(opacityCallback); @@ -157,7 +158,7 @@ namespace ControlUnitTests // The mouse location and buttons don't matter here. interactivity->MouseWheel(modifiers, - 30, + -30, til::point{ 0, 0 }, buttonState); } diff --git a/src/cascadia/UnitTests_Control/MockControlSettings.h b/src/cascadia/UnitTests_Control/MockControlSettings.h index d84006589..c2e6db9ae 100644 --- a/src/cascadia/UnitTests_Control/MockControlSettings.h +++ b/src/cascadia/UnitTests_Control/MockControlSettings.h @@ -51,7 +51,7 @@ namespace ControlUnitTests WINRT_PROPERTY(winrt::hstring, ProfileName); WINRT_PROPERTY(bool, UseAcrylic, false); - WINRT_PROPERTY(double, TintOpacity, 0.5); + WINRT_PROPERTY(double, Opacity, .5); WINRT_PROPERTY(winrt::hstring, Padding, DEFAULT_PADDING); WINRT_PROPERTY(winrt::hstring, FontFace, L"Consolas"); WINRT_PROPERTY(int32_t, FontSize, DEFAULT_FONT_SIZE); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 0fa5654c4..8fc7e78d3 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -7,6 +7,8 @@ #include "resource.h" #include "icon.h" #include "NotificationIcon.h" +#include +#include extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -59,7 +61,14 @@ void IslandWindow::MakeWindow() noexcept // Create the window with the default size here - During the creation of the // window, the system will give us a chance to set its size in WM_CREATE. // WM_CREATE will be handled synchronously, before CreateWindow returns. - WINRT_VERIFY(CreateWindowEx(_alwaysOnTop ? WS_EX_TOPMOST : 0, + // + // We need WS_EX_NOREDIRECTIONBITMAP for vintage style opacity, GH#603 + // + // WS_EX_LAYERED acts REAL WEIRD with TerminalTrySetTransparentBackground, + // but it works just fine when the window is in the TOPMOST group. But if + // you enable it always, activating the window will remove our DWM frame + // entirely. Weird. + WINRT_VERIFY(CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP | (_alwaysOnTop ? WS_EX_TOPMOST : 0), wc.lpszClassName, L"Windows Terminal", WS_OVERLAPPEDWINDOW, @@ -311,6 +320,10 @@ void IslandWindow::Initialize() _taskbar = std::move(taskbar); } } + + // Enable vintage opacity by removing the XAML emergency backstop, GH#603. + // We don't really care if this failed or not. + TerminalTrySetTransparentBackground(true); } void IslandWindow::OnSize(const UINT width, const UINT height) @@ -424,6 +437,7 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize { _WindowActivatedHandlers(); } + break; } @@ -828,7 +842,7 @@ void IslandWindow::SetTaskbarProgress(const size_t state, const size_t progress) } // From GdiEngine::s_SetWindowLongWHelper -void _SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept +void SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept { // SetWindowLong has strange error handling. On success, it returns the // previous Window Long value and doesn't modify the Last Error state. To @@ -919,14 +933,14 @@ void IslandWindow::_SetIsBorderless(const bool borderlessEnabled) // First, modify regular window styles as appropriate auto windowStyle = _getDesiredWindowStyle(); - _SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle); + SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle); // Now modify extended window styles as appropriate // When moving to fullscreen, remove the window edge style to avoid an // ugly border when not focused. auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE); WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen); - _SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle); + SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle); // Resize the window, with SWP_FRAMECHANGED, to trigger user32 to // recalculate the non/client areas @@ -1064,14 +1078,14 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled) // First, modify regular window styles as appropriate auto windowStyle = _getDesiredWindowStyle(); - _SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle); + SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle); // Now modify extended window styles as appropriate // When moving to fullscreen, remove the window edge style to avoid an // ugly border when not focused. auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE); WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen); - _SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle); + SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle); // Only change the window position if changing fullscreen state. if (fChangingFullscreen) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index b15f63a23..8f6b283c1 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -6,6 +6,8 @@ #include #include "../../cascadia/inc/cppwinrt_utils.h" +void SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept; + class IslandWindow : public BaseWindow { diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index c00347708..b4f0683fa 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -685,9 +685,18 @@ SIZE NonClientIslandWindow::GetTotalNonClientExclusiveSize(UINT dpi) const noexc // - the HRESULT returned by DwmExtendFrameIntoClientArea. void NonClientIslandWindow::_UpdateFrameMargins() const noexcept { - MARGINS margins = {}; + MARGINS margins = { 0, 0, 0, 0 }; - if (_GetTopBorderHeight() != 0) + // GH#603: When we're in Focus Mode, hide the titlebar, by setting it to a single + // pixel tall. Otherwise, the titlebar will be visible underneath controls with + // vintage opacity set. + // + // We can't set it to all 0's unfortunately. + if (_borderless) + { + margins.cyTopHeight = 1; + } + else if (_GetTopBorderHeight() != 0) { RECT frame = {}; winrt::check_bool(::AdjustWindowRectExForDpi(&frame, GetWindowStyle(_window.get()), FALSE, 0, _currentDpi)); @@ -896,6 +905,10 @@ void NonClientIslandWindow::_SetIsBorderless(const bool borderlessEnabled) _titlebar.Visibility(_IsTitlebarVisible() ? Visibility::Visible : Visibility::Collapsed); } + // Update the margins when entering/leaving focus mode, so we can prevent + // the titlebar from showing through transparent terminal controls + _UpdateFrameMargins(); + // GH#4224 - When the auto-hide taskbar setting is enabled, then we don't // always get another window message to trigger us to remove the drag bar. // So, make sure to update the size of the drag region here, so that it diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index 07ba6658f..e0e6ae06a 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -128,7 +128,7 @@ - + - - \ No newline at end of file + + diff --git a/src/cascadia/WindowsTerminal/packages.config b/src/cascadia/WindowsTerminal/packages.config index f2f5c3f58..db58384dc 100644 --- a/src/cascadia/WindowsTerminal/packages.config +++ b/src/cascadia/WindowsTerminal/packages.config @@ -3,6 +3,6 @@ - - + + diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h index 01d2f04a7..59f24e949 100644 --- a/src/cascadia/WindowsTerminal/pch.h +++ b/src/cascadia/WindowsTerminal/pch.h @@ -67,6 +67,7 @@ Abstract: #include #include #include +#include #include #include From cfe14e87118b2bd11c5f732afa0d336227fac5ce Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 20 Sep 2021 17:08:55 -0500 Subject: [PATCH 3/7] Update to MUX 2.7 (#11240) * this is the same thing as #10996, but with the fix that caused us to #11031 * This includes https://github.com/microsoft/microsoft-ui-xaml/pull/3769, so we had to make some adjustments to how we handle tab colors. It works the same as before. * Should enable #11231 to be started * [x] Closes #10508 * [x] Closes #7133 * [x] Closes #8948 * [ ] I need to finish letting my 19H1 VM boot to make sure unpackaged still works --- .../ScratchIslandApp/Package/Package.wapproj | 4 ++-- .../SampleApp/SampleAppLib.vcxproj | 4 ++-- .../SampleApp/dll/SampleApp.vcxproj | 4 ++-- .../SampleApp/packages.config | 2 +- .../WindowExe/WindowExe.vcxproj | 4 ++-- .../WindowExe/packages.config | 2 +- .../CascadiaPackage/CascadiaPackage.wapproj | 4 ++-- .../SettingsModel.LocalTests.vcxproj | 4 ++-- .../TerminalApp.LocalTests.vcxproj | 4 ++-- .../TestHostApp/TestHostApp.vcxproj | 2 +- src/cascadia/TerminalApp/App.xaml | 3 ++- .../TerminalApp/TerminalAppLib.vcxproj | 5 +++-- src/cascadia/TerminalApp/TerminalTab.cpp | 21 +++++++++++++++---- .../TerminalApp/dll/TerminalApp.vcxproj | 4 ++-- src/cascadia/TerminalApp/packages.config | 2 +- ...Microsoft.Terminal.Settings.Editor.vcxproj | 4 ++-- .../TerminalSettingsEditor/packages.config | 2 +- ...crosoft.Terminal.Settings.ModelLib.vcxproj | 7 ++++--- .../Microsoft.Terminal.Settings.Model.vcxproj | 4 ++-- .../WindowsTerminal/WindowsTerminal.vcxproj | 4 ++-- src/cascadia/WindowsTerminal/packages.config | 2 +- .../WindowsTerminalUniversal.vcxproj | 4 ++-- .../ut_app/TerminalApp.UnitTests.vcxproj | 2 +- 23 files changed, 57 insertions(+), 41 deletions(-) diff --git a/scratch/ScratchIslandApp/Package/Package.wapproj b/scratch/ScratchIslandApp/Package/Package.wapproj index 262719541..f7458a902 100644 --- a/scratch/ScratchIslandApp/Package/Package.wapproj +++ b/scratch/ScratchIslandApp/Package/Package.wapproj @@ -140,12 +140,12 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/scratch/ScratchIslandApp/SampleApp/SampleAppLib.vcxproj b/scratch/ScratchIslandApp/SampleApp/SampleAppLib.vcxproj index 54a6c5706..04b9b0824 100644 --- a/scratch/ScratchIslandApp/SampleApp/SampleAppLib.vcxproj +++ b/scratch/ScratchIslandApp/SampleApp/SampleAppLib.vcxproj @@ -147,13 +147,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.vcxproj b/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.vcxproj index 23b157e9d..59d500ccc 100644 --- a/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.vcxproj +++ b/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.vcxproj @@ -80,13 +80,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/scratch/ScratchIslandApp/SampleApp/packages.config b/scratch/ScratchIslandApp/SampleApp/packages.config index 9de6f1b8d..78467467c 100644 --- a/scratch/ScratchIslandApp/SampleApp/packages.config +++ b/scratch/ScratchIslandApp/SampleApp/packages.config @@ -1,6 +1,6 @@  - + diff --git a/scratch/ScratchIslandApp/WindowExe/WindowExe.vcxproj b/scratch/ScratchIslandApp/WindowExe/WindowExe.vcxproj index e8cb725b7..4332ae5d0 100644 --- a/scratch/ScratchIslandApp/WindowExe/WindowExe.vcxproj +++ b/scratch/ScratchIslandApp/WindowExe/WindowExe.vcxproj @@ -120,14 +120,14 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/scratch/ScratchIslandApp/WindowExe/packages.config b/scratch/ScratchIslandApp/WindowExe/packages.config index 3d9e08c4b..d55a833a5 100644 --- a/scratch/ScratchIslandApp/WindowExe/packages.config +++ b/scratch/ScratchIslandApp/WindowExe/packages.config @@ -2,6 +2,6 @@ - + diff --git a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj index b18c6d57a..ba722f246 100644 --- a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj +++ b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj @@ -146,12 +146,12 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj index ac1af0396..576c89e7d 100644 --- a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj @@ -98,10 +98,10 @@ x86 $(Platform) - <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\runtimes\win10-$(Native-Platform)\native\" + <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\runtimes\win10-$(Native-Platform)\native\" - + diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj index a6340764b..95d8ea17f 100644 --- a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj @@ -92,11 +92,11 @@ x86 $(Platform) - <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\runtimes\win10-$(Native-Platform)\native\" + <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\runtimes\win10-$(Native-Platform)\native\" - + diff --git a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj index cef96d52f..722ce9f5d 100644 --- a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj @@ -123,7 +123,7 @@ - + diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index bc77872e3..251028dee 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -20,7 +20,8 @@ - + - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index f5e2ca206..53c75eb6f 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -1400,11 +1400,22 @@ namespace winrt::TerminalApp::implementation deselectedTabBrush.Color(deselectedTabColor); // currently if a tab has a custom color, a deselected state is - // signified by using the same color with a bit ot transparency + // signified by using the same color with a bit of transparency + // + // Prior to MUX 2.7, we set TabViewItemHeaderBackground, but now we can + // use TabViewItem().Background() for that. HOWEVER, + // TabViewItem().Background() only sets the color of the tab background + // when the TabViewItem is unselected. So we still need to set the other + // properties ourselves. + TabViewItem().Background(deselectedTabBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); - TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); + + // TabViewItem().Foreground() unfortunately does not work for us. It + // sets the color for the text when the TabViewItem isn't selected, but + // not when it is hovered, pressed, dragged, or selected, so we'll need + // to just set them all anyways. TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush); @@ -1442,7 +1453,6 @@ namespace winrt::TerminalApp::implementation void TerminalTab::_ClearTabBackgroundColor() { winrt::hstring keys[] = { - L"TabViewItemHeaderBackground", L"TabViewItemHeaderBackgroundSelected", L"TabViewItemHeaderBackgroundPointerOver", L"TabViewItemHeaderForeground", @@ -1463,6 +1473,9 @@ namespace winrt::TerminalApp::implementation } } + // Clear out the Background. + TabViewItem().Background(nullptr); + _RefreshVisualState(); _colorCleared(); } @@ -1487,7 +1500,7 @@ namespace winrt::TerminalApp::implementation // - void TerminalTab::_RefreshVisualState() { - if (_focusState != FocusState::Unfocused) + if (TabViewItem().IsSelected()) { VisualStateManager::GoToState(TabViewItem(), L"Normal", true); VisualStateManager::GoToState(TabViewItem(), L"Selected", true); diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 5f9d3f7c7..a5d865776 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -89,13 +89,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/src/cascadia/TerminalApp/packages.config b/src/cascadia/TerminalApp/packages.config index 9de6f1b8d..78467467c 100644 --- a/src/cascadia/TerminalApp/packages.config +++ b/src/cascadia/TerminalApp/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index e60fccb32..e4335126c 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -312,12 +312,12 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/packages.config b/src/cascadia/TerminalSettingsEditor/packages.config index ecf244764..bd1e02e27 100644 --- a/src/cascadia/TerminalSettingsEditor/packages.config +++ b/src/cascadia/TerminalSettingsEditor/packages.config @@ -1,5 +1,5 @@  - + diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 09988eb2e..3c56c6620 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -241,12 +241,13 @@ - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + x86 $(Platform) - <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\runtimes\win10-$(Native-Platform)\native\" + <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\runtimes\win10-$(Native-Platform)\native\" From 3b666b95177e9fad0af15adf7a64fba705afa900 Mon Sep 17 00:00:00 2001 From: Sergey <45919738+serd2011@users.noreply.github.com> Date: Tue, 21 Sep 2021 07:13:46 +0300 Subject: [PATCH 4/7] Fix selection render on paste (#11286) ## Summary of the Pull Request Clears selection render on paste ## PR Checklist * [x] Closes #11227 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA ## Detailed Description of the Pull Request / Additional comments Added ```_renderer->TriggerSelection(); ``` similarly to the copy action few lines up in ```CopySelectionToClipboard``` function ## Validation Steps Performed Manually tested --- src/cascadia/TerminalControl/ControlCore.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 0231c1acb..e09eb93ae 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -979,6 +979,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _terminal->WritePastedText(hstr); _terminal->ClearSelection(); + _renderer->TriggerSelection(); _terminal->TrySnapOnInput(); } From fbd50af8afb2d0bcdd7b4a252515c6d7fe729e84 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Tue, 21 Sep 2021 08:47:17 -0700 Subject: [PATCH 5/7] Change exit code to hex; Fix format spec (#11123) Process exit code now shows as hex not decimal. Format specification needs length "10" not "8" because the leading '0x' generated by the # symbol counts as part of the length. ## PR Checklist * [x] Closes annoyance at looking up process exit codes * [x] I work here. * [x] Checked manually ## Validation Steps Performed - [x] Ran it, opened tab, opened another CMD tab, ran `exit ` and observed hex pattern --- src/cascadia/TerminalConnection/ConptyConnection.cpp | 8 ++++++-- .../TerminalConnection/Resources/en-US/Resources.resw | 8 ++++---- .../TerminalConnection/TerminalConnection.vcxproj | 8 +++++--- .../TerminalConnection/TerminalConnection.vcxproj.filters | 1 + 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 5c2373fd2..b6ad2ce7f 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -16,6 +16,10 @@ #include "LibraryResources.h" using namespace ::Microsoft::Console; +using namespace std::string_view_literals; + +// Format is: "DecimalResult (HexadecimalForm)" +static constexpr auto _errorFormat = L"{0} ({0:#010x})"sv; // Notes: // There is a number of ways that the Conpty connection can be terminated (voluntarily or not): @@ -417,7 +421,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation const auto hr = wil::ResultFromCaughtException(); winrt::hstring failureText{ fmt::format(std::wstring_view{ RS_(L"ProcessFailedToLaunch") }, - gsl::narrow_cast(hr), + fmt::format(_errorFormat, hr), _commandline) }; _TerminalOutputHandlers(failureText); @@ -444,7 +448,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { try { - winrt::hstring exitText{ fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, status) }; + winrt::hstring exitText{ fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status)) }; _TerminalOutputHandlers(L"\r\n"); _TerminalOutputHandlers(exitText); } diff --git a/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw b/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw index d32f59269..7af3cf590 100644 --- a/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalConnection/Resources/en-US/Resources.resw @@ -205,15 +205,15 @@ [process exited with code {0}] - The first argument {0} is the (positive) error code of the process. When there is no error, the number ZERO will be displayed. + The first argument {0} is the error code of the process. When there is no error, the number ZERO will be displayed. - [error {0:#08x} when launching `{1}'] - The first argument {0...} is the hexadecimal error code. The second argument {1} is the user-specified path to a program. + [error {0} when launching `{1}'] + The first argument {0} is the error code. The second argument {1} is the user-specified path to a program. If this string is broken to multiple lines, it will not be displayed properly. Could not access starting directory "{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/TerminalConnection.vcxproj b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj index 5cfd57c40..f594bc46f 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj @@ -56,7 +56,9 @@ - + + Designer + @@ -88,11 +90,11 @@ - $(IntDir)..\OpenConsoleProxy;%(AdditionalIncludeDirectories) + $(IntDir)..\OpenConsoleProxy;%(AdditionalIncludeDirectories) $(OpenConsoleCommonOutDir)\conptylib.lib;%(AdditionalDependencies) - + \ No newline at end of file diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters index 12a9cc6c4..a7d2f9092 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters @@ -32,6 +32,7 @@ + From 591a67111e4fa2a8e0ab990914ae031df24476da Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Tue, 21 Sep 2021 17:21:45 -0400 Subject: [PATCH 6/7] Attempt to make the monarch more thread safe. (#11189) ## Summary of the Pull Request ## References #11083 #11143 ## PR Checklist * [ ] Closes #xxx * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Detailed Description of the Pull Request / Additional comments While testing the save/quit features a number of issues were found that were caused by poor synchronization on the monarch, resulting in various unexpected crashes. Because this uses std collections, and I didn't see any builtin winrt multithreaded containers I went with the somewhat heavy-handed mutex approach. e.g. - https://github.com/microsoft/terminal/pull/11083#issuecomment-916218353 - https://github.com/microsoft/terminal/pull/11083#issuecomment-916220521 - https://github.com/microsoft/terminal/pull/11143/#discussion_r704738433 This also makes it so that on quit peasants don't try to become the monarch, and the monarch closes their peasant last to prevent elections from happening. ## Validation Steps Performed Create many windows (hold down ctrl-shift-n) then use the quit action from peasants/the monarch to make sure everything closes properly. --- src/cascadia/Remoting/Monarch.cpp | 294 +++++++++++------- src/cascadia/Remoting/Monarch.h | 82 +++-- src/cascadia/Remoting/WindowManager.cpp | 8 + .../UnitTests_Remoting/RemotingTests.cpp | 39 +-- 4 files changed, 265 insertions(+), 158 deletions(-) diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index 9bf60fc8b..c16eb52e9 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -50,6 +50,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // - Add the given peasant to the list of peasants we're tracking. This // Peasant may have already been assigned an ID. If it hasn't, then give // it an ID. + // - NB: this takes a unique_lock on _peasantsMutex. // Arguments: // - peasant: the new Peasant to track. // Return Value: @@ -71,10 +72,24 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation { // Peasant already had an ID (from an older monarch). Leave that one // be. Make sure that the next peasant's ID is higher than it. - _nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID; + // If multiple peasants are added concurrently we keep trying to update + // until we get to set the new id. + uint64_t current; + do + { + current = _nextPeasantID.load(std::memory_order_relaxed); + } while (current <= providedID && !_nextPeasantID.compare_exchange_weak(current, providedID + 1, std::memory_order_relaxed)); } auto newPeasantsId = peasant.GetID(); + + // Keep track of which peasant we are + // SAFETY: this is only true for one peasant, and each peasant + // is only added to a monarch once, so we do not need synchronization here. + if (peasant.GetPID() == _ourPID) + { + _ourPeasantId = newPeasantsId; + } // Add an event listener to the peasant's WindowActivated event. peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated }); peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows }); @@ -84,7 +99,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation peasant.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); }); peasant.QuitAllRequested({ this, &Monarch::_handleQuitAll }); - _peasants[newPeasantsId] = peasant; + { + std::unique_lock lock{ _peasantsMutex }; + _peasants[newPeasantsId] = peasant; + } TraceLoggingWrite(g_hRemotingProvider, "Monarch_AddPeasant", @@ -124,9 +142,15 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // closing all windows. _QuitAllRequestedHandlers(*this, nullptr); + _quitting.store(true); // Tell all peasants to exit. - const auto callback = [&](const auto& /*id*/, const auto& p) { - p.Quit(); + const auto callback = [&](const auto& id, const auto& p) { + // We want to tell our peasant to quit last, so that we don't try + // to perform a bunch of elections on quit. + if (id != _ourPeasantId) + { + p.Quit(); + } }; const auto onError = [&](const auto& id) { TraceLoggingWrite(g_hRemotingProvider, @@ -137,18 +161,46 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation }; _forEachPeasant(callback, onError); + + { + std::shared_lock lock{ _peasantsMutex }; + const auto peasantSearch = _peasants.find(_ourPeasantId); + if (peasantSearch != _peasants.end()) + { + peasantSearch->second.Quit(); + } + else + { + // Somehow we don't have our own peasant, this should never happen. + // We are trying to quit anyways so just fail here. + assert(peasantSearch != _peasants.end()); + } + } } // Method Description: // - Tells the monarch that a peasant is being closed. + // - NB: this (separately) takes unique locks on _peasantsMutex and + // _mruPeasantsMutex. // Arguments: // - peasantId: the id of the peasant // Return Value: // - void Monarch::SignalClose(const uint64_t peasantId) { - _clearOldMruEntries(peasantId); - _peasants.erase(peasantId); + // If we are quitting we don't care about maintaining our list of + // peasants anymore, and don't need to notify the host that something + // changed. + if (_quitting.load(std::memory_order_acquire)) + { + return; + } + + _clearOldMruEntries({ peasantId }); + { + std::unique_lock lock{ _peasantsMutex }; + _peasants.erase(peasantId); + } _WindowClosedHandlers(nullptr, nullptr); } @@ -160,23 +212,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // - the number of active peasants. uint64_t Monarch::GetNumberOfPeasants() { - auto num = 0; - auto callback = [&](const auto& /*id*/, const auto& p) { - // Check that the peasant is alive, and if so increment the count - p.GetID(); - num += 1; - }; - auto onError = [](const auto& id) { - TraceLoggingWrite(g_hRemotingProvider, - "Monarch_GetNumberOfPeasants_Failed", - TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not enumerate"), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - }; - - _forEachPeasant(callback, onError); - - return num; + std::shared_lock lock{ _peasantsMutex }; + return _peasants.size(); } // Method Description: @@ -197,16 +234,25 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // Method Description: // - Lookup a peasant by its ID. If the peasant has died, this will also // remove the peasant from our list of peasants. + // - NB: this (separately) takes unique locks on _peasantsMutex and + // _mruPeasantsMutex. // Arguments: // - peasantID: The ID Of the peasant to find + // - clearMruPeasantOnFailure: When true this function will handle clearing + // from _mruPeasants if a peasant was not found, otherwise the caller is + // expected to handle that cleanup themselves. // Return Value: // - the peasant if it exists in our map, otherwise null - Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID) + Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID, bool clearMruPeasantOnFailure) { try { - const auto peasantSearch = _peasants.find(peasantID); - auto maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second; + IPeasant maybeThePeasant = nullptr; + { + std::shared_lock lock{ _peasantsMutex }; + const auto peasantSearch = _peasants.find(peasantID); + maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second; + } // Ask the peasant for their PID. This will validate that they're // actually still alive. if (maybeThePeasant) @@ -218,12 +264,19 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation catch (...) { LOG_CAUGHT_EXCEPTION(); - // Remove the peasant from the list of peasants - _peasants.erase(peasantID); - // Remove the peasant from the list of MRU windows. They're dead. - // They can't be the MRU anymore. - _clearOldMruEntries(peasantID); + // Remove the peasant from the list of peasants + { + std::unique_lock lock{ _peasantsMutex }; + _peasants.erase(peasantID); + } + + if (clearMruPeasantOnFailure) + { + // Remove the peasant from the list of MRU windows. They're dead. + // They can't be the MRU anymore. + _clearOldMruEntries({ peasantID }); + } return nullptr; } } @@ -244,39 +297,27 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation return 0; } - std::vector peasantsToErase{}; uint64_t result = 0; - for (const auto& [id, p] : _peasants) - { - try - { - auto otherName = p.WindowName(); - if (otherName == name) - { - result = id; - break; - } - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - // Normally, we'd just erase the peasant here. However, we can't - // erase from the map while we're iterating over it like this. - // Instead, pull a good ole Java and collect this id for removal - // later. - peasantsToErase.push_back(id); - } - } - // Remove the dead peasants we came across while iterating. - for (const auto& id : peasantsToErase) - { - // Remove the peasant from the list of peasants - _peasants.erase(id); - // Remove the peasant from the list of MRU windows. They're dead. - // They can't be the MRU anymore. - _clearOldMruEntries(id); - } + const auto callback = [&](const auto& id, const auto& p) { + auto otherName = p.WindowName(); + if (otherName == name) + { + result = id; + return false; + } + return true; + }; + + const auto onError = [&](const auto& id) { + TraceLoggingWrite(g_hRemotingProvider, + "Monarch_lookupPeasantIdForName_Failed", + TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not get the name of"), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + }; + + _forEachPeasant(callback, onError); TraceLoggingWrite(g_hRemotingProvider, "Monarch_lookupPeasantIdForName", @@ -328,33 +369,42 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // - Helper for removing a peasant from the list of MRU peasants. We want to // do this both when the peasant dies, and also when the peasant is newly // activated (so that we don't leave an old entry for it in the list). + // - NB: This takes a unique lock on _mruPeasantsMutex. // Arguments: - // - peasantID: The ID of the peasant to remove from the MRU list + // - peasantIds: The list of peasant IDs to remove from the MRU list // Return Value: // - - void Monarch::_clearOldMruEntries(const uint64_t peasantID) + void Monarch::_clearOldMruEntries(const std::unordered_set& peasantIds) { - auto result = std::find_if(_mruPeasants.begin(), - _mruPeasants.end(), - [peasantID](auto&& other) { - return peasantID == other.PeasantID(); - }); - - if (result != std::end(_mruPeasants)) + if (peasantIds.size() == 0) { - TraceLoggingWrite(g_hRemotingProvider, - "Monarch_RemovedPeasantFromDesktop", - TraceLoggingUInt64(peasantID, "peasantID", "The ID of the peasant"), - TraceLoggingGuid(result->DesktopID(), "desktopGuid", "The GUID of the previous desktop the window was on"), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - - _mruPeasants.erase(result); + return; } + + std::unique_lock lock{ _mruPeasantsMutex }; + auto partition = std::remove_if(_mruPeasants.begin(), _mruPeasants.end(), [&](const auto& p) { + const auto id = p.PeasantID(); + // remove the element if it was found in the list to erase. + if (peasantIds.count(id) == 1) + { + TraceLoggingWrite(g_hRemotingProvider, + "Monarch_RemovedPeasantFromDesktop", + TraceLoggingUInt64(id, "peasantID", "The ID of the peasant"), + TraceLoggingGuid(p.DesktopID(), "desktopGuid", "The GUID of the previous desktop the window was on"), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + return true; + } + return false; + }); + + // Remove everything that was in the list + _mruPeasants.erase(partition, _mruPeasants.end()); } // Method Description: // - Actually handle inserting the WindowActivatedArgs into our list of MRU windows. + // - NB: this takes a unique_lock on _mruPeasantsMutex. // Arguments: // - localArgs: an in-proc WindowActivatedArgs that we should add to our list of MRU windows. // Return Value: @@ -365,19 +415,22 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // * Check all the current lists to look for this peasant. // remove it from any where it exists. - _clearOldMruEntries(localArgs->PeasantID()); + _clearOldMruEntries({ localArgs->PeasantID() }); // * If the current desktop doesn't have a vector, add one. const auto desktopGuid{ localArgs->DesktopID() }; - // * Add this args list. By using lower_bound with insert, we can get it - // into exactly the right spot, without having to re-sort the whole - // array. - _mruPeasants.insert(std::lower_bound(_mruPeasants.begin(), - _mruPeasants.end(), - *localArgs, - [](const auto& first, const auto& second) { return first.ActivatedTime() > second.ActivatedTime(); }), - *localArgs); + { + std::unique_lock lock{ _mruPeasantsMutex }; + // * Add this args list. By using lower_bound with insert, we can get it + // into exactly the right spot, without having to re-sort the whole + // array. + _mruPeasants.insert(std::lower_bound(_mruPeasants.begin(), + _mruPeasants.end(), + *localArgs, + [](const auto& first, const auto& second) { return first.ActivatedTime() > second.ActivatedTime(); }), + *localArgs); + } TraceLoggingWrite(g_hRemotingProvider, "Monarch_SetMostRecentPeasant", @@ -391,6 +444,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // Method Description: // - Retrieves the ID of the MRU peasant window. If requested, will limit // the search to windows that are on the current desktop. + // - NB: This method will hold a shared lock on _mruPeasantsMutex and + // potentially a unique_lock on _peasantsMutex at the same time. + // Separately it might hold a unique_lock on _mruPeasantsMutex. // Arguments: // - limitToCurrentDesktop: if true, only return the MRU peasant that's // actually on the current desktop. @@ -403,8 +459,13 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // - the ID of the most recent peasant, otherwise 0 if we could not find one. uint64_t Monarch::_getMostRecentPeasantID(const bool limitToCurrentDesktop, const bool ignoreQuakeWindow) { + std::shared_lock lock{ _mruPeasantsMutex }; if (_mruPeasants.empty()) { + // unlock the mruPeasants mutex to make sure we can't deadlock here. + lock.unlock(); + // Only need a shared lock for read + std::shared_lock peasantsLock{ _peasantsMutex }; // We haven't yet been told the MRU peasant. Just use the first one. // This is just gonna be a random one, but really shouldn't happen // in practice. The WindowManager should set the MRU peasant @@ -445,15 +506,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // - If it isn't on the current desktop, we'll loop again, on the // following peasant. // * If we don't care, then we'll just return that one. - // - // We're not just using an iterator because the contents of the list - // might change while we're iterating here (if the peasant is dead we'll - // remove it from the list). - int positionInList = 0; - while (_mruPeasants.cbegin() + positionInList < _mruPeasants.cend()) + uint64_t result = 0; + std::unordered_set peasantsToErase{}; + for (const auto& mruWindowArgs : _mruPeasants) { - const auto mruWindowArgs{ *(_mruPeasants.begin() + positionInList) }; - const auto peasant{ _getPeasant(mruWindowArgs.PeasantID()) }; + // Try to get the peasant, but do not have _getPeasant clean up old + // _mruPeasants because we are iterating here. + // SAFETY: _getPeasant can take a unique_lock on _peasantsMutex if + // it detects a peasant is dead. Currently _getMostRecentPeasantId + // is the only method that holds a lock on both _mruPeasantsMutex and + // _peasantsMutex at the same time so there cannot be a deadlock here. + const auto peasant{ _getPeasant(mruWindowArgs.PeasantID(), false) }; if (!peasant) { TraceLoggingWrite(g_hRemotingProvider, @@ -467,6 +530,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // We'll go through the loop again. We removed the current one // at positionInList, so the next one in positionInList will be // a new, different peasant. + peasantsToErase.emplace(mruWindowArgs.PeasantID()); continue; } @@ -504,7 +568,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation "true if this window was in fact on the current desktop"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - return mruWindowArgs.PeasantID(); + result = mruWindowArgs.PeasantID(); + break; } // If this window wasn't on the current desktop, another one // might be. We'll increment positionInList below, and try @@ -518,20 +583,30 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - return mruWindowArgs.PeasantID(); + result = mruWindowArgs.PeasantID(); + break; } - positionInList++; } - // Here, we've checked all the windows, and none of them was both alive - // and the most recent (on this desktop). Just return 0 - the caller - // will use this to create a new window. - TraceLoggingWrite(g_hRemotingProvider, - "Monarch_getMostRecentPeasantID_NotFound", - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + lock.unlock(); - return 0; + if (peasantsToErase.size() > 0) + { + _clearOldMruEntries(peasantsToErase); + } + + if (result == 0) + { + // Here, we've checked all the windows, and none of them was both alive + // and the most recent (on this desktop). Just return 0 - the caller + // will use this to create a new window. + TraceLoggingWrite(g_hRemotingProvider, + "Monarch_getMostRecentPeasantID_NotFound", + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + } + + return result; } // Method Description: @@ -855,7 +930,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation Windows::Foundation::Collections::IVectorView Monarch::GetPeasantInfos() { std::vector names; - names.reserve(_peasants.size()); + { + std::shared_lock lock{ _peasantsMutex }; + names.reserve(_peasants.size()); + } const auto func = [&](const auto& id, const auto& p) -> void { names.push_back({ id, p.WindowName(), p.ActiveTabTitle() }); diff --git a/src/cascadia/Remoting/Monarch.h b/src/cascadia/Remoting/Monarch.h index 2e8e40aa1..7ec904499 100644 --- a/src/cascadia/Remoting/Monarch.h +++ b/src/cascadia/Remoting/Monarch.h @@ -7,6 +7,7 @@ #include "Peasant.h" #include "../cascadia/inc/cppwinrt_utils.h" #include "WindowActivatedArgs.h" +#include // We sure different GUIDs here depending on whether we're running a Release, // Preview, or Dev build. This ensures that different installs don't @@ -69,23 +70,29 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation private: uint64_t _ourPID; - uint64_t _nextPeasantID{ 1 }; - uint64_t _thisPeasantID{ 0 }; + std::atomic _nextPeasantID{ 1 }; + uint64_t _ourPeasantId{ 0 }; + + // When we're quitting we do not care as much about handling some events that we know will be triggered + std::atomic _quitting{ false }; winrt::com_ptr _desktopManager{ nullptr }; std::unordered_map _peasants; - std::vector _mruPeasants; + // These should not be locked at the same time to prevent deadlocks + // unless they are both shared_locks. + std::shared_mutex _peasantsMutex{}; + std::shared_mutex _mruPeasantsMutex{}; - winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID); + winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID, bool clearMruPeasantOnFailure = true); uint64_t _getMostRecentPeasantID(bool limitToCurrentDesktop, const bool ignoreQuakeWindow); uint64_t _lookupPeasantIdForName(std::wstring_view name); void _peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args); void _doHandleActivatePeasant(const winrt::com_ptr& args); - void _clearOldMruEntries(const uint64_t peasantID); + void _clearOldMruEntries(const std::unordered_set& peasantIds); void _forAllPeasantsIgnoringTheDead(std::function callback, std::function errorCallback); @@ -107,6 +114,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // returns false. // - If any single peasant is dead, then we'll call onError and then add it to a // list of peasants to clean up once the loop ends. + // - NB: this (separately) takes unique locks on _peasantsMutex and + // _mruPeasantsMutex. // Arguments: // - func: The function to call on each peasant // - onError: The function to call if a peasant is dead. @@ -119,44 +128,55 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation using R = std::invoke_result_t; static constexpr auto IsVoid = std::is_void_v; - std::vector peasantsToErase; - - for (const auto& [id, p] : _peasants) + std::unordered_set peasantsToErase; { - try + std::shared_lock lock{ _peasantsMutex }; + + for (const auto& [id, p] : _peasants) { - if constexpr (IsVoid) + try { - func(id, p); - } - else - { - if (!func(id, p)) + if constexpr (IsVoid) { - break; + func(id, p); + } + else + { + if (!func(id, p)) + { + break; + } } } - } - catch (const winrt::hresult_error& exception) - { - onError(id); + catch (const winrt::hresult_error& exception) + { + onError(id); - if (exception.code() == 0x800706ba) // The RPC server is unavailable. - { - peasantsToErase.emplace_back(id); - } - else - { - LOG_CAUGHT_EXCEPTION(); - throw; + if (exception.code() == 0x800706ba) // The RPC server is unavailable. + { + peasantsToErase.emplace(id); + } + else + { + LOG_CAUGHT_EXCEPTION(); + throw; + } } } } - for (const auto& id : peasantsToErase) + if (peasantsToErase.size() > 0) { - _peasants.erase(id); - _clearOldMruEntries(id); + // Don't hold a lock on _peasants and _mruPeasants at the same + // time to avoid deadlocks. + { + std::unique_lock lock{ _peasantsMutex }; + for (const auto& id : peasantsToErase) + { + _peasants.erase(id); + } + } + _clearOldMruEntries(peasantsToErase); } } diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index e3b85e0a2..9a71fcef0 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -324,6 +324,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + // If the peasant asks us to quit we should not try to act in future elections. + _peasant.QuitRequested([weakThis{ get_weak() }](auto&&, auto&&) { + if (auto wm = weakThis.get()) + { + wm->_monarchWaitInterrupt.SetEvent(); + } + }); + return _peasant; } diff --git a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp index ab45b444b..0a534e5a0 100644 --- a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp +++ b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp @@ -47,7 +47,8 @@ namespace RemotingUnitTests }; // This is a silly helper struct. - // It will always throw an hresult_error on any of its methods. + // It will always throw an hresult_error of "RPC server is unavailable" on any of its methods. + // The monarch uses this particular error code to check for a dead peasant vs another exception. // // In the tests, it's hard to emulate a peasant process being totally dead // once the Monarch has captured a reference to it. Since everything's @@ -59,24 +60,24 @@ namespace RemotingUnitTests struct DeadPeasant : implements { DeadPeasant() = default; - void AssignID(uint64_t /*id*/) { throw winrt::hresult_error{}; }; - uint64_t GetID() { throw winrt::hresult_error{}; }; - winrt::hstring WindowName() { throw winrt::hresult_error{}; }; - winrt::hstring ActiveTabTitle() { throw winrt::hresult_error{}; }; - void ActiveTabTitle(const winrt::hstring& /*value*/) { throw winrt::hresult_error{}; }; - uint64_t GetPID() { throw winrt::hresult_error{}; }; - bool ExecuteCommandline(const Remoting::CommandlineArgs& /*args*/) { throw winrt::hresult_error{}; } - void ActivateWindow(const Remoting::WindowActivatedArgs& /*args*/) { throw winrt::hresult_error{}; } - void RequestIdentifyWindows() { throw winrt::hresult_error{}; }; - void DisplayWindowId() { throw winrt::hresult_error{}; }; - Remoting::CommandlineArgs InitialArgs() { throw winrt::hresult_error{}; } - Remoting::WindowActivatedArgs GetLastActivatedArgs() { throw winrt::hresult_error{}; } - void RequestRename(const Remoting::RenameRequestArgs& /*args*/) { throw winrt::hresult_error{}; } - void Summon(const Remoting::SummonWindowBehavior& /*args*/) { throw winrt::hresult_error{}; }; - void RequestShowNotificationIcon() { throw winrt::hresult_error{}; }; - void RequestHideNotificationIcon() { throw winrt::hresult_error{}; }; - void RequestQuitAll() { throw winrt::hresult_error{}; }; - void Quit() { throw winrt::hresult_error{}; }; + void AssignID(uint64_t /*id*/) { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + uint64_t GetID() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + winrt::hstring WindowName() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + winrt::hstring ActiveTabTitle() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + void ActiveTabTitle(const winrt::hstring& /*value*/) { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + uint64_t GetPID() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + bool ExecuteCommandline(const Remoting::CommandlineArgs& /*args*/) { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); } + void ActivateWindow(const Remoting::WindowActivatedArgs& /*args*/) { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); } + void RequestIdentifyWindows() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + void DisplayWindowId() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + Remoting::CommandlineArgs InitialArgs() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); } + Remoting::WindowActivatedArgs GetLastActivatedArgs() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); } + void RequestRename(const Remoting::RenameRequestArgs& /*args*/) { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); } + void Summon(const Remoting::SummonWindowBehavior& /*args*/) { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + void RequestShowNotificationIcon() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + void RequestHideNotificationIcon() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + void RequestQuitAll() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; + void Quit() { throw winrt::hresult_error(winrt::hresult{ (int32_t)0x800706ba }); }; TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, Remoting::WindowActivatedArgs); TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, Remoting::CommandlineArgs); TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); From 168d28b036f8a8822b7400d2539514cb54a32184 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 22 Sep 2021 18:27:31 +0200 Subject: [PATCH 7/7] Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️ --- .github/actions/spelling/expect/expect.txt | 3 + README.md | 1 + .../ColorSchemeTests.cpp | 522 +++-- .../LocalTests_SettingsModel/CommandTests.cpp | 6 - .../DeserializationTests.cpp | 1799 ++++------------- .../LocalTests_SettingsModel/JsonTestClass.h | 30 +- .../KeyBindingsTests.cpp | 6 - .../LocalTests_SettingsModel/ProfileTests.cpp | 272 +-- .../SerializationTests.cpp | 142 +- .../TerminalSettingsTests.cpp | 168 +- .../LocalTests_TerminalApp/SettingsTests.cpp | 45 +- .../LocalTests_TerminalApp/TabTests.cpp | 24 +- src/cascadia/TerminalApp/AppLogic.cpp | 37 +- .../TerminalSettingsEditor/Launch.cpp | 1 - .../TerminalSettingsEditor/Launch.xaml | 2 +- .../AppearanceConfig.cpp | 39 +- .../TerminalSettingsModel/AppearanceConfig.h | 5 +- .../AzureCloudShellGenerator.cpp | 23 +- .../AzureCloudShellGenerator.h | 12 +- .../BaseVisualStudioGenerator.cpp | 35 +- .../BaseVisualStudioGenerator.h | 9 +- .../CascadiaSettings.cpp | 814 ++------ .../TerminalSettingsModel/CascadiaSettings.h | 228 +-- .../CascadiaSettings.idl | 19 +- .../CascadiaSettingsSerialization.cpp | 1687 ++++++---------- .../TerminalSettingsModel/ColorScheme.cpp | 109 +- .../TerminalSettingsModel/ColorScheme.h | 58 +- ...ofileUtils.cpp => DynamicProfileUtils.cpp} | 18 +- ...ltProfileUtils.h => DynamicProfileUtils.h} | 2 +- .../TerminalSettingsModel/FileUtils.cpp | 6 +- .../TerminalSettingsModel/FileUtils.h | 6 +- .../TerminalSettingsModel/FontConfig.cpp | 3 +- .../TerminalSettingsModel/FontConfig.h | 2 +- .../GlobalAppSettings.cpp | 156 +- .../TerminalSettingsModel/GlobalAppSettings.h | 35 +- .../GlobalAppSettings.idl | 1 + .../IDynamicProfileGenerator.h | 20 +- .../TerminalSettingsModel/IInheritable.h | 4 +- .../TerminalSettingsModel/JsonUtils.h | 2 +- ...crosoft.Terminal.Settings.ModelLib.vcxproj | 10 +- ...Terminal.Settings.ModelLib.vcxproj.filters | 6 +- .../PowershellCoreProfileGenerator.cpp | 43 +- .../PowershellCoreProfileGenerator.h | 11 +- .../TerminalSettingsModel/Profile.cpp | 326 +-- src/cascadia/TerminalSettingsModel/Profile.h | 18 +- .../TerminalSettingsModel/Profile.idl | 10 +- .../TerminalWarnings.idl | 27 +- .../VsDevCmdGenerator.cpp | 2 +- .../TerminalSettingsModel/VsDevCmdGenerator.h | 6 +- .../VsDevShellGenerator.cpp | 2 +- .../VsDevShellGenerator.h | 6 +- .../VsSetupConfiguration.cpp | 2 +- .../VsSetupConfiguration.h | 2 +- .../WslDistroGenerator.cpp | 55 +- .../WslDistroGenerator.h | 11 +- .../TerminalSettingsModel/userDefaults.json | 50 +- .../ControlInteractivityTests.cpp | 3 - src/cascadia/ut_app/DynamicProfileTests.cpp | 673 ------ src/cascadia/ut_app/JsonTests.cpp | 175 -- .../ut_app/TerminalApp.UnitTests.vcxproj | 4 +- .../ut_app/TestDynamicProfileGenerator.h | 44 - src/inc/DefaultSettings.h | 4 +- src/inc/til/color.h | 20 +- src/types/inc/utils.hpp | 2 +- src/types/utils.cpp | 6 +- tools/GenerateHeaderForJson.ps1 | 6 +- 66 files changed, 2335 insertions(+), 5540 deletions(-) rename src/cascadia/TerminalSettingsModel/{DefaultProfileUtils.cpp => DynamicProfileUtils.cpp} (50%) rename src/cascadia/TerminalSettingsModel/{DefaultProfileUtils.h => DynamicProfileUtils.h} (84%) delete mode 100644 src/cascadia/ut_app/DynamicProfileTests.cpp delete mode 100644 src/cascadia/ut_app/JsonTests.cpp delete mode 100644 src/cascadia/ut_app/TestDynamicProfileGenerator.h diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 8e83ab3e4..efd6edff8 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -557,6 +557,7 @@ DECSTR DECSWL DECTCEM Dedupe +deduplicate deduplicated DEFAPP DEFAULTBACKGROUND @@ -784,6 +785,7 @@ FINDSTRINGEXACT FINDUP FIter FIXEDCONVERTED +FIXEDFILEINFO Flg flyout fmodern @@ -1992,6 +1994,7 @@ resx retval rfa rfc +rfid rftp rgb rgba diff --git a/README.md b/README.md index 553c5aaef..ca416b82c 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,7 @@ If you would like to ask a question that you feel doesn't warrant an issue * You must [enable Developer Mode in the Windows Settings app](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) to locally install and run Windows Terminal +* You must have [PowerShell 7 or later](https://github.com/PowerShell/PowerShell/releases/latest) installed * You must have the [Windows 10 1903 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) installed diff --git a/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp b/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp index b4545ed85..85a9dd34a 100644 --- a/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp @@ -5,9 +5,11 @@ #include "../TerminalSettingsModel/ColorScheme.h" #include "../TerminalSettingsModel/CascadiaSettings.h" +#include "../types/inc/colorTable.hpp" #include "JsonTestClass.h" using namespace Microsoft::Console; +using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; using namespace WEX::Logging; using namespace WEX::TestExecution; @@ -32,339 +34,293 @@ namespace SettingsModelLocalTests TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") END_TEST_CLASS() - TEST_METHOD(CanLayerColorScheme); - TEST_METHOD(LayerColorSchemeProperties); + TEST_METHOD(ParseSimpleColorScheme); TEST_METHOD(LayerColorSchemesOnArray); TEST_METHOD(UpdateSchemeReferences); - TEST_CLASS_SETUP(ClassSetup) + static Core::Color rgb(uint8_t r, uint8_t g, uint8_t b) noexcept { - InitializeJsonReader(); - return true; + return Core::Color{ r, g, b, 255 }; } }; - void ColorSchemeTests::CanLayerColorScheme() + void ColorSchemeTests::ParseSimpleColorScheme() { - const std::string scheme0String{ R"({ - "name": "scheme0", - "foreground": "#000000", - "background": "#010101" - })" }; - const std::string scheme1String{ R"({ - "name": "scheme1", - "foreground": "#020202", - "background": "#030303" - })" }; - const std::string scheme2String{ R"({ - "name": "scheme0", - "foreground": "#040404", - "background": "#050505" - })" }; - const std::string scheme3String{ R"({ - // "name": "scheme3", - "foreground": "#060606", - "background": "#070707" - })" }; + const std::string campbellScheme{ "{" + "\"background\" : \"#0C0C0C\"," + "\"black\" : \"#0C0C0C\"," + "\"blue\" : \"#0037DA\"," + "\"brightBlack\" : \"#767676\"," + "\"brightBlue\" : \"#3B78FF\"," + "\"brightCyan\" : \"#61D6D6\"," + "\"brightGreen\" : \"#16C60C\"," + "\"brightPurple\" : \"#B4009E\"," + "\"brightRed\" : \"#E74856\"," + "\"brightWhite\" : \"#F2F2F2\"," + "\"brightYellow\" : \"#F9F1A5\"," + "\"cursorColor\" : \"#FFFFFF\"," + "\"cyan\" : \"#3A96DD\"," + "\"foreground\" : \"#F2F2F2\"," + "\"green\" : \"#13A10E\"," + "\"name\" : \"Campbell\"," + "\"purple\" : \"#881798\"," + "\"red\" : \"#C50F1F\"," + "\"selectionBackground\" : \"#131313\"," + "\"white\" : \"#CCCCCC\"," + "\"yellow\" : \"#C19C00\"" + "}" }; - const auto scheme0Json = VerifyParseSucceeded(scheme0String); - const auto scheme1Json = VerifyParseSucceeded(scheme1String); - const auto scheme2Json = VerifyParseSucceeded(scheme2String); - const auto scheme3Json = VerifyParseSucceeded(scheme3String); + const auto schemeObject = VerifyParseSucceeded(campbellScheme); + auto scheme = ColorScheme::FromJson(schemeObject); + VERIFY_ARE_EQUAL(L"Campbell", scheme->Name()); + VERIFY_ARE_EQUAL(til::color(0xf2, 0xf2, 0xf2, 255), til::color{ scheme->Foreground() }); + VERIFY_ARE_EQUAL(til::color(0x0c, 0x0c, 0x0c, 255), til::color{ scheme->Background() }); + VERIFY_ARE_EQUAL(til::color(0x13, 0x13, 0x13, 255), til::color{ scheme->SelectionBackground() }); + VERIFY_ARE_EQUAL(til::color(0xFF, 0xFF, 0xFF, 255), til::color{ scheme->CursorColor() }); - const auto scheme0 = ColorScheme::FromJson(scheme0Json); + std::array expectedCampbellTable; + const auto campbellSpan = gsl::make_span(expectedCampbellTable); + Utils::InitializeCampbellColorTable(campbellSpan); + Utils::SetColorTableAlpha(campbellSpan, 0); - VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme0Json)); - VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme1Json)); - VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme2Json)); - VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme3Json)); + for (size_t i = 0; i < expectedCampbellTable.size(); i++) + { + const auto& expected = expectedCampbellTable.at(i); + const til::color actual{ scheme->Table().at(static_cast(i)) }; + VERIFY_ARE_EQUAL(expected, actual); + } - const auto scheme1 = ColorScheme::FromJson(scheme1Json); - - VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme0Json)); - VERIFY_IS_TRUE(scheme1->ShouldBeLayered(scheme1Json)); - VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme2Json)); - VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme3Json)); - - const auto scheme3 = ColorScheme::FromJson(scheme3Json); - - VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme0Json)); - VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme1Json)); - VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme2Json)); - VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme3Json)); - } - - void ColorSchemeTests::LayerColorSchemeProperties() - { - const std::string scheme0String{ R"({ - "name": "scheme0", - "foreground": "#000000", - "background": "#010101", - "selectionBackground": "#010100", - "cursorColor": "#010001", - "red": "#010000", - "green": "#000100", - "blue": "#000001" - })" }; - const std::string scheme1String{ R"({ - "name": "scheme1", - "foreground": "#020202", - "background": "#030303", - "selectionBackground": "#020200", - "cursorColor": "#040004", - "red": "#020000", - - "blue": "#000002" - })" }; - const std::string scheme2String{ R"({ - "name": "scheme0", - "foreground": "#040404", - "background": "#050505", - "selectionBackground": "#030300", - "cursorColor": "#060006", - "red": "#030000", - "green": "#000300" - })" }; - - const auto scheme0Json = VerifyParseSucceeded(scheme0String); - const auto scheme1Json = VerifyParseSucceeded(scheme1String); - const auto scheme2Json = VerifyParseSucceeded(scheme2String); - - auto scheme0 = ColorScheme::FromJson(scheme0Json); - VERIFY_ARE_EQUAL(L"scheme0", scheme0->_Name); - VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background); - VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 0), scheme0->_SelectionBackground); - VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 1), scheme0->_CursorColor); - VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 0), scheme0->_table[XTERM_RED_ATTR]); - VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]); - VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 1), scheme0->_table[XTERM_BLUE_ATTR]); - - Log::Comment(NoThrowString().Format( - L"Layering scheme1 on top of scheme0")); - scheme0->LayerJson(scheme1Json); - - VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0->_Background); - VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 0), scheme0->_SelectionBackground); - VERIFY_ARE_EQUAL(ARGB(0, 4, 0, 4), scheme0->_CursorColor); - VERIFY_ARE_EQUAL(ARGB(0, 2, 0, 0), scheme0->_table[XTERM_RED_ATTR]); - VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]); - VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]); - - Log::Comment(NoThrowString().Format( - L"Layering scheme2Json on top of (scheme0+scheme1)")); - scheme0->LayerJson(scheme2Json); - - VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background); - VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 0), scheme0->_SelectionBackground); - VERIFY_ARE_EQUAL(ARGB(0, 6, 0, 6), scheme0->_CursorColor); - VERIFY_ARE_EQUAL(ARGB(0, 3, 0, 0), scheme0->_table[XTERM_RED_ATTR]); - VERIFY_ARE_EQUAL(ARGB(0, 0, 3, 0), scheme0->_table[XTERM_GREEN_ATTR]); - VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]); + Log::Comment(L"Roundtrip Test for Color Scheme"); + Json::Value outJson{ scheme->ToJson() }; + VERIFY_ARE_EQUAL(schemeObject, outJson); } void ColorSchemeTests::LayerColorSchemesOnArray() { - const std::string scheme0String{ R"({ - "name": "scheme0", - "foreground": "#000000", - "background": "#010101" + static constexpr std::string_view inboxSettings{ R"({ + "schemes": [ + { + "background": "#0C0C0C", + "black": "#0C0C0C", + "blue": "#0037DA", + "brightBlack": "#767676", + "brightBlue": "#3B78FF", + "brightCyan": "#61D6D6", + "brightGreen": "#16C60C", + "brightPurple": "#B4009E", + "brightRed": "#E74856", + "brightWhite": "#F2F2F2", + "brightYellow": "#F9F1A5", + "cursorColor": "#FFFFFF", + "cyan": "#3A96DD", + "foreground": "#CCCCCC", + "green": "#13A10E", + "name": "Campbell", + "purple": "#881798", + "red": "#C50F1F", + "selectionBackground": "#FFFFFF", + "white": "#CCCCCC", + "yellow": "#C19C00" + } + ] })" }; - const std::string scheme1String{ R"({ - "name": "scheme1", - "foreground": "#020202", - "background": "#030303" - })" }; - const std::string scheme2String{ R"({ - "name": "scheme0", - "foreground": "#040404", - "background": "#050505" - })" }; - const std::string scheme3String{ R"({ - // by not providing a name, the scheme will have the name "" - "foreground": "#060606", - "background": "#070707" + static constexpr std::string_view userSettings{ R"({ + "profiles": [ + { + "name" : "profile0" + } + ], + "schemes": [ + { + "background": "#121314", + "black": "#121314", + "blue": "#121314", + "brightBlack": "#121314", + "brightBlue": "#121314", + "brightCyan": "#121314", + "brightGreen": "#121314", + "brightPurple": "#121314", + "brightRed": "#121314", + "brightWhite": "#121314", + "brightYellow": "#121314", + "cursorColor": "#121314", + "cyan": "#121314", + "foreground": "#121314", + "green": "#121314", + "name": "Campbell", + "purple": "#121314", + "red": "#121314", + "selectionBackground": "#121314", + "white": "#121314", + "yellow": "#121314" + }, + { + "background": "#012456", + "black": "#0C0C0C", + "blue": "#0037DA", + "brightBlack": "#767676", + "brightBlue": "#3B78FF", + "brightCyan": "#61D6D6", + "brightGreen": "#16C60C", + "brightPurple": "#B4009E", + "brightRed": "#E74856", + "brightWhite": "#F2F2F2", + "brightYellow": "#F9F1A5", + "cursorColor": "#FFFFFF", + "cyan": "#3A96DD", + "foreground": "#CCCCCC", + "green": "#13A10E", + "name": "Campbell Powershell", + "purple": "#881798", + "red": "#C50F1F", + "selectionBackground": "#FFFFFF", + "white": "#CCCCCC", + "yellow": "#C19C00" + } + ] })" }; - const auto scheme0Json = VerifyParseSucceeded(scheme0String); - const auto scheme1Json = VerifyParseSucceeded(scheme1String); - const auto scheme2Json = VerifyParseSucceeded(scheme2String); - const auto scheme3Json = VerifyParseSucceeded(scheme3String); + const auto settings = winrt::make_self(userSettings, inboxSettings); - auto settings = winrt::make_self(); + const auto colorSchemes = settings->GlobalSettings().ColorSchemes(); + VERIFY_ARE_EQUAL(2u, colorSchemes.Size()); - VERIFY_ARE_EQUAL(0u, settings->_globals->ColorSchemes().Size()); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme0Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme2Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json)); + const auto scheme0 = winrt::get_self(colorSchemes.Lookup(L"Campbell")); + VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Foreground()); + VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Background()); - settings->_LayerOrCreateColorScheme(scheme0Json); - { - for (auto kv : settings->_globals->ColorSchemes()) - { - Log::Comment(NoThrowString().Format( - L"kv:%s->%s", kv.Key().data(), kv.Value().Name().data())); - } - VERIFY_ARE_EQUAL(1u, settings->_globals->ColorSchemes().Size()); - - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0")); - auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0"); - auto scheme0 = winrt::get_self(scheme0Proj); - - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json)); - VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background); - } - - settings->_LayerOrCreateColorScheme(scheme1Json); - - { - VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size()); - - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0")); - auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0"); - auto scheme0 = winrt::get_self(scheme0Proj); - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1")); - auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1"); - auto scheme1 = winrt::get_self(scheme1Proj); - - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json)); - VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background); - VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background); - } - settings->_LayerOrCreateColorScheme(scheme2Json); - - { - VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size()); - - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0")); - auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0"); - auto scheme0 = winrt::get_self(scheme0Proj); - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1")); - auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1"); - auto scheme1 = winrt::get_self(scheme1Proj); - - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json)); - VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background); - VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background); - } - settings->_LayerOrCreateColorScheme(scheme3Json); - - { - VERIFY_ARE_EQUAL(3u, settings->_globals->ColorSchemes().Size()); - - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0")); - auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0"); - auto scheme0 = winrt::get_self(scheme0Proj); - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1")); - auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1"); - auto scheme1 = winrt::get_self(scheme1Proj); - VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"")); - auto scheme2Proj = settings->_globals->ColorSchemes().Lookup(L""); - auto scheme2 = winrt::get_self(scheme2Proj); - - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json)); - VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json)); - VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background); - VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background); - VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2->_Foreground); - VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_Background); - } + const auto scheme1 = winrt::get_self(colorSchemes.Lookup(L"Campbell Powershell")); + VERIFY_ARE_EQUAL(rgb(0xCC, 0xCC, 0xCC), scheme1->Foreground()); + VERIFY_ARE_EQUAL(rgb(0x01, 0x24, 0x56), scheme1->Background()); } void ColorSchemeTests::UpdateSchemeReferences() { - const std::string settingsString{ R"json({ - "defaultProfile": "Inherited reference", - "profiles": { - "defaults": { - "colorScheme": "Scheme 1" - }, - "list": [ - { - "name": "Explicit scheme reference", - "colorScheme": "Scheme 1" - }, - { - "name": "Explicit reference; hidden", - "colorScheme": "Scheme 1", - "hidden": true - }, - { - "name": "Inherited reference" - }, - { - "name": "Different reference", - "colorScheme": "Scheme 2" - } - ] - }, - "schemes": [ - { "name": "Scheme 1" }, - { "name": "Scheme 2" }, - { "name": "Scheme 1 (renamed)" } - ] - })json" }; + static constexpr std::string_view settingsString{ R"json({ + "defaultProfile": "Inherited reference", + "profiles": { + "defaults": { + "colorScheme": "Campbell" + }, + "list": [ + { + "name": "Explicit scheme reference", + "colorScheme": "Campbell" + }, + { + "name": "Explicit reference; hidden", + "colorScheme": "Campbell", + "hidden": true + }, + { + "name": "Inherited reference" + }, + { + "name": "Different reference", + "colorScheme": "One Half Dark" + } + ] + }, + "schemes": [ + { + "background": "#0C0C0C", + "black": "#0C0C0C", + "blue": "#0037DA", + "brightBlack": "#767676", + "brightBlue": "#3B78FF", + "brightCyan": "#61D6D6", + "brightGreen": "#16C60C", + "brightPurple": "#B4009E", + "brightRed": "#E74856", + "brightWhite": "#F2F2F2", + "brightYellow": "#F9F1A5", + "cursorColor": "#FFFFFF", + "cyan": "#3A96DD", + "foreground": "#CCCCCC", + "green": "#13A10E", + "name": "Campbell", + "purple": "#881798", + "red": "#C50F1F", + "selectionBackground": "#FFFFFF", + "white": "#CCCCCC", + "yellow": "#C19C00" + }, + { + "background": "#0C0C0C", + "black": "#0C0C0C", + "blue": "#0037DA", + "brightBlack": "#767676", + "brightBlue": "#3B78FF", + "brightCyan": "#61D6D6", + "brightGreen": "#16C60C", + "brightPurple": "#B4009E", + "brightRed": "#E74856", + "brightWhite": "#F2F2F2", + "brightYellow": "#F9F1A5", + "cursorColor": "#FFFFFF", + "cyan": "#3A96DD", + "foreground": "#CCCCCC", + "green": "#13A10E", + "name": "Campbell (renamed)", + "purple": "#881798", + "red": "#C50F1F", + "selectionBackground": "#FFFFFF", + "white": "#CCCCCC", + "yellow": "#C19C00" + }, + { + "background": "#282C34", + "black": "#282C34", + "blue": "#61AFEF", + "brightBlack": "#5A6374", + "brightBlue": "#61AFEF", + "brightCyan": "#56B6C2", + "brightGreen": "#98C379", + "brightPurple": "#C678DD", + "brightRed": "#E06C75", + "brightWhite": "#DCDFE4", + "brightYellow": "#E5C07B", + "cursorColor": "#FFFFFF", + "cyan": "#56B6C2", + "foreground": "#DCDFE4", + "green": "#98C379", + "name": "One Half Dark", + "purple": "#C678DD", + "red": "#E06C75", + "selectionBackground": "#FFFFFF", + "white": "#DCDFE4", + "yellow": "#E5C07B" + } + ] + })json" }; - auto settings{ winrt::make_self(false) }; - settings->_ParseJsonString(settingsString, false); - settings->_ApplyDefaultsFromUserSettings(); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); + const auto settings{ winrt::make_self(settingsString) }; - // update all references to "Scheme 1" - const auto newName{ L"Scheme 1 (renamed)" }; - settings->UpdateColorSchemeReferences(L"Scheme 1", newName); + const auto newName{ L"Campbell (renamed)" }; + settings->UpdateColorSchemeReferences(L"Campbell", newName); - // verify profile defaults - Log::Comment(L"Profile Defaults"); VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().ColorSchemeName()); VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasColorSchemeName()); - // verify all other profiles const auto& profiles{ settings->AllProfiles() }; { const auto& prof{ profiles.GetAt(0) }; - Log::Comment(prof.Name().c_str()); VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName()); VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName()); } { const auto& prof{ profiles.GetAt(1) }; - Log::Comment(prof.Name().c_str()); VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName()); VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName()); } { const auto& prof{ profiles.GetAt(2) }; - Log::Comment(prof.Name().c_str()); VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName()); VERIFY_IS_FALSE(prof.DefaultAppearance().HasColorSchemeName()); } { const auto& prof{ profiles.GetAt(3) }; - Log::Comment(prof.Name().c_str()); - VERIFY_ARE_EQUAL(L"Scheme 2", prof.DefaultAppearance().ColorSchemeName()); + VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().ColorSchemeName()); VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName()); } } diff --git a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp b/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp index e8ab845b3..b25d476e7 100644 --- a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp @@ -43,12 +43,6 @@ namespace SettingsModelLocalTests TEST_METHOD(TestLayerOnAutogeneratedName); TEST_METHOD(TestGenerateCommandline); - - TEST_CLASS_SETUP(ClassSetup) - { - InitializeJsonReader(); - return true; - } }; void CommandTests::ManyCommandsSameAction() diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index df4fcb9a2..7f6638954 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -8,7 +8,6 @@ #include "JsonTestClass.h" #include "TestUtils.h" #include -#include "../ut_app/TestDynamicProfileGenerator.h" using namespace Microsoft::Console; using namespace WEX::Logging; @@ -44,61 +43,68 @@ namespace SettingsModelLocalTests TEST_METHOD(LayerGlobalProperties); TEST_METHOD(ValidateProfileOrdering); TEST_METHOD(ValidateHideProfiles); - TEST_METHOD(ValidateProfilesGenerateGuids); - TEST_METHOD(GeneratedGuidRoundtrips); - TEST_METHOD(TestAllValidationsWithNullGuids); TEST_METHOD(TestReorderWithNullGuids); TEST_METHOD(TestReorderingWithoutGuid); TEST_METHOD(TestLayeringNameOnlyProfiles); - TEST_METHOD(TestExplodingNameOnlyProfiles); TEST_METHOD(TestHideAllProfiles); TEST_METHOD(TestInvalidColorSchemeName); TEST_METHOD(TestHelperFunctions); - TEST_METHOD(TestProfileBackgroundImageWithEnvVar); TEST_METHOD(TestProfileBackgroundImageWithDesktopWallpaper); - TEST_METHOD(TestCloseOnExitParsing); TEST_METHOD(TestCloseOnExitCompatibilityShim); - TEST_METHOD(TestLayerUserDefaultsBeforeProfiles); TEST_METHOD(TestDontLayerGuidFromUserDefaults); TEST_METHOD(TestLayerUserDefaultsOnDynamics); - TEST_METHOD(FindMissingProfile); - TEST_METHOD(ValidateKeybindingsWarnings); - TEST_METHOD(ValidateColorSchemeInCommands); - TEST_METHOD(ValidateExecuteCommandlineWarning); - - TEST_METHOD(ValidateLegacyGlobalsWarning); - TEST_METHOD(TestTrailingCommas); - TEST_METHOD(TestCommandsAndKeybindings); - TEST_METHOD(TestNestedCommandWithoutName); TEST_METHOD(TestNestedCommandWithBadSubCommands); TEST_METHOD(TestUnbindNestedCommand); TEST_METHOD(TestRebindNestedCommand); - TEST_METHOD(TestCopy); TEST_METHOD(TestCloneInheritanceTree); - TEST_METHOD(TestValidDefaults); - TEST_METHOD(TestInheritedCommand); - TEST_CLASS_SETUP(ClassSetup) + private: + static winrt::com_ptr createSettings(const std::string_view& userJSON) { - InitializeJsonReader(); - return true; + static constexpr std::string_view inboxJSON{ R"({ + "schemes": [ + { + "name": "Campbell", + "foreground": "#CCCCCC", + "background": "#0C0C0C", + "cursorColor": "#FFFFFF", + "black": "#0C0C0C", + "red": "#C50F1F", + "green": "#13A10E", + "yellow": "#C19C00", + "blue": "#0037DA", + "purple": "#881798", + "cyan": "#3A96DD", + "white": "#CCCCCC", + "brightBlack": "#767676", + "brightRed": "#E74856", + "brightGreen": "#16C60C", + "brightYellow": "#F9F1A5", + "brightBlue": "#3B78FF", + "brightPurple": "#B4009E", + "brightCyan": "#61D6D6", + "brightWhite": "#F2F2F2" + } + ] + })" }; + + return winrt::make_self(userJSON, inboxJSON); } - private: - void _logCommandNames(winrt::Windows::Foundation::Collections::IMapView commands, const int indentation = 1) + static void _logCommandNames(winrt::Windows::Foundation::Collections::IMapView commands, const int indentation = 1) { if (indentation == 1) { @@ -125,7 +131,7 @@ namespace SettingsModelLocalTests void DeserializationTests::ValidateProfilesExist() { - const std::string settingsWithProfiles{ R"( + static constexpr std::string_view settingsWithProfiles{ R"( { "profiles": [ { @@ -134,30 +140,26 @@ namespace SettingsModelLocalTests ] })" }; - const std::string settingsWithoutProfiles{ R"( + static constexpr std::string_view settingsWithoutProfiles{ R"( { "defaultProfile": "{6239a42c-1de4-49a3-80bd-e8fdd045185c}" })" }; - const std::string settingsWithEmptyProfiles{ R"( + static constexpr std::string_view settingsWithEmptyProfiles{ R"( { "profiles": [] })" }; { // Case 1: Good settings - const auto settingsObject = VerifyParseSucceeded(settingsWithProfiles); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - settings->_ValidateProfilesExist(); + auto settings = winrt::make_self(settingsWithProfiles); } { // Case 2: Bad settings - const auto settingsObject = VerifyParseSucceeded(settingsWithoutProfiles); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); bool caughtExpectedException = false; try { - settings->_ValidateProfilesExist(); + auto settings = winrt::make_self(settingsWithoutProfiles); } catch (const implementation::SettingsException& ex) { @@ -168,12 +170,10 @@ namespace SettingsModelLocalTests } { // Case 3: Bad settings - const auto settingsObject = VerifyParseSucceeded(settingsWithEmptyProfiles); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); bool caughtExpectedException = false; try { - settings->_ValidateProfilesExist(); + auto settings = winrt::make_self(settingsWithEmptyProfiles); } catch (const implementation::SettingsException& ex) { @@ -186,7 +186,7 @@ namespace SettingsModelLocalTests void DeserializationTests::ValidateDefaultProfileExists() { - const std::string goodProfiles{ R"( + static constexpr std::string_view goodProfiles{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -201,7 +201,7 @@ namespace SettingsModelLocalTests ] })" }; - const std::string badProfiles{ R"( + static constexpr std::string_view badProfiles{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -216,22 +216,7 @@ namespace SettingsModelLocalTests ] })" }; - const std::string noDefaultAtAll{ R"( - { - "alwaysShowTabs": true, - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-5555-49a3-80bd-e8fdd045185c}" - }, - { - "name" : "profile1", - "guid": "{6239a42c-6666-49a3-80bd-e8fdd045185c}" - } - ] - })" }; - - const std::string goodProfilesSpecifiedByName{ R"( + static constexpr std::string_view goodProfilesSpecifiedByName{ R"( { "defaultProfile": "profile1", "profiles": [ @@ -250,87 +235,47 @@ namespace SettingsModelLocalTests // Case 1: Good settings Log::Comment(NoThrowString().Format( L"Testing a pair of profiles with unique guids, and the defaultProfile is one of those guids")); - const auto settingsObject = VerifyParseSucceeded(goodProfiles); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - settings->_ResolveDefaultProfile(); - settings->_ValidateDefaultProfileExists(); - VERIFY_ARE_EQUAL(static_cast(0), settings->_warnings.Size()); - VERIFY_ARE_EQUAL(static_cast(2), settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), settings->_allProfiles.GetAt(0).Guid()); + const auto settings = createSettings(goodProfiles); + VERIFY_ARE_EQUAL(static_cast(0), settings->Warnings().Size()); + VERIFY_ARE_EQUAL(static_cast(2), settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), settings->AllProfiles().GetAt(0).Guid()); } { // Case 2: Bad settings Log::Comment(NoThrowString().Format( L"Testing a pair of profiles with unique guids, but the defaultProfile is NOT one of those guids")); - const auto settingsObject = VerifyParseSucceeded(badProfiles); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - settings->_ResolveDefaultProfile(); - settings->_ValidateDefaultProfileExists(); - VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.GetAt(0)); + const auto settings = createSettings(badProfiles); + VERIFY_ARE_EQUAL(static_cast(1), settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingDefaultProfile, settings->Warnings().GetAt(0)); - VERIFY_ARE_EQUAL(static_cast(2), settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), settings->_allProfiles.GetAt(0).Guid()); + VERIFY_ARE_EQUAL(static_cast(2), settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), settings->AllProfiles().GetAt(0).Guid()); } { // Case 2: Bad settings Log::Comment(NoThrowString().Format( L"Testing a pair of profiles with unique guids, and no defaultProfile at all")); - const auto settingsObject = VerifyParseSucceeded(badProfiles); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - settings->_ResolveDefaultProfile(); - settings->_ValidateDefaultProfileExists(); - VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.GetAt(0)); + const auto settings = createSettings(badProfiles); + VERIFY_ARE_EQUAL(static_cast(1), settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingDefaultProfile, settings->Warnings().GetAt(0)); - VERIFY_ARE_EQUAL(static_cast(2), settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), settings->_allProfiles.GetAt(0).Guid()); + VERIFY_ARE_EQUAL(static_cast(2), settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), settings->AllProfiles().GetAt(0).Guid()); } { // Case 4: Good settings, default profile is a string Log::Comment(NoThrowString().Format( L"Testing a pair of profiles with unique guids, and the defaultProfile is one of the profile names")); - const auto settingsObject = VerifyParseSucceeded(goodProfilesSpecifiedByName); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - settings->_ResolveDefaultProfile(); - settings->_ValidateDefaultProfileExists(); - VERIFY_ARE_EQUAL(static_cast(0), settings->_warnings.Size()); - VERIFY_ARE_EQUAL(static_cast(2), settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), settings->_allProfiles.GetAt(1).Guid()); + const auto settings = createSettings(goodProfilesSpecifiedByName); + VERIFY_ARE_EQUAL(static_cast(0), settings->Warnings().Size()); + VERIFY_ARE_EQUAL(static_cast(2), settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), settings->AllProfiles().GetAt(1).Guid()); } } void DeserializationTests::ValidateDuplicateProfiles() { - const std::string goodProfiles{ R"( - { - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" - }, - { - "name" : "profile0", - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" - } - ] - })" }; - - const std::string badProfiles{ R"( - { - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" - }, - { - "name" : "profile1", - "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" - } - ] - })" }; - - const std::string veryBadProfiles{ R"( + static constexpr std::string_view veryBadProfiles{ R"( { "profiles": [ { @@ -363,82 +308,22 @@ namespace SettingsModelLocalTests } ] })" }; - Profile profile0 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-4444-49a3-80bd-e8fdd045185c}")); - profile0.Name(L"profile0"); - Profile profile1 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-5555-49a3-80bd-e8fdd045185c}")); - profile1.Name(L"profile1"); - Profile profile2 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-4444-49a3-80bd-e8fdd045185c}")); - profile2.Name(L"profile2"); - Profile profile3 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-4444-49a3-80bd-e8fdd045185c}")); - profile3.Name(L"profile3"); - Profile profile4 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-6666-49a3-80bd-e8fdd045185c}")); - profile4.Name(L"profile4"); - Profile profile5 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-5555-49a3-80bd-e8fdd045185c}")); - profile5.Name(L"profile5"); - Profile profile6 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-7777-49a3-80bd-e8fdd045185c}")); - profile6.Name(L"profile6"); - { - // Case 1: Good settings - Log::Comment(NoThrowString().Format( - L"Testing a pair of profiles with unique guids")); + const auto settings = createSettings(veryBadProfiles); - auto settings = winrt::make_self(); - settings->_allProfiles.Append(profile0); - settings->_allProfiles.Append(profile1); + VERIFY_ARE_EQUAL(static_cast(1), settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::DuplicateProfile, settings->Warnings().GetAt(0)); - settings->_ValidateNoDuplicateProfiles(); - - VERIFY_ARE_EQUAL(static_cast(0), settings->_warnings.Size()); - VERIFY_ARE_EQUAL(static_cast(2), settings->_allProfiles.Size()); - } - { - // Case 2: Bad settings - Log::Comment(NoThrowString().Format( - L"Testing a pair of profiles with the same guid")); - - auto settings = winrt::make_self(); - settings->_allProfiles.Append(profile2); - settings->_allProfiles.Append(profile3); - - settings->_ValidateNoDuplicateProfiles(); - - VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::DuplicateProfile, settings->_warnings.GetAt(0)); - - VERIFY_ARE_EQUAL(static_cast(1), settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(0).Name()); - } - { - // Case 3: Very bad settings - Log::Comment(NoThrowString().Format( - L"Testing a set of profiles, many of which with duplicated guids")); - - auto settings = winrt::make_self(); - settings->_allProfiles.Append(profile0); - settings->_allProfiles.Append(profile1); - settings->_allProfiles.Append(profile2); - settings->_allProfiles.Append(profile3); - settings->_allProfiles.Append(profile4); - settings->_allProfiles.Append(profile5); - settings->_allProfiles.Append(profile6); - - settings->_ValidateNoDuplicateProfiles(); - - VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::DuplicateProfile, settings->_warnings.GetAt(0)); - - VERIFY_ARE_EQUAL(static_cast(4), settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"profile6", settings->_allProfiles.GetAt(3).Name()); - } + VERIFY_ARE_EQUAL(static_cast(4), settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"profile1", settings->AllProfiles().GetAt(1).Name()); + VERIFY_ARE_EQUAL(L"profile4", settings->AllProfiles().GetAt(2).Name()); + VERIFY_ARE_EQUAL(L"profile6", settings->AllProfiles().GetAt(3).Name()); } void DeserializationTests::ValidateManyWarnings() { - const std::string badProfiles{ R"( + static constexpr std::string_view badProfiles{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -453,72 +338,56 @@ namespace SettingsModelLocalTests { "name" : "profile2", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile3", + "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile4", + "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" } ] })" }; - Profile profile4 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-4444-49a3-80bd-e8fdd045185c}")); - profile4.Name(L"profile4"); - Profile profile5 = winrt::make(::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-4444-49a3-80bd-e8fdd045185c}")); - profile5.Name(L"profile5"); - // Case 2: Bad settings - Log::Comment(NoThrowString().Format( - L"Testing a pair of profiles with the same guid")); - const auto settingsObject = VerifyParseSucceeded(badProfiles); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); + const auto settings = createSettings(badProfiles); - settings->_allProfiles.Append(profile4); - settings->_allProfiles.Append(profile5); + VERIFY_ARE_EQUAL(2u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::DuplicateProfile, settings->Warnings().GetAt(0)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingDefaultProfile, settings->Warnings().GetAt(1)); - settings->_ValidateSettings(); - - VERIFY_ARE_EQUAL(3u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::DuplicateProfile, settings->_warnings.GetAt(0)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.GetAt(1)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::UnknownColorScheme, settings->_warnings.GetAt(2)); - - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), settings->_allProfiles.GetAt(0).Guid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); + VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->AllProfiles().GetAt(0).Guid(), settings->GlobalSettings().DefaultProfile()); } void DeserializationTests::LayerGlobalProperties() { - const std::string settings0String{ R"( - { + static constexpr std::string_view inboxSettings{ R"({ "alwaysShowTabs": true, "initialCols" : 120, "initialRows" : 30 })" }; - const std::string settings1String{ R"( - { + static constexpr std::string_view userSettings{ R"({ "showTabsInTitlebar": false, "initialCols" : 240, - "initialRows" : 60 + "initialRows" : 60, + "profiles": [ + { + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}" + } + ] })" }; - const auto settings0Json = VerifyParseSucceeded(settings0String); - const auto settings1Json = VerifyParseSucceeded(settings1String); - auto settings = winrt::make_self(); - - settings->LayerJson(settings0Json); - VERIFY_ARE_EQUAL(true, settings->_globals->AlwaysShowTabs()); - VERIFY_ARE_EQUAL(120, settings->_globals->InitialCols()); - VERIFY_ARE_EQUAL(30, settings->_globals->InitialRows()); - VERIFY_ARE_EQUAL(true, settings->_globals->ShowTabsInTitlebar()); - - settings->LayerJson(settings1Json); - VERIFY_ARE_EQUAL(true, settings->_globals->AlwaysShowTabs()); - VERIFY_ARE_EQUAL(240, settings->_globals->InitialCols()); - VERIFY_ARE_EQUAL(60, settings->_globals->InitialRows()); - VERIFY_ARE_EQUAL(false, settings->_globals->ShowTabsInTitlebar()); + const auto settings = winrt::make_self(userSettings, inboxSettings); + VERIFY_ARE_EQUAL(true, settings->GlobalSettings().AlwaysShowTabs()); + VERIFY_ARE_EQUAL(240, settings->GlobalSettings().InitialCols()); + VERIFY_ARE_EQUAL(60, settings->GlobalSettings().InitialRows()); + VERIFY_ARE_EQUAL(false, settings->GlobalSettings().ShowTabsInTitlebar()); } void DeserializationTests::ValidateProfileOrdering() { - const std::string userProfiles0String{ R"( + static constexpr std::string_view userProfiles0String{ R"( { "profiles": [ { @@ -532,7 +401,7 @@ namespace SettingsModelLocalTests ] })" }; - const std::string defaultProfilesString{ R"( + static constexpr std::string_view defaultProfilesString{ R"( { "profiles": [ { @@ -546,7 +415,7 @@ namespace SettingsModelLocalTests ] })" }; - const std::string userProfiles1String{ R"( + static constexpr std::string_view userProfiles1String{ R"( { "profiles": [ { @@ -560,63 +429,32 @@ namespace SettingsModelLocalTests ] })" }; - const auto userProfiles0Json = VerifyParseSucceeded(userProfiles0String); - const auto userProfiles1Json = VerifyParseSucceeded(userProfiles1String); - const auto defaultProfilesJson = VerifyParseSucceeded(defaultProfilesString); - { Log::Comment(NoThrowString().Format( L"Case 1: Simple swapping of the ordering. The user has the " L"default profiles in the opposite order of the default ordering.")); - auto settings = winrt::make_self(); - settings->_ParseJsonString(defaultProfilesString, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(1).Name()); - - settings->_ParseJsonString(userProfiles0String, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(1).Name()); - - settings->_ReorderProfilesToMatchUserSettingsOrder(); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(1).Name()); + const auto settings = winrt::make_self(userProfiles0String, defaultProfilesString); + VERIFY_ARE_EQUAL(2u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"profile1", settings->AllProfiles().GetAt(1).Name()); } { Log::Comment(NoThrowString().Format( L"Case 2: Make sure all the user's profiles appear before the defaults.")); - auto settings = winrt::make_self(); - settings->_ParseJsonString(defaultProfilesString, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(1).Name()); - - settings->_ParseJsonString(userProfiles1String, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile5", settings->_allProfiles.GetAt(2).Name()); - - settings->_ReorderProfilesToMatchUserSettingsOrder(); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile5", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(2).Name()); + const auto settings = winrt::make_self(userProfiles1String, defaultProfilesString); + VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(L"profile4", settings->AllProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"profile5", settings->AllProfiles().GetAt(1).Name()); + VERIFY_ARE_EQUAL(L"profile2", settings->AllProfiles().GetAt(2).Name()); } } void DeserializationTests::ValidateHideProfiles() { - const std::string defaultProfilesString{ R"( + static constexpr std::string_view defaultProfilesString{ R"( { "profiles": [ { @@ -630,7 +468,7 @@ namespace SettingsModelLocalTests ] })" }; - const std::string userProfiles0String{ R"( + static constexpr std::string_view userProfiles0String{ R"( { "profiles": [ { @@ -645,7 +483,7 @@ namespace SettingsModelLocalTests ] })" }; - const std::string userProfiles1String{ R"( + static constexpr std::string_view userProfiles1String{ R"( { "profiles": [ { @@ -665,237 +503,28 @@ namespace SettingsModelLocalTests ] })" }; - const auto userProfiles0Json = VerifyParseSucceeded(userProfiles0String); - const auto userProfiles1Json = VerifyParseSucceeded(userProfiles1String); - const auto defaultProfilesJson = VerifyParseSucceeded(defaultProfilesString); - { - auto settings = winrt::make_self(); - settings->_ParseJsonString(defaultProfilesString, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(false, settings->_allProfiles.GetAt(0).Hidden()); - VERIFY_ARE_EQUAL(false, settings->_allProfiles.GetAt(1).Hidden()); - - settings->_ParseJsonString(userProfiles0String, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(false, settings->_allProfiles.GetAt(0).Hidden()); - VERIFY_ARE_EQUAL(true, settings->_allProfiles.GetAt(1).Hidden()); - - settings->_ReorderProfilesToMatchUserSettingsOrder(); - settings->_UpdateActiveProfiles(); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(1u, settings->_activeProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile1", settings->_activeProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(false, settings->_activeProfiles.GetAt(0).Hidden()); + const auto settings = winrt::make_self(userProfiles0String, defaultProfilesString); + VERIFY_ARE_EQUAL(2u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(1u, settings->ActiveProfiles().Size()); + VERIFY_ARE_EQUAL(L"profile1", settings->ActiveProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(false, settings->ActiveProfiles().GetAt(0).Hidden()); } { - auto settings = winrt::make_self(); - settings->_ParseJsonString(defaultProfilesString, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(false, settings->_allProfiles.GetAt(0).Hidden()); - VERIFY_ARE_EQUAL(false, settings->_allProfiles.GetAt(1).Hidden()); - - settings->_ParseJsonString(userProfiles1String, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile5", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"profile6", settings->_allProfiles.GetAt(3).Name()); - VERIFY_ARE_EQUAL(false, settings->_allProfiles.GetAt(0).Hidden()); - VERIFY_ARE_EQUAL(true, settings->_allProfiles.GetAt(1).Hidden()); - VERIFY_ARE_EQUAL(false, settings->_allProfiles.GetAt(2).Hidden()); - VERIFY_ARE_EQUAL(true, settings->_allProfiles.GetAt(3).Hidden()); - - settings->_ReorderProfilesToMatchUserSettingsOrder(); - settings->_UpdateActiveProfiles(); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(2u, settings->_activeProfiles.Size()); - VERIFY_ARE_EQUAL(L"profile5", settings->_activeProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile2", settings->_activeProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(false, settings->_activeProfiles.GetAt(0).Hidden()); - VERIFY_ARE_EQUAL(false, settings->_activeProfiles.GetAt(1).Hidden()); + const auto settings = winrt::make_self(userProfiles1String, defaultProfilesString); + VERIFY_ARE_EQUAL(4u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(2u, settings->ActiveProfiles().Size()); + VERIFY_ARE_EQUAL(L"profile5", settings->ActiveProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"profile2", settings->ActiveProfiles().GetAt(1).Name()); + VERIFY_ARE_EQUAL(false, settings->ActiveProfiles().GetAt(0).Hidden()); + VERIFY_ARE_EQUAL(false, settings->ActiveProfiles().GetAt(1).Hidden()); } } - void DeserializationTests::ValidateProfilesGenerateGuids() - { - const std::string profile0String{ R"( - { - "name" : "profile0" - })" }; - const std::string profile1String{ R"( - { - "name" : "profile1" - })" }; - const std::string profile2String{ R"( - { - "name" : "profile2", - "guid" : null - })" }; - const std::string profile3String{ R"( - { - "name" : "profile3", - "guid" : "{00000000-0000-0000-0000-000000000000}" - })" }; - const std::string profile4String{ R"( - { - "name" : "profile4", - "guid" : "{6239a42c-1de4-49a3-80bd-e8fdd045185c}" - })" }; - const std::string profile5String{ R"( - { - "name" : "profile2" - })" }; - - const auto profile0Json = VerifyParseSucceeded(profile0String); - const auto profile1Json = VerifyParseSucceeded(profile1String); - const auto profile2Json = VerifyParseSucceeded(profile2String); - const auto profile3Json = VerifyParseSucceeded(profile3String); - const auto profile4Json = VerifyParseSucceeded(profile4String); - const auto profile5Json = VerifyParseSucceeded(profile5String); - - const auto profile0 = implementation::Profile::FromJson(profile0Json); - const auto profile1 = implementation::Profile::FromJson(profile1Json); - const auto profile2 = implementation::Profile::FromJson(profile2Json); - const auto profile3 = implementation::Profile::FromJson(profile3Json); - const auto profile4 = implementation::Profile::FromJson(profile4Json); - const auto profile5 = implementation::Profile::FromJson(profile5Json); - - const winrt::guid cmdGuid{ Utils::GuidFromString(L"{6239a42c-1de4-49a3-80bd-e8fdd045185c}") }; - const winrt::guid nullGuid{}; - - VERIFY_IS_FALSE(profile0->HasGuid()); - VERIFY_IS_FALSE(profile1->HasGuid()); - VERIFY_IS_FALSE(profile2->HasGuid()); - VERIFY_IS_TRUE(profile3->HasGuid()); - VERIFY_IS_TRUE(profile4->HasGuid()); - VERIFY_IS_FALSE(profile5->HasGuid()); - - VERIFY_ARE_EQUAL(profile3->Guid(), nullGuid); - VERIFY_ARE_EQUAL(profile4->Guid(), cmdGuid); - - auto settings = winrt::make_self(); - settings->_allProfiles.Append(*profile0); - settings->_allProfiles.Append(*profile1); - settings->_allProfiles.Append(*profile2); - settings->_allProfiles.Append(*profile3); - settings->_allProfiles.Append(*profile4); - settings->_allProfiles.Append(*profile5); - - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(4).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(5).HasGuid()); - - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(), nullGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(1).Guid(), nullGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(2).Guid(), nullGuid); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(3).Guid(), nullGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(4).Guid(), nullGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(5).Guid(), nullGuid); - - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(), cmdGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(1).Guid(), cmdGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(2).Guid(), cmdGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(3).Guid(), cmdGuid); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(4).Guid(), cmdGuid); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(5).Guid(), cmdGuid); - - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(), settings->_allProfiles.GetAt(2).Guid()); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(1).Guid(), settings->_allProfiles.GetAt(2).Guid()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(2).Guid(), settings->_allProfiles.GetAt(2).Guid()); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(3).Guid(), settings->_allProfiles.GetAt(2).Guid()); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(4).Guid(), settings->_allProfiles.GetAt(2).Guid()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(5).Guid(), settings->_allProfiles.GetAt(2).Guid()); - } - - void DeserializationTests::GeneratedGuidRoundtrips() - { - // Parse a profile without a guid. - // We should automatically generate a GUID for that profile. - // When that profile is serialized and deserialized again, the GUID we - // generated for it should persist. - const std::string profileWithoutGuid{ R"({ - "name" : "profile0" - })" }; - const auto profile0Json = VerifyParseSucceeded(profileWithoutGuid); - - const auto profile0 = implementation::Profile::FromJson(profile0Json); - const GUID nullGuid{ 0 }; - - VERIFY_IS_FALSE(profile0->HasGuid()); - - const auto serialized0Profile = profile0->GenerateStub(); - const auto profile1 = implementation::Profile::FromJson(serialized0Profile); - VERIFY_IS_FALSE(profile0->HasGuid()); - VERIFY_IS_TRUE(profile1->HasGuid()); - - auto settings = winrt::make_self(); - settings->_allProfiles.Append(*profile1); - - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - - const auto profileImpl = winrt::get_self(settings->_allProfiles.GetAt(0)); - const auto serialized1Profile = profileImpl->GenerateStub(); - - const auto profile2 = implementation::Profile::FromJson(serialized1Profile); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(profile2->HasGuid()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(0).Guid(), profile2->Guid()); - } - - void DeserializationTests::TestAllValidationsWithNullGuids() - { - const std::string settings0String{ R"( - { - "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "profiles": [ - { - "name" : "profile0", - "guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}" - }, - { - "name" : "profile1" - } - ], - "schemes": [ - { "name": "Campbell" } - ] - })" }; - - const auto settings0Json = VerifyParseSucceeded(settings0String); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - - settings->_ValidateSettings(); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - } - void DeserializationTests::TestReorderWithNullGuids() { - const std::string settings0String{ R"( + static constexpr std::string_view settings0String{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -913,41 +542,18 @@ namespace SettingsModelLocalTests ] })" }; - const auto settings0Json = VerifyParseSucceeded(settings0String); + const auto settings = winrt::make_self(settings0String, DefaultJson); - auto settings = winrt::make_self(); - settings->_ParseJsonString(DefaultJson, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(1).Name()); - - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"cmdFromUserSettings", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(3).Name()); - - settings->_ValidateSettings(); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"cmdFromUserSettings", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(3).Name()); + VERIFY_ARE_EQUAL(0u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(4u, settings->AllProfiles().Size()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(0).HasGuid()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(1).HasGuid()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(2).HasGuid()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(3).HasGuid()); + VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"profile1", settings->AllProfiles().GetAt(1).Name()); + VERIFY_ARE_EQUAL(L"cmdFromUserSettings", settings->AllProfiles().GetAt(2).Name()); + VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->AllProfiles().GetAt(3).Name()); } void DeserializationTests::TestReorderingWithoutGuid() @@ -961,7 +567,7 @@ namespace SettingsModelLocalTests L" about this scenario specifically that causes a crash, when " L" TestReorderWithNullGuids did _not_.")); - const std::string settings0String{ R"( + static constexpr std::string_view settings0String{ R"( { "defaultProfile" : "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", "profiles": [ @@ -1014,41 +620,18 @@ namespace SettingsModelLocalTests ] })" }; - const auto settings0Json = VerifyParseSucceeded(settings0String); + const auto settings = winrt::make_self(settings0String, DefaultJson); - auto settings = winrt::make_self(); - settings->_ParseJsonString(DefaultJson, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(1).Name()); - - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileShouldNotCrash", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"Ubuntu", settings->_allProfiles.GetAt(3).Name()); - - settings->_ValidateSettings(); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileShouldNotCrash", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"Ubuntu", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(3).Name()); + VERIFY_ARE_EQUAL(0u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(4u, settings->AllProfiles().Size()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(0).HasGuid()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(1).HasGuid()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(2).HasGuid()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(3).HasGuid()); + VERIFY_ARE_EQUAL(L"Command Prompt", settings->AllProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"ThisProfileShouldNotCrash", settings->AllProfiles().GetAt(1).Name()); + VERIFY_ARE_EQUAL(L"Ubuntu", settings->AllProfiles().GetAt(2).Name()); + VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->AllProfiles().GetAt(3).Name()); } void DeserializationTests::TestLayeringNameOnlyProfiles() @@ -1057,14 +640,13 @@ namespace SettingsModelLocalTests // profile, it should only layer with other name-only profiles with the // _same name_ - const std::string settings0String{ R"( + static constexpr std::string_view settings0String{ R"( { "defaultProfile" : "{00000000-0000-5f56-a8ff-afceeeaa6101}", "profiles": [ { "guid" : "{00000000-0000-5f56-a8ff-afceeeaa6101}", "name" : "ThisProfileIsGood" - }, { "name" : "ThisProfileShouldNotLayer" @@ -1075,141 +657,19 @@ namespace SettingsModelLocalTests ] })" }; - const auto settings0Json = VerifyParseSucceeded(settings0String); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(DefaultJson, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(1).Name()); - - Log::Comment(NoThrowString().Format( - L"Parse the user settings")); - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(5u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(4).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileShouldNotLayer", settings->_allProfiles.GetAt(3).Name()); - VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings->_allProfiles.GetAt(4).Name()); - } - - void DeserializationTests::TestExplodingNameOnlyProfiles() - { - // This is a test for GH#2782. When we add a name-only profile, we'll - // generate a GUID for it. We should make sure that we don't re-append - // that profile to the list of profiles. - - const std::string settings0String{ R"( - { - "defaultProfile" : "{00000000-0000-5f56-a8ff-afceeeaa6101}", - "profiles": [ - { - "guid" : "{00000000-0000-5f56-a8ff-afceeeaa6101}", - "name" : "ThisProfileIsGood" - - }, - { - "name" : "ThisProfileShouldNotDuplicate" - }, - { - "name" : "NeitherShouldThisOne" - } - ] - })" }; - - const auto settings0Json = VerifyParseSucceeded(settings0String); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(DefaultJson, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(1).Name()); - - Log::Comment(NoThrowString().Format( - L"Parse the user settings")); - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(5u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(4).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileShouldNotDuplicate", settings->_allProfiles.GetAt(3).Name()); - VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings->_allProfiles.GetAt(4).Name()); - - Log::Comment(NoThrowString().Format( - L"Pretend like we're checking to append dynamic profiles to the " - L"user's settings file. We absolutely _shouldn't_ be adding anything here.")); - bool const needToWriteFile = settings->_AppendDynamicProfilesToUserSettings(); - VERIFY_IS_FALSE(needToWriteFile); - VERIFY_ARE_EQUAL(settings0String.size(), settings->_userSettingsString.size()); - - Log::Comment(NoThrowString().Format( - L"Re-parse the settings file. We should have the _same_ settings as before.")); - Log::Comment(NoThrowString().Format( - L"Do this to a _new_ settings object, to make sure it turns out the same.")); - { - auto settings2 = winrt::make_self(); - settings2->_ParseJsonString(DefaultJson, true); - settings2->LayerJson(settings2->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings2->_allProfiles.Size()); - // Initialize the second settings object from the first settings - // object's settings string, the one that we synthesized. - const auto firstSettingsString = settings->_userSettingsString; - settings2->_ParseJsonString(firstSettingsString, false); - settings2->LayerJson(settings2->_userSettings); - VERIFY_ARE_EQUAL(5u, settings2->_allProfiles.Size()); - VERIFY_IS_TRUE(settings2->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings2->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings2->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_FALSE(settings2->_allProfiles.GetAt(3).HasGuid()); - VERIFY_IS_FALSE(settings2->_allProfiles.GetAt(4).HasGuid()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings2->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings2->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings2->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileShouldNotDuplicate", settings2->_allProfiles.GetAt(3).Name()); - VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings2->_allProfiles.GetAt(4).Name()); - } - - Log::Comment(NoThrowString().Format( - L"Validate the settings. All the profiles we have should be valid.")); - settings->_ValidateSettings(); - - VERIFY_ARE_EQUAL(5u, settings->_allProfiles.Size()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(4).HasGuid()); - VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"ThisProfileShouldNotDuplicate", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", settings->_allProfiles.GetAt(3).Name()); - VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(4).Name()); + const auto settings = winrt::make_self(settings0String, DefaultJson); + const auto profiles = settings->AllProfiles(); + VERIFY_ARE_EQUAL(5u, profiles.Size()); + VERIFY_ARE_EQUAL(L"ThisProfileIsGood", profiles.GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"ThisProfileShouldNotLayer", profiles.GetAt(1).Name()); + VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", profiles.GetAt(2).Name()); + VERIFY_ARE_EQUAL(L"Windows PowerShell", profiles.GetAt(3).Name()); + VERIFY_ARE_EQUAL(L"Command Prompt", profiles.GetAt(4).Name()); } void DeserializationTests::TestHideAllProfiles() { - const std::string settingsWithProfiles{ R"( + static constexpr std::string_view settingsWithProfiles{ R"( { "profiles": [ { @@ -1223,7 +683,7 @@ namespace SettingsModelLocalTests ] })" }; - const std::string settingsWithoutProfiles{ R"( + static constexpr std::string_view settingsWithoutProfiles{ R"( { "profiles": [ { @@ -1237,38 +697,17 @@ namespace SettingsModelLocalTests ] })" }; - VerifyParseSucceeded(settingsWithProfiles); - VerifyParseSucceeded(settingsWithoutProfiles); - { // Case 1: Good settings - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsWithProfiles, false); - settings->LayerJson(settings->_userSettings); - - settings->_UpdateActiveProfiles(); - Log::Comment(NoThrowString().Format( - L"settingsWithProfiles successfully parsed and validated")); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(1u, settings->_activeProfiles.Size()); + const auto settings = createSettings(settingsWithProfiles); + VERIFY_ARE_EQUAL(2u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(1u, settings->ActiveProfiles().Size()); } { // Case 2: Bad settings - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsWithoutProfiles, false); - settings->LayerJson(settings->_userSettings); - - bool caughtExpectedException = false; - try - { - settings->_UpdateActiveProfiles(); - } - catch (const implementation::SettingsException& ex) - { - VERIFY_IS_TRUE(ex.Error() == SettingsLoadErrors::AllProfilesHidden); - caughtExpectedException = true; - } - VERIFY_IS_TRUE(caughtExpectedException); + VERIFY_THROWS_SPECIFIC(winrt::make_self(settingsWithoutProfiles), const implementation::SettingsException, [](const auto& ex) { + return ex.Error() == SettingsLoadErrors::AllProfilesHidden; + }); } } @@ -1277,12 +716,11 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Ensure that setting a profile's scheme to a non-existent scheme causes a warning.")); - const std::string settings0String{ R"( - { + static constexpr std::string_view settings0String{ R"({ "profiles": [ { "name" : "profile0", - "colorScheme": "schemeOne" + "colorScheme": "Campbell" }, { "name" : "profile1", @@ -1292,43 +730,19 @@ namespace SettingsModelLocalTests "name" : "profile2" // Will use the Profile default value, "Campbell" } - ], - "schemes": [ - { - "name": "schemeOne", - "foreground": "#111111" - }, - { - "name": "schemeTwo", - "foreground": "#222222" - } ] })" }; - VerifyParseSucceeded(settings0String); + const auto settings = createSettings(settings0String); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); + VERIFY_ARE_EQUAL(1u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::UnknownColorScheme, settings->Warnings().GetAt(0)); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size()); - - VERIFY_ARE_EQUAL(L"schemeOne", settings->_allProfiles.GetAt(0).DefaultAppearance().ColorSchemeName()); - VERIFY_ARE_EQUAL(L"InvalidSchemeName", settings->_allProfiles.GetAt(1).DefaultAppearance().ColorSchemeName()); - VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(2).DefaultAppearance().ColorSchemeName()); - - settings->_ValidateAllSchemesExist(); - - VERIFY_ARE_EQUAL(1u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::UnknownColorScheme, settings->_warnings.GetAt(0)); - - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size()); - - VERIFY_ARE_EQUAL(L"schemeOne", settings->_allProfiles.GetAt(0).DefaultAppearance().ColorSchemeName()); - VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(1).DefaultAppearance().ColorSchemeName()); - VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(2).DefaultAppearance().ColorSchemeName()); + VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size()); + for (const auto& profile : settings->AllProfiles()) + { + VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().ColorSchemeName()); + } } void DeserializationTests::ValidateColorSchemeInCommands() @@ -1336,23 +750,17 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Ensure that setting a command's color scheme to a non-existent scheme causes a warning.")); - const std::string settings0String{ R"( + static constexpr std::string_view settings0String{ R"( { "profiles": [ { "name" : "profile0", - "colorScheme": "schemeOne" - } - ], - "schemes": [ - { - "name": "schemeOne", - "foreground": "#111111" + "colorScheme": "Campbell" } ], "actions": [ { - "command": { "action": "setColorScheme", "colorScheme": "schemeOne" } + "command": { "action": "setColorScheme", "colorScheme": "Campbell" } }, { "command": { "action": "setColorScheme", "colorScheme": "invalidScheme" } @@ -1360,23 +768,17 @@ namespace SettingsModelLocalTests ] })" }; - const std::string settings1String{ R"( + static constexpr std::string_view settings1String{ R"( { "profiles": [ { "name" : "profile0", - "colorScheme": "schemeOne" - } - ], - "schemes": [ - { - "name": "schemeOne", - "foreground": "#111111" + "colorScheme": "Campbell" } ], "actions": [ { - "command": { "action": "setColorScheme", "colorScheme": "schemeOne" } + "command": { "action": "setColorScheme", "colorScheme": "Campbell" } }, { "name": "parent", @@ -1387,23 +789,17 @@ namespace SettingsModelLocalTests ] })" }; - const std::string settings2String{ R"( + static constexpr std::string_view settings2String{ R"( { "profiles": [ { "name" : "profile0", - "colorScheme": "schemeOne" - } - ], - "schemes": [ - { - "name": "schemeOne", - "foreground": "#111111" + "colorScheme": "Campbell" } ], "actions": [ { - "command": { "action": "setColorScheme", "colorScheme": "schemeOne" } + "command": { "action": "setColorScheme", "colorScheme": "Campbell" } }, { "name": "grandparent", @@ -1425,49 +821,37 @@ namespace SettingsModelLocalTests // Case 1: setColorScheme command with invalid scheme Log::Comment(NoThrowString().Format( L"Testing a simple command with invalid scheme")); - VerifyParseSucceeded(settings0String); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateColorSchemesInCommands(); + const auto settings = createSettings(settings0String); - VERIFY_ARE_EQUAL(1u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::InvalidColorSchemeInCmd, settings->_warnings.GetAt(0)); + VERIFY_ARE_EQUAL(1u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::InvalidColorSchemeInCmd, settings->Warnings().GetAt(0)); } { // Case 2: nested setColorScheme command with invalid scheme Log::Comment(NoThrowString().Format( L"Testing a nested command with invalid scheme")); - VerifyParseSucceeded(settings1String); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settings1String, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateColorSchemesInCommands(); + const auto settings = createSettings(settings1String); - VERIFY_ARE_EQUAL(1u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::InvalidColorSchemeInCmd, settings->_warnings.GetAt(0)); + VERIFY_ARE_EQUAL(1u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::InvalidColorSchemeInCmd, settings->Warnings().GetAt(0)); } { // Case 3: nested-in-nested setColorScheme command with invalid scheme Log::Comment(NoThrowString().Format( L"Testing a nested-in-nested command with invalid scheme")); - VerifyParseSucceeded(settings2String); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settings2String, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateColorSchemesInCommands(); + const auto settings = createSettings(settings2String); - VERIFY_ARE_EQUAL(1u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::InvalidColorSchemeInCmd, settings->_warnings.GetAt(0)); + VERIFY_ARE_EQUAL(1u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::InvalidColorSchemeInCmd, settings->Warnings().GetAt(0)); } } void DeserializationTests::TestHelperFunctions() { - const std::string settings0String{ R"( + static constexpr std::string_view settings0String{ R"( { "defaultProfile" : "{2C4DE342-38B7-51CF-B940-2309A097F518}", "profiles": [ @@ -1489,48 +873,37 @@ namespace SettingsModelLocalTests ] })" }; - auto name0{ L"profile0" }; - auto name1{ L"profile1" }; - auto name2{ L"Ubuntu" }; - auto name3{ L"ThisProfileShouldNotThrow" }; - auto badName{ L"DoesNotExist" }; + const auto name0{ L"profile0" }; + const auto name1{ L"profile1" }; + const auto name2{ L"Ubuntu" }; + const auto name3{ L"ThisProfileShouldNotThrow" }; + const auto badName{ L"DoesNotExist" }; - const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-5555-49a3-80bd-e8fdd045185c}") }; - const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-6666-49a3-80bd-e8fdd045185c}") }; - const winrt::guid guid2{ ::Microsoft::Console::Utils::GuidFromString(L"{2C4DE342-38B7-51CF-B940-2309A097F518}") }; - const winrt::guid fakeGuid{ ::Microsoft::Console::Utils::GuidFromString(L"{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}") }; + const winrt::guid guid0{ Utils::GuidFromString(L"{6239a42c-5555-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid1{ Utils::GuidFromString(L"{6239a42c-6666-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid2{ Utils::GuidFromString(L"{2C4DE342-38B7-51CF-B940-2309A097F518}") }; + const winrt::guid fakeGuid{ Utils::GuidFromString(L"{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}") }; const winrt::guid autogeneratedGuid{ implementation::Profile::_GenerateGuidForProfile(name3, L"") }; - const std::optional badGuid{}; - VerifyParseSucceeded(settings0String); + const auto settings = createSettings(settings0String); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settings0String, false); - settings->LayerJson(settings->_userSettings); + VERIFY_ARE_EQUAL(guid0, settings->GetProfileByName(name0).Guid()); + VERIFY_ARE_EQUAL(guid1, settings->GetProfileByName(name1).Guid()); + VERIFY_ARE_EQUAL(guid2, settings->GetProfileByName(name2).Guid()); + VERIFY_ARE_EQUAL(autogeneratedGuid, settings->GetProfileByName(name3).Guid()); + VERIFY_IS_NULL(settings->GetProfileByName(badName)); - VERIFY_ARE_EQUAL(guid0, settings->_GetProfileGuidByName(name0)); - VERIFY_ARE_EQUAL(guid1, settings->_GetProfileGuidByName(name1)); - VERIFY_ARE_EQUAL(guid2, settings->_GetProfileGuidByName(name2)); - VERIFY_ARE_EQUAL(autogeneratedGuid, settings->_GetProfileGuidByName(name3)); - VERIFY_ARE_EQUAL(badGuid, settings->_GetProfileGuidByName(badName)); - - auto prof0{ settings->FindProfile(guid0) }; - auto prof1{ settings->FindProfile(guid1) }; - auto prof2{ settings->FindProfile(guid2) }; - - auto badProf{ settings->FindProfile(fakeGuid) }; - VERIFY_ARE_EQUAL(badProf, nullptr); - - VERIFY_ARE_EQUAL(name0, prof0.Name()); - VERIFY_ARE_EQUAL(name1, prof1.Name()); - VERIFY_ARE_EQUAL(name2, prof2.Name()); + VERIFY_ARE_EQUAL(name0, settings->FindProfile(guid0).Name()); + VERIFY_ARE_EQUAL(name1, settings->FindProfile(guid1).Name()); + VERIFY_ARE_EQUAL(name2, settings->FindProfile(guid2).Name()); + VERIFY_IS_NULL(settings->FindProfile(fakeGuid)); } void DeserializationTests::TestProfileBackgroundImageWithEnvVar() { const auto expectedPath = wil::ExpandEnvironmentStringsW(L"%WINDIR%\\System32\\x_80.png"); - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "profiles": [ { @@ -1540,19 +913,16 @@ namespace SettingsModelLocalTests ] })" }; - VerifyParseSucceeded(settingsJson); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_NOT_EQUAL(0u, settings->_allProfiles.Size()); - VERIFY_ARE_EQUAL(expectedPath, settings->_allProfiles.GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath()); + const auto settings = createSettings(settingsJson); + VERIFY_ARE_NOT_EQUAL(0u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(expectedPath, settings->AllProfiles().GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath()); } + void DeserializationTests::TestProfileBackgroundImageWithDesktopWallpaper() { const winrt::hstring expectedBackgroundImagePath{ L"desktopWallpaper" }; - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "profiles": [ { @@ -1562,17 +932,14 @@ namespace SettingsModelLocalTests ] })" }; - VerifyParseSucceeded(settingsJson); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(expectedBackgroundImagePath, settings->_allProfiles.GetAt(0).DefaultAppearance().BackgroundImagePath()); - VERIFY_ARE_NOT_EQUAL(expectedBackgroundImagePath, settings->_allProfiles.GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath()); + const auto settings = createSettings(settingsJson); + VERIFY_ARE_EQUAL(expectedBackgroundImagePath, settings->AllProfiles().GetAt(0).DefaultAppearance().BackgroundImagePath()); + VERIFY_ARE_NOT_EQUAL(expectedBackgroundImagePath, settings->AllProfiles().GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath()); } + void DeserializationTests::TestCloseOnExitParsing() { - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "profiles": [ { @@ -1594,21 +961,18 @@ namespace SettingsModelLocalTests ] })" }; - VerifyParseSucceeded(settingsJson); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->_allProfiles.GetAt(0).CloseOnExit()); - VERIFY_ARE_EQUAL(CloseOnExitMode::Always, settings->_allProfiles.GetAt(1).CloseOnExit()); - VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings->_allProfiles.GetAt(2).CloseOnExit()); + const auto settings = createSettings(settingsJson); + VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(0).CloseOnExit()); + VERIFY_ARE_EQUAL(CloseOnExitMode::Always, settings->AllProfiles().GetAt(1).CloseOnExit()); + VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings->AllProfiles().GetAt(2).CloseOnExit()); // Unknown modes parse as "Graceful" - VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->_allProfiles.GetAt(3).CloseOnExit()); + VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(3).CloseOnExit()); } + void DeserializationTests::TestCloseOnExitCompatibilityShim() { - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "profiles": [ { @@ -1622,13 +986,9 @@ namespace SettingsModelLocalTests ] })" }; - VerifyParseSucceeded(settingsJson); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->_allProfiles.GetAt(0).CloseOnExit()); - VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings->_allProfiles.GetAt(1).CloseOnExit()); + const auto settings = createSettings(settingsJson); + VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(0).CloseOnExit()); + VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings->AllProfiles().GetAt(1).CloseOnExit()); } void DeserializationTests::TestLayerUserDefaultsBeforeProfiles() @@ -1639,7 +999,7 @@ namespace SettingsModelLocalTests // we'll override that value, and in the other, we'll leave it // untouched. - const std::string settings0String{ R"( + static constexpr std::string_view settings0String{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": { @@ -1659,24 +1019,16 @@ namespace SettingsModelLocalTests ] } })" }; - VerifyParseSucceeded(settings0String); - const auto guid1String = L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"; + const auto settings = createSettings(settings0String); - { - auto settings = winrt::make_self(false); - settings->_ParseJsonString(settings0String, false); - VERIFY_IS_NULL(settings->_userDefaultProfileSettings); - settings->_ApplyDefaultsFromUserSettings(); - VERIFY_IS_NOT_NULL(settings->_userDefaultProfileSettings); - settings->LayerJson(settings->_userSettings); + VERIFY_IS_NOT_NULL(settings->ProfileDefaults()); - VERIFY_ARE_EQUAL(guid1String, settings->_globals->UnparsedDefaultProfile()); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); + VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", settings->GlobalSettings().UnparsedDefaultProfile()); + VERIFY_ARE_EQUAL(2u, settings->AllProfiles().Size()); - VERIFY_ARE_EQUAL(2345, settings->_allProfiles.GetAt(0).HistorySize()); - VERIFY_ARE_EQUAL(1234, settings->_allProfiles.GetAt(1).HistorySize()); - } + VERIFY_ARE_EQUAL(2345, settings->AllProfiles().GetAt(0).HistorySize()); + VERIFY_ARE_EQUAL(1234, settings->AllProfiles().GetAt(1).HistorySize()); } void DeserializationTests::TestDontLayerGuidFromUserDefaults() @@ -1685,8 +1037,7 @@ namespace SettingsModelLocalTests // "guid" in the "defaultSettings", and have that apply to all the other // profiles - const std::string settings0String{ R"( - { + static constexpr std::string_view settings0String{ R"({ "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": { "defaults": { @@ -1705,36 +1056,16 @@ namespace SettingsModelLocalTests ] } })" }; - VerifyParseSucceeded(settings0String); const auto guid1String = L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"; - const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(guid1String) }; - const winrt::guid guid2{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid1{ Utils::GuidFromString(guid1String) }; - { - auto settings = winrt::make_self(false); - settings->_ParseJsonString(DefaultJson, true); - settings->LayerJson(settings->_defaultSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); + const auto settings = winrt::make_self(settings0String, DefaultJson); - settings->_ParseJsonString(settings0String, false); - VERIFY_IS_NULL(settings->_userDefaultProfileSettings); - settings->_ApplyDefaultsFromUserSettings(); - VERIFY_IS_NOT_NULL(settings->_userDefaultProfileSettings); - - Log::Comment(NoThrowString().Format( - L"Ensure that cmd and powershell don't get their GUIDs overwritten")); - VERIFY_ARE_NOT_EQUAL(guid2, settings->_allProfiles.GetAt(0).Guid()); - VERIFY_ARE_NOT_EQUAL(guid2, settings->_allProfiles.GetAt(1).Guid()); - - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(guid1String, settings->_globals->UnparsedDefaultProfile()); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - - VERIFY_ARE_EQUAL(guid1, settings->_allProfiles.GetAt(2).Guid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).HasGuid()); - } + VERIFY_ARE_EQUAL(guid1String, settings->GlobalSettings().UnparsedDefaultProfile()); + VERIFY_ARE_EQUAL(4u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(guid1, settings->AllProfiles().GetAt(0).Guid()); + VERIFY_ARE_NOT_EQUAL(guid1, settings->AllProfiles().GetAt(1).Guid()); } void DeserializationTests::TestLayerUserDefaultsOnDynamics() @@ -1746,11 +1077,35 @@ namespace SettingsModelLocalTests // settings in defaultSettings should apply _on top_ of settings from // dynamic profiles. - const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") }; - const winrt::guid guid2{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}") }; - const winrt::guid guid3{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid1{ Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid2{ Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid3{ Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid4{ Utils::GuidFromString(L"{6239a42c-4444-49a3-80bd-e8fdd045185c}") }; - const std::string userProfiles{ R"( + static constexpr std::string_view dynamicProfiles{ R"({ + "profiles": [ + { + "name": "profile0", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "source": "Terminal.App.UnitTest.0", + "historySize": 1111 + }, + { + "name": "profile1", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "source": "Terminal.App.UnitTest.1", + "historySize": 2222 + }, + { + "name": "profile2", + "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}", + "source": "Terminal.App.UnitTest.1", + "historySize": 4444 + } + ] + })" }; + + static constexpr std::string_view userProfiles{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": { @@ -1759,18 +1114,18 @@ namespace SettingsModelLocalTests }, "list": [ { - "name" : "profile0FromUserSettings", // this is _allProfiles.GetAt(0) + "name" : "profile0FromUserSettings", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "source": "Terminal.App.UnitTest.0" }, { - "name" : "profile1FromUserSettings", // this is _allProfiles.GetAt(2) + "name" : "profile1FromUserSettings", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "source": "Terminal.App.UnitTest.1", "historySize": 4444 }, { - "name" : "profile2FromUserSettings", // this is _allProfiles.GetAt(3) + "name" : "profile2FromUserSettings", "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", "historySize": 5555 } @@ -1778,73 +1133,29 @@ namespace SettingsModelLocalTests } })" }; - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - gen0->pfnGenerate = [guid1, guid2]() { - std::vector profiles; - Profile p0 = winrt::make(guid1); - p0.Name(L"profile0"); // this is _allProfiles.GetAt(0) - p0.HistorySize(1111); - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - gen1->pfnGenerate = [guid1, guid2]() { - std::vector profiles; - Profile p0 = winrt::make(guid1); - Profile p1 = winrt::make(guid2); - p0.Name(L"profile0"); // this is _allProfiles.GetAt(1) - p1.Name(L"profile1"); // this is _allProfiles.GetAt(2) - p0.HistorySize(2222); - profiles.push_back(p0); - p1.HistorySize(3333); - profiles.push_back(p1); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); + const auto settings = winrt::make_self(userProfiles, dynamicProfiles); + const auto allProfiles = settings->AllProfiles(); Log::Comment(NoThrowString().Format( L"All profiles with the same name have the same GUID. However, they" L" will not be layered, because they have different source's")); - // parse userProfiles as the user settings - settings->_ParseJsonString(userProfiles, false); - VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size(), L"Just parsing the user settings doesn't actually layer them"); - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); + VERIFY_ARE_EQUAL(4u, allProfiles.Size()); - VERIFY_ARE_EQUAL(1111, settings->_allProfiles.GetAt(0).HistorySize()); - VERIFY_ARE_EQUAL(2222, settings->_allProfiles.GetAt(1).HistorySize()); - VERIFY_ARE_EQUAL(3333, settings->_allProfiles.GetAt(2).HistorySize()); + VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.0", allProfiles.GetAt(0).Source()); + VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", allProfiles.GetAt(1).Source()); + VERIFY_ARE_EQUAL(L"", allProfiles.GetAt(2).Source()); + VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", allProfiles.GetAt(3).Source()); - settings->_ApplyDefaultsFromUserSettings(); + VERIFY_ARE_EQUAL(guid1, allProfiles.GetAt(0).Guid()); + VERIFY_ARE_EQUAL(guid2, allProfiles.GetAt(1).Guid()); + VERIFY_ARE_EQUAL(guid3, allProfiles.GetAt(2).Guid()); + VERIFY_ARE_EQUAL(guid4, allProfiles.GetAt(3).Guid()); - VERIFY_ARE_EQUAL(1234, settings->_allProfiles.GetAt(0).HistorySize()); - VERIFY_ARE_EQUAL(1234, settings->_allProfiles.GetAt(1).HistorySize()); - VERIFY_ARE_EQUAL(1234, settings->_allProfiles.GetAt(2).HistorySize()); - - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).Source().empty()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).Source().empty()); - - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.0", settings->_allProfiles.GetAt(0).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(1).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(2).Source()); - - VERIFY_ARE_EQUAL(guid1, settings->_allProfiles.GetAt(0).Guid()); - VERIFY_ARE_EQUAL(guid1, settings->_allProfiles.GetAt(1).Guid()); - VERIFY_ARE_EQUAL(guid2, settings->_allProfiles.GetAt(2).Guid()); - - VERIFY_ARE_EQUAL(L"profile0FromUserSettings", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile1FromUserSettings", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"profile2FromUserSettings", settings->_allProfiles.GetAt(3).Name()); + VERIFY_ARE_EQUAL(L"profile0FromUserSettings", allProfiles.GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"profile1FromUserSettings", allProfiles.GetAt(1).Name()); + VERIFY_ARE_EQUAL(L"profile2FromUserSettings", allProfiles.GetAt(2).Name()); + VERIFY_ARE_EQUAL(L"profile2", allProfiles.GetAt(3).Name()); Log::Comment(NoThrowString().Format( L"This is the real meat of the test: The two dynamic profiles that " @@ -1852,17 +1163,17 @@ namespace SettingsModelLocalTests L"1234 as their historySize(from the defaultSettings).The other two" L" profiles should have their custom historySize value.")); - VERIFY_ARE_EQUAL(1234, settings->_allProfiles.GetAt(0).HistorySize()); - VERIFY_ARE_EQUAL(1234, settings->_allProfiles.GetAt(1).HistorySize()); - VERIFY_ARE_EQUAL(4444, settings->_allProfiles.GetAt(2).HistorySize()); - VERIFY_ARE_EQUAL(5555, settings->_allProfiles.GetAt(3).HistorySize()); + VERIFY_ARE_EQUAL(1234, allProfiles.GetAt(0).HistorySize()); + VERIFY_ARE_EQUAL(4444, allProfiles.GetAt(1).HistorySize()); + VERIFY_ARE_EQUAL(5555, allProfiles.GetAt(2).HistorySize()); + VERIFY_ARE_EQUAL(1234, allProfiles.GetAt(3).HistorySize()); } void DeserializationTests::FindMissingProfile() { // Test that CascadiaSettings::FindProfile returns null for a GUID that // doesn't exist - const std::string settingsString{ R"( + static constexpr std::string_view settingsString{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -1876,12 +1187,11 @@ namespace SettingsModelLocalTests } ] })" }; - const auto settingsJsonObj = VerifyParseSucceeded(settingsString); - auto settings = implementation::CascadiaSettings::FromJson(settingsJsonObj); + const auto settings = createSettings(settingsString); - const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); - const auto guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}"); + const auto guid1 = Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); + const auto guid2 = Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); + const auto guid3 = Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}"); const auto profile1 = settings->FindProfile(guid1); const auto profile2 = settings->FindProfile(guid2); @@ -1897,7 +1207,7 @@ namespace SettingsModelLocalTests void DeserializationTests::ValidateKeybindingsWarnings() { - const std::string badSettings{ R"( + static constexpr std::string_view badSettings{ R"( { "defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -1918,35 +1228,35 @@ namespace SettingsModelLocalTests ] })" }; - const auto settingsObject = VerifyParseSucceeded(badSettings); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); + const auto settings = createSettings(badSettings); // KeyMap: ctrl+a/b are mapped to "invalid" // ActionMap: "splitPane" and "invalid" are the only deserialized actions // NameMap: "splitPane" has no key binding, but it is still added to the name map - VERIFY_ARE_EQUAL(2u, settings->_globals->_actionMap->_KeyMap.size()); - VERIFY_ARE_EQUAL(2u, settings->_globals->_actionMap->_ActionMap.size()); - VERIFY_ARE_EQUAL(1u, settings->_globals->_actionMap->NameMap().Size()); + const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); + VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(2u, actionMap->_ActionMap.size()); + VERIFY_ARE_EQUAL(1u, actionMap->NameMap().Size()); + VERIFY_ARE_EQUAL(5u, settings->Warnings().Size()); - VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(1)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(2)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_globals->_keybindingsWarnings.at(3)); + const auto globalAppSettings = winrt::get_self(settings->GlobalSettings()); + const auto& keybindingsWarnings = globalAppSettings->KeybindingsWarnings(); + VERIFY_ARE_EQUAL(4u, keybindingsWarnings.size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, keybindingsWarnings.at(0)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, keybindingsWarnings.at(1)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, keybindingsWarnings.at(2)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, keybindingsWarnings.at(3)); - settings->_ValidateKeybindings(); - - VERIFY_ARE_EQUAL(5u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_warnings.GetAt(1)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(2)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(3)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(4)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->Warnings().GetAt(0)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->Warnings().GetAt(1)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->Warnings().GetAt(2)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->Warnings().GetAt(3)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->Warnings().GetAt(4)); } void DeserializationTests::ValidateExecuteCommandlineWarning() { - const std::string badSettings{ R"( + static constexpr std::string_view badSettings{ R"( { "defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -1966,98 +1276,37 @@ namespace SettingsModelLocalTests ] })" }; - const auto settingsObject = VerifyParseSucceeded(badSettings); + const auto settings = createSettings(badSettings); - auto settings = implementation::CascadiaSettings::FromJson(settingsObject); + const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); + VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('A'), 0 })); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('B'), 0 })); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); - VERIFY_ARE_EQUAL(3u, settings->_globals->_actionMap->_KeyMap.size()); - VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('A'), 0 })); - VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('B'), 0 })); - VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); + const auto globalAppSettings = winrt::get_self(settings->GlobalSettings()); + const auto& keybindingsWarnings = globalAppSettings->KeybindingsWarnings(); + VERIFY_ARE_EQUAL(3u, keybindingsWarnings.size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, keybindingsWarnings.at(0)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, keybindingsWarnings.at(1)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, keybindingsWarnings.at(2)); - for (const auto& warning : settings->_globals->_keybindingsWarnings) - { - Log::Comment(NoThrowString().Format( - L"warning:%d", warning)); - } - VERIFY_ARE_EQUAL(3u, settings->_globals->_keybindingsWarnings.size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(0)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(1)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(2)); - - settings->_ValidateKeybindings(); - - VERIFY_ARE_EQUAL(4u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(1)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(2)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(3)); - } - - void DeserializationTests::ValidateLegacyGlobalsWarning() - { - const std::string badSettings{ R"( - { - "globals": {}, - "defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" - }, - { - "name" : "profile1", - "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" - } - ], - "keybindings": [] - })" }; - - // Create the default settings - auto settings = winrt::make_self(); - settings->_ParseJsonString(DefaultJson, true); - settings->LayerJson(settings->_defaultSettings); - - settings->_ValidateNoGlobalsKey(); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - - // Now layer on the user's settings - settings->_ParseJsonString(badSettings, false); - settings->LayerJson(settings->_userSettings); - - settings->_ValidateNoGlobalsKey(); - VERIFY_ARE_EQUAL(1u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::LegacyGlobalsProperty, settings->_warnings.GetAt(0)); + VERIFY_ARE_EQUAL(4u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->Warnings().GetAt(0)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->Warnings().GetAt(1)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->Warnings().GetAt(2)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->Warnings().GetAt(3)); } void DeserializationTests::TestTrailingCommas() { - const std::string badSettings{ R"( - { - "defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" - }, - { - "name" : "profile1", - "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" - }, - ], - "keybindings": [], + static constexpr std::string_view badSettings{ R"({ + "profiles": [{ "name": "profile0" }], })" }; - // Create the default settings - auto settings = winrt::make_self(); - settings->_ParseJsonString(DefaultJson, true); - settings->LayerJson(settings->_defaultSettings); - - // Now layer on the user's settings try { - settings->_ParseJsonString(badSettings, false); - settings->LayerJson(settings->_userSettings); + const auto settings = createSettings(badSettings); } catch (...) { @@ -2067,7 +1316,7 @@ namespace SettingsModelLocalTests void DeserializationTests::TestCommandsAndKeybindings() { - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -2099,23 +1348,15 @@ namespace SettingsModelLocalTests ] })" }; - const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}"); - const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); + const auto settings = createSettings(settingsJson); - VerifyParseSucceeded(settingsJson); + VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size()); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - - const auto profile2Guid = settings->_allProfiles.GetAt(2).Guid(); + const auto profile2Guid = settings->AllProfiles().GetAt(2).Guid(); VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); - auto actionMap = winrt::get_self(settings->_globals->ActionMap()); - VERIFY_ARE_EQUAL(5u, actionMap->_KeyMap.size()); + auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); + VERIFY_ARE_EQUAL(5u, actionMap->KeyBindings().Size()); // A/D, B, C, E will be in the list of commands, for 4 total. // * A and D share the same name, so they'll only generate a single action. @@ -2124,8 +1365,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(1u, nameMap.Size()); { - KeyChord kc{ true, false, false, false, static_cast('A'), 0 }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); + const KeyChord kc{ true, false, false, false, static_cast('A'), 0 }; + const auto actionAndArgs = TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2141,8 +1382,8 @@ namespace SettingsModelLocalTests Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set."); { - KeyChord kc{ true, false, false, false, static_cast('C'), 0 }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); + const KeyChord kc{ true, false, false, false, static_cast('C'), 0 }; + const auto actionAndArgs = TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2155,8 +1396,8 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - KeyChord kc{ true, false, false, false, static_cast('D'), 0 }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); + const KeyChord kc{ true, false, false, false, static_cast('D'), 0 }; + const auto actionAndArgs = TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2169,8 +1410,8 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - KeyChord kc{ true, false, false, false, static_cast('E'), 0 }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); + const KeyChord kc{ true, false, false, false, static_cast('E'), 0 }; + const auto actionAndArgs = TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2183,8 +1424,8 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - KeyChord kc{ true, false, false, false, static_cast('F'), 0 }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); + const KeyChord kc{ true, false, false, false, static_cast('F'), 0 }; + const auto actionAndArgs = TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2201,18 +1442,18 @@ namespace SettingsModelLocalTests _logCommandNames(nameMap); { // This was renamed to "ctrl+c" in C. So this does not exist. - auto command = nameMap.TryLookup(L"Split pane, split: vertical"); + const auto command = nameMap.TryLookup(L"Split pane, split: vertical"); VERIFY_IS_NULL(command); } { // This was renamed to "ctrl+c" in C. So this does not exist. - auto command = nameMap.TryLookup(L"ctrl+b"); + const auto command = nameMap.TryLookup(L"ctrl+b"); VERIFY_IS_NULL(command); } { - auto command = nameMap.TryLookup(L"ctrl+c"); + const auto command = nameMap.TryLookup(L"ctrl+c"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.ActionAndArgs(); + const auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -2227,7 +1468,7 @@ namespace SettingsModelLocalTests } { // This was renamed to null (aka removed from the name map) in F. So this does not exist. - auto command = nameMap.TryLookup(L"Split pane, split: horizontal"); + const auto command = nameMap.TryLookup(L"Split pane, split: horizontal"); VERIFY_IS_NULL(command); } } @@ -2238,7 +1479,7 @@ namespace SettingsModelLocalTests // of command should just be ignored, since we can't auto-generate names // for nested commands, they _must_ have names specified. - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -2273,29 +1514,16 @@ namespace SettingsModelLocalTests } ] }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - VerifyParseSucceeded(settingsJson); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - - settings->_ValidateSettings(); - const auto& nameMap{ settings->ActionMap().NameMap() }; - _logCommandNames(nameMap); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - + const auto settings = createSettings(settingsJson); + VERIFY_ARE_EQUAL(0u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size()); // Because the "parent" command didn't have a name, it couldn't be // placed into the list of commands. It and it's children are just // ignored. - VERIFY_ARE_EQUAL(0u, nameMap.Size()); + VERIFY_ARE_EQUAL(0u, settings->ActionMap().NameMap().Size()); } void DeserializationTests::TestNestedCommandWithBadSubCommands() @@ -2304,7 +1532,7 @@ namespace SettingsModelLocalTests // of command should just be ignored, since we can't auto-generate names // for nested commands, they _must_ have names specified. - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -2327,20 +1555,14 @@ namespace SettingsModelLocalTests } ] }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - VerifyParseSucceeded(settingsJson); + const auto settings = createSettings(settingsJson); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - - VERIFY_ARE_EQUAL(2u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0)); - VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(1)); + VERIFY_ARE_EQUAL(2u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->Warnings().GetAt(0)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->Warnings().GetAt(1)); const auto& nameMap{ settings->ActionMap().NameMap() }; VERIFY_ARE_EQUAL(0u, nameMap.Size()); } @@ -2349,7 +1571,7 @@ namespace SettingsModelLocalTests { // Test that layering a command with `"commands": null` set will unbind a command that already exists. - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -2385,11 +1607,10 @@ namespace SettingsModelLocalTests } ] }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - const std::string settings1Json{ R"( + static constexpr std::string_view settings1Json{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "actions": [ @@ -2400,33 +1621,9 @@ namespace SettingsModelLocalTests ], })" }; - VerifyParseSucceeded(settingsJson); - VerifyParseSucceeded(settings1Json); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - - settings->_ValidateSettings(); - auto nameMap{ settings->ActionMap().NameMap() }; - _logCommandNames(nameMap); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, nameMap.Size()); - - Log::Comment(L"Layer second bit of json, to unbind the original command."); - - settings->_ParseJsonString(settings1Json, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - - nameMap = settings->ActionMap().NameMap(); - _logCommandNames(nameMap); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(0u, nameMap.Size()); + const auto settings = winrt::make_self(settings1Json, settingsJson); + VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(0u, settings->ActionMap().NameMap().Size()); } void DeserializationTests::TestRebindNestedCommand() @@ -2434,7 +1631,7 @@ namespace SettingsModelLocalTests // Test that layering a command with an action set on top of a command // with nested commands replaces the nested commands with an action. - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -2470,11 +1667,10 @@ namespace SettingsModelLocalTests } ] }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - const std::string settings1Json{ R"( + static constexpr std::string_view settings1Json{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "actions": [ @@ -2485,52 +1681,17 @@ namespace SettingsModelLocalTests ], })" }; - VerifyParseSucceeded(settingsJson); - VerifyParseSucceeded(settings1Json); + const auto settings = winrt::make_self(settings1Json, settingsJson); - auto settings = winrt::make_self(); - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - - const auto& actionMap{ settings->ActionMap() }; - settings->_ValidateSettings(); - auto nameMap{ actionMap.NameMap() }; - _logCommandNames(nameMap); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); + const auto nameMap = settings->ActionMap().NameMap(); VERIFY_ARE_EQUAL(1u, nameMap.Size()); { - winrt::hstring commandName{ L"parent" }; - auto commandProj = nameMap.TryLookup(commandName); - VERIFY_IS_NOT_NULL(commandProj); - - winrt::com_ptr commandImpl; - commandImpl.copy_from(winrt::get_self(commandProj)); - - VERIFY_IS_TRUE(commandImpl->HasNestedCommands()); - VERIFY_ARE_EQUAL(2u, commandImpl->_subcommands.Size()); - } - - Log::Comment(L"Layer second bit of json, to unbind the original command."); - settings->_ParseJsonString(settings1Json, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - - nameMap = settings->ActionMap().NameMap(); - _logCommandNames(nameMap); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, nameMap.Size()); - - { - winrt::hstring commandName{ L"parent" }; - auto commandProj = nameMap.TryLookup(commandName); + const winrt::hstring commandName{ L"parent" }; + const auto commandProj = nameMap.TryLookup(commandName); VERIFY_IS_NOT_NULL(commandProj); - auto actionAndArgs = commandProj.ActionAndArgs(); + const auto actionAndArgs = commandProj.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -2545,7 +1706,7 @@ namespace SettingsModelLocalTests void DeserializationTests::TestCopy() { - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", "initialCols": 50, @@ -2600,43 +1761,37 @@ namespace SettingsModelLocalTests ] })" }; - VerifyParseSucceeded(settingsJson); - - auto settings{ winrt::make_self() }; - settings->_ParseJsonString(settingsJson, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - + const auto settings{ winrt::make_self(settingsJson) }; const auto copy{ settings->Copy() }; const auto copyImpl{ winrt::get_self(copy) }; // test globals - VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), copyImpl->_globals->DefaultProfile()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), copyImpl->GlobalSettings().DefaultProfile()); // test profiles - VERIFY_ARE_EQUAL(settings->_allProfiles.Size(), copyImpl->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(0).Name(), copyImpl->_allProfiles.GetAt(0).Name()); + VERIFY_ARE_EQUAL(settings->AllProfiles().Size(), copyImpl->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->AllProfiles().GetAt(0).Name(), copyImpl->AllProfiles().GetAt(0).Name()); // test schemes const auto schemeName{ L"Campbell, but for a test" }; - VERIFY_ARE_EQUAL(settings->_globals->_colorSchemes.Size(), copyImpl->_globals->_colorSchemes.Size()); - VERIFY_ARE_EQUAL(settings->_globals->_colorSchemes.HasKey(schemeName), copyImpl->_globals->_colorSchemes.HasKey(schemeName)); + VERIFY_ARE_EQUAL(settings->GlobalSettings().ColorSchemes().Size(), copyImpl->GlobalSettings().ColorSchemes().Size()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().ColorSchemes().HasKey(schemeName), copyImpl->GlobalSettings().ColorSchemes().HasKey(schemeName)); // test actions - VERIFY_ARE_EQUAL(settings->_globals->_actionMap->_KeyMap.size(), copyImpl->_globals->_actionMap->_KeyMap.size()); - const auto& nameMapOriginal{ settings->_globals->_actionMap->NameMap() }; - const auto& nameMapCopy{ copyImpl->_globals->_actionMap->NameMap() }; + VERIFY_ARE_EQUAL(settings->GlobalSettings().ActionMap().KeyBindings().Size(), copyImpl->GlobalSettings().ActionMap().KeyBindings().Size()); + const auto& nameMapOriginal{ settings->GlobalSettings().ActionMap().NameMap() }; + const auto& nameMapCopy{ copyImpl->GlobalSettings().ActionMap().NameMap() }; VERIFY_ARE_EQUAL(nameMapOriginal.Size(), nameMapCopy.Size()); // Test that changing the copy should not change the original - VERIFY_ARE_EQUAL(settings->_globals->WordDelimiters(), copyImpl->_globals->WordDelimiters()); - copyImpl->_globals->WordDelimiters(L"changed value"); - VERIFY_ARE_NOT_EQUAL(settings->_globals->WordDelimiters(), copyImpl->_globals->WordDelimiters()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().WordDelimiters(), copyImpl->GlobalSettings().WordDelimiters()); + copyImpl->GlobalSettings().WordDelimiters(L"changed value"); + VERIFY_ARE_NOT_EQUAL(settings->GlobalSettings().WordDelimiters(), copyImpl->GlobalSettings().WordDelimiters()); } void DeserializationTests::TestCloneInheritanceTree() { - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", "profiles": @@ -2660,45 +1815,38 @@ namespace SettingsModelLocalTests } })" }; - VerifyParseSucceeded(settingsJson); - - auto settings{ winrt::make_self() }; - settings->_ParseJsonString(settingsJson, false); - settings->_ApplyDefaultsFromUserSettings(); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - + const auto settings{ winrt::make_self(settingsJson) }; const auto copy{ settings->Copy() }; const auto copyImpl{ winrt::get_self(copy) }; // test globals - VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), copyImpl->_globals->DefaultProfile()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), copyImpl->GlobalSettings().DefaultProfile()); // test profiles - VERIFY_ARE_EQUAL(settings->_allProfiles.Size(), copyImpl->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(0).Name(), copyImpl->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(1).Name(), copyImpl->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(2).Name(), copyImpl->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(settings->_userDefaultProfileSettings->Name(), copyImpl->_userDefaultProfileSettings->Name()); + VERIFY_ARE_EQUAL(settings->AllProfiles().Size(), copyImpl->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->AllProfiles().GetAt(0).Name(), copyImpl->AllProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(settings->AllProfiles().GetAt(1).Name(), copyImpl->AllProfiles().GetAt(1).Name()); + VERIFY_ARE_EQUAL(settings->AllProfiles().GetAt(2).Name(), copyImpl->AllProfiles().GetAt(2).Name()); + VERIFY_ARE_EQUAL(settings->ProfileDefaults().Name(), copyImpl->ProfileDefaults().Name()); // Modifying profile.defaults should... - VERIFY_ARE_EQUAL(settings->_userDefaultProfileSettings->HasName(), copyImpl->_userDefaultProfileSettings->HasName()); - copyImpl->_userDefaultProfileSettings->Name(L"changed value"); + VERIFY_ARE_EQUAL(settings->ProfileDefaults().HasName(), copyImpl->ProfileDefaults().HasName()); + copyImpl->ProfileDefaults().Name(L"changed value"); // ...keep the same name for the first two profiles - VERIFY_ARE_EQUAL(settings->_allProfiles.Size(), copyImpl->_allProfiles.Size()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(0).Name(), copyImpl->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(settings->_allProfiles.GetAt(1).Name(), copyImpl->_allProfiles.GetAt(1).Name()); + VERIFY_ARE_EQUAL(settings->AllProfiles().Size(), copyImpl->AllProfiles().Size()); + VERIFY_ARE_EQUAL(settings->AllProfiles().GetAt(0).Name(), copyImpl->AllProfiles().GetAt(0).Name()); + VERIFY_ARE_EQUAL(settings->AllProfiles().GetAt(1).Name(), copyImpl->AllProfiles().GetAt(1).Name()); // ...but change the name for the one that inherited it from profile.defaults - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(2).Name(), copyImpl->_allProfiles.GetAt(2).Name()); + VERIFY_ARE_NOT_EQUAL(settings->AllProfiles().GetAt(2).Name(), copyImpl->AllProfiles().GetAt(2).Name()); // profile.defaults should be different between the two graphs - VERIFY_ARE_EQUAL(settings->_userDefaultProfileSettings->HasName(), copyImpl->_userDefaultProfileSettings->HasName()); - VERIFY_ARE_NOT_EQUAL(settings->_userDefaultProfileSettings->Name(), copyImpl->_userDefaultProfileSettings->Name()); + VERIFY_ARE_EQUAL(settings->ProfileDefaults().HasName(), copyImpl->ProfileDefaults().HasName()); + VERIFY_ARE_NOT_EQUAL(settings->ProfileDefaults().Name(), copyImpl->ProfileDefaults().Name()); Log::Comment(L"Test empty profiles.defaults"); - const std::string emptyPDJson{ R"( + static constexpr std::string_view emptyPDJson{ R"( { "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", "profiles": @@ -2714,7 +1862,7 @@ namespace SettingsModelLocalTests } })" }; - const std::string missingPDJson{ R"( + static constexpr std::string_view missingPDJson{ R"( { "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", "profiles": @@ -2726,28 +1874,21 @@ namespace SettingsModelLocalTests ] })" }; - auto verifyEmptyPD = [this](const std::string json) { - VerifyParseSucceeded(json); - - auto settings{ winrt::make_self() }; - settings->_ParseJsonString(json, false); - settings->_ApplyDefaultsFromUserSettings(); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - + auto verifyEmptyPD = [this](const auto json) { + const auto settings{ winrt::make_self(json) }; const auto copy{ settings->Copy() }; const auto copyImpl{ winrt::get_self(copy) }; // if we don't have profiles.defaults, it should still be in the tree - VERIFY_IS_NOT_NULL(settings->_userDefaultProfileSettings); - VERIFY_IS_NOT_NULL(copyImpl->_userDefaultProfileSettings); + VERIFY_IS_NOT_NULL(settings->ProfileDefaults()); + VERIFY_IS_NOT_NULL(copyImpl->ProfileDefaults()); VERIFY_ARE_EQUAL(settings->ActiveProfiles().Size(), 1u); VERIFY_ARE_EQUAL(settings->ActiveProfiles().Size(), copyImpl->ActiveProfiles().Size()); // so we should only have one parent, instead of two - auto srcProfile{ winrt::get_self(settings->ActiveProfiles().GetAt(0)) }; - auto copyProfile{ winrt::get_self(copyImpl->ActiveProfiles().GetAt(0)) }; + const auto srcProfile{ winrt::get_self(settings->ActiveProfiles().GetAt(0)) }; + const auto copyProfile{ winrt::get_self(copyImpl->ActiveProfiles().GetAt(0)) }; VERIFY_ARE_EQUAL(srcProfile->Parents().size(), 1u); VERIFY_ARE_EQUAL(srcProfile->Parents().size(), copyProfile->Parents().size()); }; @@ -2769,7 +1910,7 @@ namespace SettingsModelLocalTests { // Test unbinding a command's key chord or name that originated in another layer. - const std::string settings1Json{ R"( + static constexpr std::string_view settings1Json{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -2797,11 +1938,10 @@ namespace SettingsModelLocalTests "command": "closePane", "keys": "ctrl+shift+w" } - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - const std::string settings2Json{ R"( + static constexpr std::string_view settings2Json{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "actions": [ @@ -2809,13 +1949,6 @@ namespace SettingsModelLocalTests "command": null, "keys": "ctrl+shift+w" }, - ], - })" }; - - const std::string settings3Json{ R"( - { - "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", - "actions": [ { "name": "bar", "command": "closePane" @@ -2823,82 +1956,10 @@ namespace SettingsModelLocalTests ], })" }; - VerifyParseSucceeded(settings1Json); - VerifyParseSucceeded(settings2Json); - VerifyParseSucceeded(settings3Json); - - auto settings = winrt::make_self(); - settings->_ParseJsonString(settings1Json, false); - settings->LayerJson(settings->_userSettings); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - - settings->_ValidateSettings(); - auto nameMap{ settings->ActionMap().NameMap() }; - _logCommandNames(nameMap); - - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, nameMap.Size()); - + const auto settings = winrt::make_self(settings2Json, settings1Json); const KeyChord expectedKeyChord{ true, false, true, false, static_cast('W'), 0 }; - { - // Verify NameMap returns correct value - const auto& cmd{ nameMap.TryLookup(L"foo") }; - VERIFY_IS_NOT_NULL(cmd); - VERIFY_IS_NOT_NULL(cmd.Keys()); - VERIFY_IS_TRUE(cmd.Keys().Modifiers() == expectedKeyChord.Modifiers() && cmd.Keys().Vkey() == expectedKeyChord.Vkey()); - } - { - // Verify ActionMap::GetActionByKeyChord API - const auto& cmd{ settings->ActionMap().GetActionByKeyChord(expectedKeyChord) }; - VERIFY_IS_NOT_NULL(cmd); - VERIFY_IS_NOT_NULL(cmd.Keys()); - VERIFY_IS_TRUE(cmd.Keys().Modifiers() == expectedKeyChord.Modifiers() && cmd.Keys().Vkey() == expectedKeyChord.Vkey()); - } - { - // Verify ActionMap::GetKeyBindingForAction API - const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(ShortcutAction::ClosePane) }; - VERIFY_IS_NOT_NULL(actualKeyChord); - VERIFY_IS_TRUE(actualKeyChord.Modifiers() == expectedKeyChord.Modifiers() && actualKeyChord.Vkey() == expectedKeyChord.Vkey()); - } - Log::Comment(L"Layer second bit of json, to unbind the key chord of the original command."); - - settings->_ParseJsonString(settings2Json, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - - nameMap = settings->ActionMap().NameMap(); - _logCommandNames(nameMap); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, nameMap.Size()); - { - // Verify NameMap returns correct value - const auto& cmd{ nameMap.TryLookup(L"foo") }; - VERIFY_IS_NOT_NULL(cmd); - VERIFY_IS_NULL(cmd.Keys()); - } - { - // Verify ActionMap::GetActionByKeyChord API - const auto& cmd{ settings->ActionMap().GetActionByKeyChord(expectedKeyChord) }; - VERIFY_IS_NULL(cmd); - } - { - // Verify ActionMap::GetKeyBindingForAction API - const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(ShortcutAction::ClosePane) }; - VERIFY_IS_NULL(actualKeyChord); - } - - Log::Comment(L"Layer third bit of json, to unbind name of the original command."); - - settings->_ParseJsonString(settings3Json, false); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); - - nameMap = settings->ActionMap().NameMap(); - _logCommandNames(nameMap); - VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); + const auto nameMap = settings->ActionMap().NameMap(); VERIFY_ARE_EQUAL(1u, nameMap.Size()); { // Verify NameMap returns correct value diff --git a/src/cascadia/LocalTests_SettingsModel/JsonTestClass.h b/src/cascadia/LocalTests_SettingsModel/JsonTestClass.h index 5cb0bf3f5..c39c6f0d2 100644 --- a/src/cascadia/LocalTests_SettingsModel/JsonTestClass.h +++ b/src/cascadia/LocalTests_SettingsModel/JsonTestClass.h @@ -7,44 +7,34 @@ Module Name: Abstract: - This class is a helper that can be used to quickly create tests that need to - read & parse json data. Test classes that need to read JSON should make sure - to derive from this class, and also make sure to call InitializeJsonReader() - in the TEST_CLASS_SETUP(). + read & parse json data. Author(s): Mike Griese (migrie) August-2019 --*/ +#pragma once + class JsonTestClass { public: - void InitializeJsonReader() + static Json::Value VerifyParseSucceeded(const std::string_view& content) { - _reader = std::unique_ptr(Json::CharReaderBuilder::CharReaderBuilder().newCharReader()); - }; + static const std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; - void InitializeJsonWriter() - { - _writer = std::unique_ptr(Json::StreamWriterBuilder::StreamWriterBuilder().newStreamWriter()); - } - - Json::Value VerifyParseSucceeded(std::string content) - { Json::Value root; std::string errs; - const bool parseResult = _reader->parse(content.c_str(), content.c_str() + content.size(), &root, &errs); + const bool parseResult = reader->parse(content.data(), content.data() + content.size(), &root, &errs); VERIFY_IS_TRUE(parseResult, winrt::to_hstring(errs).c_str()); return root; }; - std::string toString(const Json::Value& json) + static std::string toString(const Json::Value& json) { + static const std::unique_ptr writer{ Json::StreamWriterBuilder::StreamWriterBuilder().newStreamWriter() }; + std::stringstream s; - _writer->write(json, &s); + writer->write(json, &s); return s.str(); } - -protected: - std::unique_ptr _reader; - std::unique_ptr _writer; }; diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp index 211567e49..ad7a20113 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp @@ -59,12 +59,6 @@ namespace SettingsModelLocalTests TEST_METHOD(TestGetKeyBindingForAction); TEST_METHOD(KeybindingsWithoutVkey); - - TEST_CLASS_SETUP(ClassSetup) - { - InitializeJsonReader(); - return true; - } }; void KeyBindingsTests::KeyChords() diff --git a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp index 582a52c82..54c405592 100644 --- a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp @@ -7,6 +7,8 @@ #include "../TerminalSettingsModel/CascadiaSettings.h" #include "JsonTestClass.h" +#include + using namespace Microsoft::Console; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace WEX::Logging; @@ -32,81 +34,86 @@ namespace SettingsModelLocalTests TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") END_TEST_CLASS() - TEST_METHOD(CanLayerProfile); + TEST_METHOD(ProfileGeneratesGuid); TEST_METHOD(LayerProfileProperties); TEST_METHOD(LayerProfileIcon); TEST_METHOD(LayerProfilesOnArray); TEST_METHOD(DuplicateProfileTest); - - TEST_CLASS_SETUP(ClassSetup) - { - InitializeJsonReader(); - return true; - } + TEST_METHOD(TestGenGuidsForProfiles); }; - void ProfileTests::CanLayerProfile() + void ProfileTests::ProfileGeneratesGuid() { - const std::string profile0String{ R"({ - "name" : "profile0", - "guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}" - })" }; - const std::string profile1String{ R"({ - "name" : "profile1", - "guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}" - })" }; - const std::string profile2String{ R"({ - "name" : "profile2", - "guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}" - })" }; - const std::string profile3String{ R"({ - "name" : "profile3" - })" }; + // Parse some profiles without guids. We should NOT generate new guids + // for them. If a profile doesn't have a GUID, we'll leave its _guid + // set to nullopt. The Profile::Guid() getter will + // ensure all profiles have a GUID that's actually set. + // The null guid _is_ a valid guid, so we won't re-generate that + // guid. null is _not_ a valid guid, so we'll leave that nullopt - const auto profile0Json = VerifyParseSucceeded(profile0String); - const auto profile1Json = VerifyParseSucceeded(profile1String); - const auto profile2Json = VerifyParseSucceeded(profile2String); - const auto profile3Json = VerifyParseSucceeded(profile3String); + // See SettingsTests::ValidateProfilesGenerateGuids for a version of + // this test that includes synthesizing GUIDS for profiles without GUIDs + // set + + const std::string profileWithoutGuid{ R"({ + "name" : "profile0" + })" }; + const std::string secondProfileWithoutGuid{ R"({ + "name" : "profile1" + })" }; + const std::string profileWithNullForGuid{ R"({ + "name" : "profile2", + "guid" : null + })" }; + const std::string profileWithNullGuid{ R"({ + "name" : "profile3", + "guid" : "{00000000-0000-0000-0000-000000000000}" + })" }; + const std::string profileWithGuid{ R"({ + "name" : "profile4", + "guid" : "{6239a42c-1de4-49a3-80bd-e8fdd045185c}" + })" }; + + const auto profile0Json = VerifyParseSucceeded(profileWithoutGuid); + const auto profile1Json = VerifyParseSucceeded(secondProfileWithoutGuid); + const auto profile2Json = VerifyParseSucceeded(profileWithNullForGuid); + const auto profile3Json = VerifyParseSucceeded(profileWithNullGuid); + const auto profile4Json = VerifyParseSucceeded(profileWithGuid); const auto profile0 = implementation::Profile::FromJson(profile0Json); - - VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile1Json)); - VERIFY_IS_TRUE(profile0->ShouldBeLayered(profile2Json)); - VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile3Json)); - const auto profile1 = implementation::Profile::FromJson(profile1Json); - - VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile0Json)); - // A profile _can_ be layered with itself, though what's the point? - VERIFY_IS_TRUE(profile1->ShouldBeLayered(profile1Json)); - VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile2Json)); - VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile3Json)); - + const auto profile2 = implementation::Profile::FromJson(profile2Json); const auto profile3 = implementation::Profile::FromJson(profile3Json); + const auto profile4 = implementation::Profile::FromJson(profile4Json); + const winrt::guid cmdGuid = Utils::GuidFromString(L"{6239a42c-1de4-49a3-80bd-e8fdd045185c}"); + const winrt::guid nullGuid{}; - VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile0Json)); - // A profile _can_ be layered with itself, though what's the point? - VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile1Json)); - VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile2Json)); - VERIFY_IS_TRUE(profile3->ShouldBeLayered(profile3Json)); + VERIFY_IS_FALSE(profile0->HasGuid()); + VERIFY_IS_FALSE(profile1->HasGuid()); + VERIFY_IS_FALSE(profile2->HasGuid()); + VERIFY_IS_TRUE(profile3->HasGuid()); + VERIFY_IS_TRUE(profile4->HasGuid()); + + VERIFY_ARE_EQUAL(profile3->Guid(), nullGuid); + VERIFY_ARE_EQUAL(profile4->Guid(), cmdGuid); } void ProfileTests::LayerProfileProperties() { - const std::string profile0String{ R"({ + static constexpr std::string_view profile0String{ R"({ "name": "profile0", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "foreground": "#000000", "background": "#010101", "selectionBackground": "#010101" })" }; - const std::string profile1String{ R"({ + static constexpr std::string_view profile1String{ R"({ "name": "profile1", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "foreground": "#020202", "startingDirectory": "C:/" })" }; - const std::string profile2String{ R"({ + static constexpr std::string_view profile2String{ R"({ "name": "profile2", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "foreground": "#030303", @@ -172,21 +179,21 @@ namespace SettingsModelLocalTests void ProfileTests::LayerProfileIcon() { - const std::string profile0String{ R"({ + static constexpr std::string_view profile0String{ R"({ "name": "profile0", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "icon": "not-null.png" })" }; - const std::string profile1String{ R"({ + static constexpr std::string_view profile1String{ R"({ "name": "profile1", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "icon": null })" }; - const std::string profile2String{ R"({ + static constexpr std::string_view profile2String{ R"({ "name": "profile2", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" })" }; - const std::string profile3String{ R"({ + static constexpr std::string_view profile3String{ R"({ "name": "profile3", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "icon": "another-real.png" @@ -228,102 +235,95 @@ namespace SettingsModelLocalTests void ProfileTests::LayerProfilesOnArray() { - const std::string profile0String{ R"({ - "name" : "profile0", - "guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}" + static constexpr std::string_view inboxProfiles{ R"({ + "profiles": [ + { + "name" : "profile0", + "guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}" + }, { + "name" : "profile1", + "guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + }, { + "name" : "profile2", + "guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + } + ] })" }; - const std::string profile1String{ R"({ - "name" : "profile1", - "guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}" - })" }; - const std::string profile2String{ R"({ - "name" : "profile2", - "guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}" - })" }; - const std::string profile3String{ R"({ - "name" : "profile3", - "guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}" - })" }; - const std::string profile4String{ R"({ - "name" : "profile4", - "guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}" + static constexpr std::string_view userProfiles{ R"({ + "profiles": [ + { + "name" : "profile3", + "guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}" + }, { + "name" : "profile4", + "guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + } + ] })" }; - const auto profile0Json = VerifyParseSucceeded(profile0String); - const auto profile1Json = VerifyParseSucceeded(profile1String); - const auto profile2Json = VerifyParseSucceeded(profile2String); - const auto profile3Json = VerifyParseSucceeded(profile3String); - const auto profile4Json = VerifyParseSucceeded(profile4String); - - auto settings = winrt::make_self(); - - VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size()); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile0Json)); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json)); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json)); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile3Json)); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile4Json)); - - settings->_LayerOrCreateProfile(profile0Json); - VERIFY_ARE_EQUAL(1u, settings->_allProfiles.Size()); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json)); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json)); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json)); - - settings->_LayerOrCreateProfile(profile1Json); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json)); - VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json)); - - settings->_LayerOrCreateProfile(profile2Json); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json)); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name()); - - settings->_LayerOrCreateProfile(profile3Json); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json)); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(0).Name()); - - settings->_LayerOrCreateProfile(profile4Json); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json)); - VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json)); - VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(0).Name()); + const auto settings = winrt::make_self(userProfiles, inboxProfiles); + const auto allProfiles = settings->AllProfiles(); + VERIFY_ARE_EQUAL(3u, allProfiles.Size()); + VERIFY_ARE_EQUAL(L"profile3", allProfiles.GetAt(0).Name()); + VERIFY_ARE_EQUAL(L"profile4", allProfiles.GetAt(1).Name()); + VERIFY_ARE_EQUAL(L"profile2", allProfiles.GetAt(2).Name()); } void ProfileTests::DuplicateProfileTest() { - const std::string profile0String{ R"({ - "name" : "profile0", - "backgroundImage" : "some//path" + static constexpr std::string_view userProfiles{ R"({ + "profiles": [ + { + "name": "profile0", + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "backgroundImage": "file:///some/path", + "hidden": false, + } + ] })" }; - const auto profile0Json = VerifyParseSucceeded(profile0String); + const auto settings = winrt::make_self(userProfiles); + const auto profile = settings->AllProfiles().GetAt(0); + const auto duplicatedProfile = settings->DuplicateProfile(profile); - auto settings = winrt::make_self(); - - settings->_LayerOrCreateProfile(profile0Json); - auto duplicatedProfile = settings->DuplicateProfile(*settings->_FindMatchingProfile(profile0Json)); - duplicatedProfile.Name(L"profile0"); + duplicatedProfile.Guid(profile.Guid()); + duplicatedProfile.Name(profile.Name()); + const auto json = winrt::get_self(profile)->ToJson(); const auto duplicatedJson = winrt::get_self(duplicatedProfile)->ToJson(); - VERIFY_ARE_EQUAL(profile0Json, duplicatedJson); + VERIFY_ARE_EQUAL(json, duplicatedJson, til::u8u16(toString(duplicatedJson)).c_str()); + } + + void ProfileTests::TestGenGuidsForProfiles() + { + // We'll generate GUIDs in the Profile::Guid getter. We should make sure that + // the GUID generated for a dynamic profile (with a source) is different + // than that of a profile without a source. + + static constexpr std::string_view userSettings{ R"({ + "profiles": [ + { + "name": "profile0", + "source": "Terminal.App.UnitTest.0", + }, + { + "name": "profile0" + } + ] + })" }; + + const auto settings = winrt::make_self(userSettings, DefaultJson); + + VERIFY_ARE_EQUAL(4u, settings->AllProfiles().Size()); + + VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(0).Name()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(0).HasGuid()); + VERIFY_IS_FALSE(settings->AllProfiles().GetAt(0).Source().empty()); + + VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(1).Name()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(1).HasGuid()); + VERIFY_IS_TRUE(settings->AllProfiles().GetAt(1).Source().empty()); + + VERIFY_ARE_NOT_EQUAL(settings->AllProfiles().GetAt(0).Guid(), settings->AllProfiles().GetAt(1).Guid()); } } diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp index 54d5863e0..e538967f9 100644 --- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp @@ -8,7 +8,6 @@ #include "JsonTestClass.h" #include "TestUtils.h" #include -#include "../ut_app/TestDynamicProfileGenerator.h" using namespace Microsoft::Console; using namespace WEX::Logging; @@ -43,13 +42,6 @@ namespace SettingsModelLocalTests TEST_METHOD(CascadiaSettings); TEST_METHOD(LegacyFontSettings); - TEST_CLASS_SETUP(ClassSetup) - { - InitializeJsonReader(); - InitializeJsonWriter(); - return true; - } - private: // Method Description: // - deserializes and reserializes a json string representing a settings object model of type T @@ -309,10 +301,10 @@ namespace SettingsModelLocalTests ])" }; const std::string actionsString9B{ R"([ { - "commands": + "commands": [ { - "command": + "command": { "action": "sendInput", "input": "${profile.name}" @@ -325,13 +317,13 @@ namespace SettingsModelLocalTests ])" }; const std::string actionsString9C{ R""([ { - "commands": + "commands": [ { - "commands": + "commands": [ { - "command": + "command": { "action": "sendInput", "input": "${profile.name} ${scheme.name}" @@ -348,7 +340,7 @@ namespace SettingsModelLocalTests ])"" }; const std::string actionsString9D{ R""([ { - "command": + "command": { "action": "newTab", "profile": "${profile.name}" @@ -404,75 +396,71 @@ namespace SettingsModelLocalTests void SerializationTests::CascadiaSettings() { const std::string settingsString{ R"({ - "$schema": "https://aka.ms/terminal-profiles-schema", - "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", - "disabledProfileSources": [ "Windows.Terminal.Wsl" ], + "$help" : "https://aka.ms/terminal-documentation", + "$schema" : "https://aka.ms/terminal-profiles-schema", + "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", + "disabledProfileSources": [ "Windows.Terminal.Wsl" ], + "profiles": { + "defaults": { + "font": { + "face": "Zamora Code" + } + }, + "list": [ + { + "font": { "face": "Cascadia Code" }, + "guid": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", + "name": "HowettShell" + }, + { + "hidden": true, + "guid": "{c08b0496-e71c-5503-b84e-3af7a7a6d2a7}", + "name": "BhojwaniShell" + }, + { + "antialiasingMode": "aliased", + "guid": "{fe9df758-ac22-5c20-922d-c7766cdd13af}", + "name": "NiksaShell" + } + ] + }, + "schemes": [ + { + "name": "Cinnamon Roll", - "profiles": { - "defaults": { - "font": { - "face": "Zamora Code" - } - }, - "list": [ - { - "font": { "face": "Cascadia Code" }, - "guid": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", - "name": "HowettShell" - }, - { - "hidden": true, - "name": "BhojwaniShell" - }, - { - "antialiasingMode": "aliased", - "name": "NiksaShell" - } - ] - }, - "schemes": [ - { - "name": "Cinnamon Roll", + "cursorColor": "#FFFFFD", + "selectionBackground": "#FFFFFF", - "cursorColor": "#FFFFFD", - "selectionBackground": "#FFFFFF", + "background": "#3C0315", + "foreground": "#FFFFFD", - "background": "#3C0315", - "foreground": "#FFFFFD", + "black": "#282A2E", + "blue": "#0170C5", + "cyan": "#3F8D83", + "green": "#76AB23", + "purple": "#7D498F", + "red": "#BD0940", + "white": "#FFFFFD", + "yellow": "#E0DE48", + "brightBlack": "#676E7A", + "brightBlue": "#5C98C5", + "brightCyan": "#8ABEB7", + "brightGreen": "#B5D680", + "brightPurple": "#AC79BB", + "brightRed": "#BD6D85", + "brightWhite": "#FFFFFD", + "brightYellow": "#FFFD76" + } + ], + "actions": [ + { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "keys": "ctrl+k" } + ] + })" }; - "black": "#282A2E", - "blue": "#0170C5", - "cyan": "#3F8D83", - "green": "#76AB23", - "purple": "#7D498F", - "red": "#BD0940", - "white": "#FFFFFD", - "yellow": "#E0DE48", - "brightBlack": "#676E7A", - "brightBlue": "#5C98C5", - "brightCyan": "#8ABEB7", - "brightGreen": "#B5D680", - "brightPurple": "#AC79BB", - "brightRed": "#BD6D85", - "brightWhite": "#FFFFFD", - "brightYellow": "#FFFD76" - } - ], - "actions": [ - { "command": { "action": "renameTab", "title": "Liang Tab" }, "keys": "ctrl+t" }, - { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "keys": "ctrl+k" }, - { "command": { "action": "renameWindow", "name": "Hecker Window" }, "keys": "ctrl+l" } - ] - })" }; - - auto settings{ winrt::make_self(false) }; - settings->_ParseJsonString(settingsString, false); - settings->_ApplyDefaultsFromUserSettings(); - settings->LayerJson(settings->_userSettings); - settings->_ValidateSettings(); + const auto settings{ winrt::make_self(settingsString) }; const auto result{ settings->ToJson() }; - VERIFY_ARE_EQUAL(toString(settings->_userSettings), toString(result)); + VERIFY_ARE_EQUAL(toString(VerifyParseSucceeded(settingsString)), toString(result)); } void SerializationTests::LegacyFontSettings() diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp index 498f6822e..0fa507a9d 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -62,7 +62,7 @@ namespace SettingsModelLocalTests void TerminalSettingsTests::TestTerminalArgsForBinding() { - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": { "list": [ @@ -106,12 +106,12 @@ namespace SettingsModelLocalTests const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") }; const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") }; - CascadiaSettings settings{ til::u8u16(settingsJson) }; + const auto settings = winrt::make_self(settingsJson); - auto actionMap = settings.GlobalSettings().ActionMap(); - VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); + auto actionMap = settings->GlobalSettings().ActionMap(); + VERIFY_ARE_EQUAL(3u, settings->ActiveProfiles().Size()); - const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid(); + const auto profile2Guid = settings->ActiveProfiles().GetAt(2).Guid(); VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); const auto& actionMapImpl{ winrt::get_self(actionMap) }; @@ -131,8 +131,8 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -153,8 +153,8 @@ namespace SettingsModelLocalTests VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); @@ -175,8 +175,8 @@ namespace SettingsModelLocalTests VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); @@ -197,8 +197,8 @@ namespace SettingsModelLocalTests VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); @@ -219,13 +219,13 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled()) { // This action specified a command but no profile; it gets reassigned to the base profile - VERIFY_ARE_EQUAL(settings.ProfileDefaults(), profile); + VERIFY_ARE_EQUAL(settings->ProfileDefaults(), profile); VERIFY_ARE_EQUAL(29, termSettings.HistorySize()); } else @@ -251,8 +251,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); @@ -271,8 +271,8 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -292,8 +292,8 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -315,8 +315,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); @@ -337,8 +337,8 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -360,8 +360,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); @@ -385,8 +385,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); @@ -399,7 +399,7 @@ namespace SettingsModelLocalTests void TerminalSettingsTests::MakeSettingsForProfile() { // Test that making settings generally works. - const std::string settingsString{ R"( + static constexpr std::string_view settingsString{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -415,17 +415,17 @@ namespace SettingsModelLocalTests } ] })" }; - CascadiaSettings settings{ til::u8u16(settingsString) }; + const auto settings = winrt::make_self(settingsString); const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); - const auto profile1 = settings.FindProfile(guid1); - const auto profile2 = settings.FindProfile(guid2); + const auto profile1 = settings->FindProfile(guid1); + const auto profile2 = settings->FindProfile(guid2); try { - auto terminalSettings{ TerminalSettings::CreateWithProfile(settings, profile1, nullptr) }; + auto terminalSettings{ TerminalSettings::CreateWithProfile(*settings, profile1, nullptr) }; VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); VERIFY_ARE_EQUAL(1, terminalSettings.DefaultSettings().HistorySize()); } @@ -436,7 +436,7 @@ namespace SettingsModelLocalTests try { - auto terminalSettings{ TerminalSettings::CreateWithProfile(settings, profile2, nullptr) }; + auto terminalSettings{ TerminalSettings::CreateWithProfile(*settings, profile2, nullptr) }; VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); VERIFY_ARE_EQUAL(2, terminalSettings.DefaultSettings().HistorySize()); } @@ -447,7 +447,7 @@ namespace SettingsModelLocalTests try { - const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(*settings, nullptr, nullptr) }; VERIFY_ARE_NOT_EQUAL(nullptr, termSettings); VERIFY_ARE_EQUAL(1, termSettings.DefaultSettings().HistorySize()); } @@ -463,7 +463,7 @@ namespace SettingsModelLocalTests // defaultProfile that's not in the list, we validate the settings, and // then call MakeSettings(nullopt). The validation should ensure that // the default profile is something reasonable - const std::string settingsString{ R"( + static constexpr std::string_view settingsString{ R"( { "defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -479,14 +479,14 @@ namespace SettingsModelLocalTests } ] })" }; - CascadiaSettings settings{ til::u8u16(settingsString) }; + const auto settings = winrt::make_self(settingsString); - VERIFY_ARE_EQUAL(2u, settings.Warnings().Size()); - VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size()); - VERIFY_ARE_EQUAL(settings.GlobalSettings().DefaultProfile(), settings.ActiveProfiles().GetAt(0).Guid()); + VERIFY_ARE_EQUAL(2u, settings->Warnings().Size()); + VERIFY_ARE_EQUAL(2u, settings->ActiveProfiles().Size()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), settings->ActiveProfiles().GetAt(0).Guid()); try { - const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(*settings, nullptr, nullptr) }; VERIFY_ARE_NOT_EQUAL(nullptr, termSettings); VERIFY_ARE_EQUAL(1, termSettings.DefaultSettings().HistorySize()); } @@ -501,7 +501,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly.")); - const std::string settings0String{ R"( + static constexpr std::string_view settings0String{ R"( { "defaultProfile": "profile5", "profiles": [ @@ -534,18 +534,50 @@ namespace SettingsModelLocalTests "schemes": [ { "name": "schemeWithCursorColor", - "cursorColor": "#123456" + "cursorColor": "#123456", + "black": "#121314", + "red": "#121314", + "green": "#121314", + "yellow": "#121314", + "blue": "#121314", + "purple": "#121314", + "cyan": "#121314", + "white": "#121314", + "brightBlack": "#121314", + "brightRed": "#121314", + "brightGreen": "#121314", + "brightYellow": "#121314", + "brightBlue": "#121314", + "brightPurple": "#121314", + "brightCyan": "#121314", + "brightWhite": "#121314" }, { - "name": "schemeWithoutCursorColor" + "name": "schemeWithoutCursorColor", + "black": "#121314", + "red": "#121314", + "green": "#121314", + "yellow": "#121314", + "blue": "#121314", + "purple": "#121314", + "cyan": "#121314", + "white": "#121314", + "brightBlack": "#121314", + "brightRed": "#121314", + "brightGreen": "#121314", + "brightYellow": "#121314", + "brightBlue": "#121314", + "brightPurple": "#121314", + "brightCyan": "#121314", + "brightWhite": "#121314" } ] })" }; - CascadiaSettings settings{ til::u8u16(settings0String) }; + const auto settings = winrt::make_self(settings0String); - VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size()); - VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size()); + VERIFY_ARE_EQUAL(6u, settings->ActiveProfiles().Size()); + VERIFY_ARE_EQUAL(2u, settings->GlobalSettings().ColorSchemes().Size()); auto createTerminalSettings = [&](const auto& profile, const auto& schemes) { auto terminalSettings{ winrt::make_self() }; @@ -554,12 +586,14 @@ namespace SettingsModelLocalTests return terminalSettings; }; - auto terminalSettings0 = createTerminalSettings(settings.ActiveProfiles().GetAt(0), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings1 = createTerminalSettings(settings.ActiveProfiles().GetAt(1), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings2 = createTerminalSettings(settings.ActiveProfiles().GetAt(2), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings3 = createTerminalSettings(settings.ActiveProfiles().GetAt(3), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings4 = createTerminalSettings(settings.ActiveProfiles().GetAt(4), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings5 = createTerminalSettings(settings.ActiveProfiles().GetAt(5), settings.GlobalSettings().ColorSchemes()); + const auto activeProfiles = settings->ActiveProfiles(); + const auto colorSchemes = settings->GlobalSettings().ColorSchemes(); + const auto terminalSettings0 = createTerminalSettings(activeProfiles.GetAt(0), colorSchemes); + const auto terminalSettings1 = createTerminalSettings(activeProfiles.GetAt(1), colorSchemes); + const auto terminalSettings2 = createTerminalSettings(activeProfiles.GetAt(2), colorSchemes); + const auto terminalSettings3 = createTerminalSettings(activeProfiles.GetAt(3), colorSchemes); + const auto terminalSettings4 = createTerminalSettings(activeProfiles.GetAt(4), colorSchemes); + const auto terminalSettings5 = createTerminalSettings(activeProfiles.GetAt(5), colorSchemes); VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default @@ -571,7 +605,7 @@ namespace SettingsModelLocalTests void TerminalSettingsTests::TestCommandlineToTitlePromotion() { - const std::string settingsJson{ R"( + static constexpr std::string_view settingsJson{ R"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": { "list": [ @@ -587,65 +621,63 @@ namespace SettingsModelLocalTests } } })" }; - const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") }; - - CascadiaSettings settings{ til::u8u16(settingsJson) }; + const auto settings = winrt::make_self(settingsJson); { // just a profile (profile wins) NewTerminalArgs args{}; args.Profile(L"profile0"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle()); } { // profile and command line -> no promotion (profile wins) NewTerminalArgs args{}; args.Profile(L"profile0"); args.Commandline(L"foo.exe"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle()); } { // just a title -> it is propagated NewTerminalArgs args{}; args.TabTitle(L"Analog Kid"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"Analog Kid", settingsStruct.DefaultSettings().StartingTitle()); } { // title and command line -> no promotion NewTerminalArgs args{}; args.TabTitle(L"Digital Man"); args.Commandline(L"foo.exe"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"Digital Man", settingsStruct.DefaultSettings().StartingTitle()); } { // just a commandline -> promotion NewTerminalArgs args{}; args.Commandline(L"foo.exe"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle()); } // various typesof commandline follow { NewTerminalArgs args{}; args.Commandline(L"foo.exe bar"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle()); } { NewTerminalArgs args{}; args.Commandline(L"\"foo exe.exe\" bar"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"foo exe.exe", settingsStruct.DefaultSettings().StartingTitle()); } { NewTerminalArgs args{}; args.Commandline(L"\"\" grand designs"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle()); } { NewTerminalArgs args{}; args.Commandline(L" imagine a man"); - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) }; VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle()); } } diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index ff83a2ae5..fd7a8e446 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -79,7 +79,7 @@ namespace TerminalAppLocalTests // containing a ${profile.name} to replace. When we expand it, it should // have created one command for each profile. - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -111,10 +111,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}"); - const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -207,7 +204,7 @@ namespace TerminalAppLocalTests // For this test, put an iterable command without a given `name` to // replace. When we expand it, it should still work. - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -238,10 +235,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}"); - const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -335,7 +329,7 @@ namespace TerminalAppLocalTests // cause bad json to be filled in. Something like a profile with a name // of "Foo\"", so the trailing '"' might break the json parsing. - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -367,10 +361,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}"); - const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -468,7 +459,7 @@ namespace TerminalAppLocalTests // ├─ first.com // └─ second.com - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -508,7 +499,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -558,7 +549,7 @@ namespace TerminalAppLocalTests // ├─ child1 // └─ child2 - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -603,7 +594,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -691,7 +682,7 @@ namespace TerminalAppLocalTests // ├─ Split pane, direction: right, profile: profile2 // └─ Split pane, direction: down, profile: profile2 - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -727,7 +718,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -828,7 +819,7 @@ namespace TerminalAppLocalTests // ├─ Profile 2 // └─ Profile 3 - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -864,7 +855,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -926,7 +917,7 @@ namespace TerminalAppLocalTests // ├─ Split right // └─ Split down - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -967,7 +958,7 @@ namespace TerminalAppLocalTests "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. })" }; - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -1071,7 +1062,7 @@ namespace TerminalAppLocalTests // containing a ${profile.name} to replace. When we expand it, it should // have created one command for each profile. - const std::string settingsJson{ R"( + static constexpr std::wstring_view settingsJson{ LR"( { "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -1107,7 +1098,7 @@ namespace TerminalAppLocalTests ] })" }; - CascadiaSettings settings{ til::u8u16(settingsJson) }; + CascadiaSettings settings{ settingsJson, {} }; // Since at least one profile does not reference a color scheme, // we add a warning saying "the color scheme is unknown" diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 782ae265f..4e3da70aa 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -311,7 +311,7 @@ namespace TerminalAppLocalTests // TerminalPage and not only create them successfully, but also create a // tab using those settings successfully. - const std::string settingsJson0{ R"( + static constexpr std::wstring_view settingsJson0{ LR"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -328,7 +328,7 @@ namespace TerminalAppLocalTests ] })" }; - CascadiaSettings settings0{ til::u8u16(settingsJson0) }; + CascadiaSettings settings0{ settingsJson0, {} }; VERIFY_IS_NOT_NULL(settings0); // This is super wacky, but we can't just initialize the @@ -357,7 +357,7 @@ namespace TerminalAppLocalTests // // Created to test GH#2455 - const std::string settingsJson0{ R"( + static constexpr std::wstring_view settingsJson0{ LR"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -374,7 +374,7 @@ namespace TerminalAppLocalTests ] })" }; - const std::string settingsJson1{ R"( + static constexpr std::wstring_view settingsJson1{ LR"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -386,10 +386,10 @@ namespace TerminalAppLocalTests ] })" }; - CascadiaSettings settings0{ til::u8u16(settingsJson0) }; + CascadiaSettings settings0{ settingsJson0, {} }; VERIFY_IS_NOT_NULL(settings0); - CascadiaSettings settings1{ til::u8u16(settingsJson1) }; + CascadiaSettings settings1{ settingsJson1, {} }; VERIFY_IS_NOT_NULL(settings1); const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); @@ -444,7 +444,7 @@ namespace TerminalAppLocalTests // // Created to test GH#2455 - const std::string settingsJson0{ R"( + static constexpr std::wstring_view settingsJson0{ LR"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -461,7 +461,7 @@ namespace TerminalAppLocalTests ] })" }; - const std::string settingsJson1{ R"( + static constexpr std::wstring_view settingsJson1{ LR"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ @@ -473,10 +473,10 @@ namespace TerminalAppLocalTests ] })" }; - CascadiaSettings settings0{ til::u8u16(settingsJson0) }; + CascadiaSettings settings0{ settingsJson0, {} }; VERIFY_IS_NOT_NULL(settings0); - CascadiaSettings settings1{ til::u8u16(settingsJson1) }; + CascadiaSettings settings1{ settingsJson1, {} }; VERIFY_IS_NOT_NULL(settings1); const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); @@ -558,7 +558,7 @@ namespace TerminalAppLocalTests // - The initialized TerminalPage, ready to use. winrt::com_ptr TabTests::_commonSetup() { - const std::string settingsJson0{ R"( + static constexpr std::wstring_view settingsJson0{ LR"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "showTabsInTitlebar": false, @@ -659,7 +659,7 @@ namespace TerminalAppLocalTests ] })" }; - CascadiaSettings settings0{ til::u8u16(settingsJson0) }; + CascadiaSettings settings0{ settingsJson0, {} }; VERIFY_IS_NOT_NULL(settings0); const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 26d7cdb8a..08032f1c8 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -29,12 +29,12 @@ namespace winrt using IInspectable = Windows::Foundation::IInspectable; } -static const winrt::hstring StartupTaskName = L"StartTerminalOnLoginTask"; +static constexpr std::wstring_view StartupTaskName = L"StartTerminalOnLoginTask"; // clang-format off // !!! IMPORTANT !!! // Make sure that these keys are in the same order as the // SettingsLoadWarnings/Errors enum is! -static const std::array(SettingsLoadWarnings::WARNINGS_SIZE)> settingsLoadWarningsLabels { +static const std::array settingsLoadWarningsLabels { USES_RESOURCE(L"MissingDefaultProfileText"), USES_RESOURCE(L"DuplicateProfileText"), USES_RESOURCE(L"UnknownColorSchemeText"), @@ -43,7 +43,6 @@ static const std::array(SettingsLoadWar USES_RESOURCE(L"AtLeastOneKeybindingWarning"), USES_RESOURCE(L"TooManyKeysForChord"), USES_RESOURCE(L"MissingRequiredParameter"), - USES_RESOURCE(L"LegacyGlobalsProperty"), USES_RESOURCE(L"FailedToParseCommandJson"), USES_RESOURCE(L"FailedToWriteToSettings"), USES_RESOURCE(L"InvalidColorSchemeInCmd"), @@ -51,12 +50,15 @@ static const std::array(SettingsLoadWar USES_RESOURCE(L"FailedToParseStartupActions"), USES_RESOURCE(L"FailedToParseSubCommands"), }; -static const std::array(SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels { +static const std::array settingsLoadErrorsLabels { USES_RESOURCE(L"NoProfilesText"), USES_RESOURCE(L"AllProfilesHiddenText") }; // clang-format on +static_assert(settingsLoadWarningsLabels.size() == static_cast(SettingsLoadWarnings::WARNINGS_SIZE)); +static_assert(settingsLoadErrorsLabels.size() == static_cast(SettingsLoadErrors::ERRORS_SIZE)); + // Function Description: // - General-purpose helper for looking up a localized string for a // warning/error. First will look for the given key in the provided map of @@ -68,12 +70,12 @@ static const std::array(SettingsLoadErr // - map: A map of keys->Resource keys. // Return Value: // - the localized string for the given type, if it exists. -template -static winrt::hstring _GetMessageText(uint32_t index, std::array keys) +template +winrt::hstring _GetMessageText(uint32_t index, const T& keys) { if (index < keys.size()) { - return GetLibraryResourceString(keys.at(index)); + return GetLibraryResourceString(til::at(keys, index)); } return {}; } @@ -488,27 +490,6 @@ namespace winrt::TerminalApp::implementation if (!warningText.empty()) { warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources())); - - // The "LegacyGlobalsProperty" warning is special - it has a URL - // that goes with it. So we need to manually construct a - // Hyperlink and insert it along with the warning text. - if (warning == SettingsLoadWarnings::LegacyGlobalsProperty) - { - // Add the URL here too - const auto legacyGlobalsLinkLabel = RS_(L"LegacyGlobalsPropertyHrefLabel"); - const auto legacyGlobalsLinkUriValue = RS_(L"LegacyGlobalsPropertyHrefUrl"); - - winrt::Windows::UI::Xaml::Documents::Run legacyGlobalsLinkText; - winrt::Windows::UI::Xaml::Documents::Hyperlink legacyGlobalsLink; - winrt::Windows::Foundation::Uri legacyGlobalsLinkUri{ legacyGlobalsLinkUriValue }; - - legacyGlobalsLinkText.Text(legacyGlobalsLinkLabel); - legacyGlobalsLink.NavigateUri(legacyGlobalsLinkUri); - legacyGlobalsLink.Inlines().Append(legacyGlobalsLinkText); - - warningsTextBlock.Inlines().Append(legacyGlobalsLink); - } - warningsTextBlock.Inlines().Append(Documents::LineBreak{}); } } diff --git a/src/cascadia/TerminalSettingsEditor/Launch.cpp b/src/cascadia/TerminalSettingsEditor/Launch.cpp index 09215561d..4dee9ac5f 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.cpp +++ b/src/cascadia/TerminalSettingsEditor/Launch.cpp @@ -34,7 +34,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void Launch::OnNavigatedTo(const NavigationEventArgs& e) { _State = e.Parameter().as(); - _State.Settings().RefreshDefaultTerminals(); } IInspectable Launch::CurrentDefaultProfile() diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index ece569aff..ce6e35ef2 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -74,7 +74,7 @@ x:Uid="Globals_DefaultTerminal" x:Load="false"> diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp index 1f8c88758..2384de2ff 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp @@ -29,30 +29,29 @@ static constexpr std::string_view IntenseTextStyleKey{ "intenseTextStyle" }; static constexpr std::string_view LegacyAcrylicTransparencyKey{ "acrylicOpacity" }; static constexpr std::string_view OpacityKey{ "opacity" }; -winrt::Microsoft::Terminal::Settings::Model::implementation::AppearanceConfig::AppearanceConfig(const winrt::weak_ref sourceProfile) : - _sourceProfile(sourceProfile) +AppearanceConfig::AppearanceConfig(winrt::weak_ref sourceProfile) : + _sourceProfile(std::move(sourceProfile)) { } -winrt::com_ptr AppearanceConfig::CopyAppearance(const winrt::com_ptr source, const winrt::weak_ref sourceProfile) +winrt::com_ptr AppearanceConfig::CopyAppearance(const AppearanceConfig* source, winrt::weak_ref sourceProfile) { - auto appearance{ winrt::make_self(sourceProfile) }; - auto const sourceAppearance = source.try_as(); - appearance->_BackgroundImagePath = sourceAppearance->_BackgroundImagePath; - appearance->_BackgroundImageOpacity = sourceAppearance->_BackgroundImageOpacity; - appearance->_BackgroundImageStretchMode = sourceAppearance->_BackgroundImageStretchMode; - appearance->_ColorSchemeName = sourceAppearance->_ColorSchemeName; - appearance->_Foreground = sourceAppearance->_Foreground; - appearance->_Background = sourceAppearance->_Background; - appearance->_SelectionBackground = sourceAppearance->_SelectionBackground; - appearance->_CursorColor = sourceAppearance->_CursorColor; - appearance->_CursorShape = sourceAppearance->_CursorShape; - appearance->_CursorHeight = sourceAppearance->_CursorHeight; - appearance->_BackgroundImageAlignment = sourceAppearance->_BackgroundImageAlignment; - appearance->_RetroTerminalEffect = sourceAppearance->_RetroTerminalEffect; - appearance->_PixelShaderPath = sourceAppearance->_PixelShaderPath; - appearance->_IntenseTextStyle = sourceAppearance->_IntenseTextStyle; - appearance->_Opacity = sourceAppearance->_Opacity; + auto appearance{ winrt::make_self(std::move(sourceProfile)) }; + appearance->_BackgroundImagePath = source->_BackgroundImagePath; + appearance->_BackgroundImageOpacity = source->_BackgroundImageOpacity; + appearance->_BackgroundImageStretchMode = source->_BackgroundImageStretchMode; + appearance->_ColorSchemeName = source->_ColorSchemeName; + appearance->_Foreground = source->_Foreground; + appearance->_Background = source->_Background; + appearance->_SelectionBackground = source->_SelectionBackground; + appearance->_CursorColor = source->_CursorColor; + appearance->_CursorShape = source->_CursorShape; + appearance->_CursorHeight = source->_CursorHeight; + appearance->_BackgroundImageAlignment = source->_BackgroundImageAlignment; + appearance->_RetroTerminalEffect = source->_RetroTerminalEffect; + appearance->_PixelShaderPath = source->_PixelShaderPath; + appearance->_IntenseTextStyle = source->_IntenseTextStyle; + appearance->_Opacity = source->_Opacity; return appearance; } diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h index d28b9a92c..e15f5ef82 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h @@ -18,7 +18,6 @@ Author(s): #include "AppearanceConfig.g.h" #include "JsonUtils.h" -#include "../inc/cppwinrt_utils.h" #include "IInheritable.h" #include @@ -27,8 +26,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct AppearanceConfig : AppearanceConfigT, IInheritable { public: - AppearanceConfig(const winrt::weak_ref sourceProfile); - static winrt::com_ptr CopyAppearance(const winrt::com_ptr source, const winrt::weak_ref sourceProfile); + AppearanceConfig(winrt::weak_ref sourceProfile); + static winrt::com_ptr CopyAppearance(const AppearanceConfig* source, winrt::weak_ref sourceProfile); Json::Value ToJson() const; void LayerJson(const Json::Value& json); diff --git a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp index 41b26163d..a69f44eae 100644 --- a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp @@ -6,16 +6,14 @@ #include "AzureCloudShellGenerator.h" #include "LegacyProfileGeneratorNamespaces.h" -#include "../../types/inc/utils.hpp" #include "../../inc/DefaultSettings.h" -#include "Utils.h" -#include "DefaultProfileUtils.h" +#include "DynamicProfileUtils.h" using namespace ::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::TerminalConnection; -std::wstring_view AzureCloudShellGenerator::GetNamespace() +std::wstring_view AzureCloudShellGenerator::GetNamespace() const noexcept { return AzureGeneratorNamespace; } @@ -27,19 +25,14 @@ std::wstring_view AzureCloudShellGenerator::GetNamespace() // - // Return Value: // - a vector with the Azure Cloud Shell connection profile, if available. -std::vector AzureCloudShellGenerator::GenerateProfiles() +void AzureCloudShellGenerator::GenerateProfiles(std::vector>& profiles) const { - std::vector profiles; - if (AzureConnection::IsAzureConnectionAvailable()) { - auto azureCloudShellProfile{ CreateDefaultProfile(L"Azure Cloud Shell") }; - azureCloudShellProfile.Commandline(L"Azure"); - azureCloudShellProfile.StartingDirectory(DEFAULT_STARTING_DIRECTORY); - azureCloudShellProfile.DefaultAppearance().ColorSchemeName(L"Vintage"); - azureCloudShellProfile.ConnectionType(AzureConnection::ConnectionType()); - profiles.emplace_back(azureCloudShellProfile); + auto azureCloudShellProfile{ CreateDynamicProfile(L"Azure Cloud Shell") }; + azureCloudShellProfile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY }); + azureCloudShellProfile->DefaultAppearance().ColorSchemeName(L"Vintage"); + azureCloudShellProfile->ConnectionType(AzureConnection::ConnectionType()); + profiles.emplace_back(std::move(azureCloudShellProfile)); } - - return profiles; } diff --git a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h index 79ad05a31..f62b66fce 100644 --- a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h +++ b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h @@ -16,17 +16,15 @@ Author(s): --*/ #pragma once + #include "IDynamicProfileGenerator.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { - class AzureCloudShellGenerator : public IDynamicProfileGenerator + class AzureCloudShellGenerator final : public IDynamicProfileGenerator { public: - AzureCloudShellGenerator() = default; - ~AzureCloudShellGenerator() = default; - std::wstring_view GetNamespace() override; - - std::vector GenerateProfiles() override; + std::wstring_view GetNamespace() const noexcept override; + void GenerateProfiles(std::vector>& profiles) const override; }; }; diff --git a/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.cpp b/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.cpp index 14a61f33b..cb351935f 100644 --- a/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.cpp @@ -3,15 +3,12 @@ #include "pch.h" #include "BaseVisualStudioGenerator.h" -#include "DefaultProfileUtils.h" +#include "DynamicProfileUtils.h" -using namespace Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Settings::Model; -std::vector BaseVisualStudioGenerator::GenerateProfiles() +void BaseVisualStudioGenerator::GenerateProfiles(std::vector>& profiles) const { - std::vector profiles; - // There's no point in enumerating valid Visual Studio instances more than once, // so cache them for use by both Visual Studio profile generators. static const auto instances = VsSetupConfiguration::QueryInstances(); @@ -25,27 +22,15 @@ std::vector BaseVisualStudioGenerator::GenerateProfiles() continue; } - auto DevShell{ CreateProfile(GetProfileGuidSeed(instance)) }; - DevShell.Name(GetProfileName(instance)); - DevShell.Commandline(GetProfileCommandLine(instance)); - DevShell.StartingDirectory(instance.GetInstallationPath()); - DevShell.Icon(GetProfileIconPath()); - - profiles.emplace_back(DevShell); + const auto seed = GetProfileGuidSeed(instance); + const winrt::guid profileGuid{ ::Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(seed))) }; + auto profile = winrt::make_self(profileGuid); + profile->Name(winrt::hstring{ GetProfileName(instance) }); + profile->Commandline(winrt::hstring{ GetProfileCommandLine(instance) }); + profile->StartingDirectory(winrt::hstring{ instance.GetInstallationPath() }); + profile->Icon(winrt::hstring{ GetProfileIconPath() }); + profiles.emplace_back(std::move(profile)); } CATCH_LOG(); } - - return profiles; -} - -Profile BaseVisualStudioGenerator::CreateProfile(const std::wstring_view seed) -{ - const winrt::guid profileGuid{ Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, - gsl::as_bytes(gsl::make_span(seed))) }; - - auto newProfile = winrt::make_self(profileGuid); - newProfile->Origin(OriginTag::Generated); - - return *newProfile; } diff --git a/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.h b/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.h index eba2ea598..e2ea620b8 100644 --- a/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.h +++ b/src/cascadia/TerminalSettingsModel/BaseVisualStudioGenerator.h @@ -18,14 +18,12 @@ Author(s): #include "IDynamicProfileGenerator.h" #include "VsSetupConfiguration.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { class BaseVisualStudioGenerator : public IDynamicProfileGenerator { public: - // Inherited via IDynamicProfileGenerator - std::wstring_view GetNamespace() override = 0; - std::vector GenerateProfiles() override; + void GenerateProfiles(std::vector>& profiles) const override; protected: virtual bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const = 0; @@ -33,8 +31,5 @@ namespace Microsoft::Terminal::Settings::Model virtual std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const = 0; virtual std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const = 0; virtual std::wstring GetProfileIconPath() const = 0; - - private: - winrt::Microsoft::Terminal::Settings::Model::Profile CreateProfile(const std::wstring_view instanceId); }; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index c2b2fceb2..61521ade2 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -6,137 +6,99 @@ #include "CascadiaSettings.g.cpp" #include +#include -#include "AzureCloudShellGenerator.h" -#include "PowershellCoreProfileGenerator.h" -#include "VsDevCmdGenerator.h" -#include "VsDevShellGenerator.h" -#include "WslDistroGenerator.h" - -using namespace ::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal; -using namespace winrt::Microsoft::Terminal::Control; +using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; +using namespace winrt::Microsoft::Terminal::Control; using namespace winrt::Windows::Foundation::Collections; using namespace Microsoft::Console; -static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///ProfileIcons/" }; - -static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" }; -static constexpr std::wstring_view DEFAULT_LINUX_ICON_GUID{ L"{9acb9455-ca41-5af7-950f-6bca1bc9722f}" }; - -// make sure this matches defaults.json. -static constexpr std::wstring_view DEFAULT_WINDOWS_POWERSHELL_GUID{ L"{61c54bbd-c2c6-5271-96e7-009a87ff44bf}" }; - -CascadiaSettings::CascadiaSettings() : - CascadiaSettings(true) +winrt::com_ptr Model::implementation::CreateChild(const winrt::com_ptr& parent) { + auto profile = winrt::make_self(); + profile->Origin(OriginTag::User); + profile->Name(parent->Name()); + profile->Guid(parent->Guid()); + profile->Hidden(parent->Hidden()); + profile->InsertParent(parent); + return profile; } -// Constructor Description: -// - Creates a new settings object. If addDynamicProfiles is true, we'll -// automatically add the built-in profile generators to our list of profile -// generators. Set this to `false` for unit testing. -// Arguments: -// - addDynamicProfiles: if true, we'll add the built-in DPGs. -CascadiaSettings::CascadiaSettings(const bool addDynamicProfiles) : - _globals{ winrt::make_self() }, - _allProfiles{ winrt::single_threaded_observable_vector() }, - _activeProfiles{ winrt::single_threaded_observable_vector() }, - _warnings{ winrt::single_threaded_vector() }, - _deserializationErrorMessage{ L"" }, - _defaultTerminals{ winrt::single_threaded_observable_vector() }, - _currentDefaultTerminal{ nullptr } +Model::CascadiaSettings CascadiaSettings::Copy() const { - if (addDynamicProfiles) + const auto settings{ winrt::make_self() }; + + // user settings { - _profileGenerators.emplace_back(std::make_unique()); - _profileGenerators.emplace_back(std::make_unique()); - _profileGenerators.emplace_back(std::make_unique()); - _profileGenerators.emplace_back(std::make_unique()); - _profileGenerators.emplace_back(std::make_unique()); + std::vector allProfiles; + std::vector activeProfiles; + allProfiles.reserve(_allProfiles.Size()); + activeProfiles.reserve(_activeProfiles.Size()); + + // Clone the graph of profiles. + // _baseLayerProfile is part of the graph + // and thus needs to be handled here as well. + { + std::vector> sourceProfiles; + std::vector> targetProfiles; + sourceProfiles.reserve(allProfiles.size()); + targetProfiles.reserve(allProfiles.size()); + + for (const auto& profile : _allProfiles) + { + winrt::com_ptr profileImpl; + profileImpl.copy_from(winrt::get_self(profile)); + sourceProfiles.emplace_back(std::move(profileImpl)); + } + + // Profiles are basically a directed acyclic graph. Cloning it without creating duplicated nodes, + // requires us to "intern" visited profiles. Thus the "visited" map contains a cache of + // previously cloned profiles/sub-graphs. It maps from source-profile-pointer to cloned-profile. + std::unordered_map> visited; + // I'm just gonna estimate that each profile has 3 parents at most on average: + // * base layer + // * fragment + // * inbox defaults + visited.reserve(sourceProfiles.size() * 3); + + // _baseLayerProfile is part of the profile graph. + // In order to get a reference to the clone, we need to copy it explicitly. + settings->_baseLayerProfile = _baseLayerProfile->CopyInheritanceGraph(visited); + Profile::CopyInheritanceGraphs(visited, sourceProfiles, targetProfiles); + + for (const auto& profile : targetProfiles) + { + allProfiles.emplace_back(*profile); + if (!profile->Hidden()) + { + activeProfiles.emplace_back(*profile); + } + } + } + + settings->_globals = _globals->Copy(); + settings->_allProfiles = winrt::single_threaded_observable_vector(std::move(allProfiles)); + settings->_activeProfiles = winrt::single_threaded_observable_vector(std::move(activeProfiles)); } -} -CascadiaSettings::CascadiaSettings(winrt::hstring json) : - CascadiaSettings(false) -{ - const auto jsonString{ til::u16u8(json) }; - _ParseJsonString(jsonString, false); - _ApplyDefaultsFromUserSettings(); - LayerJson(_userSettings); - _ValidateSettings(); -} - -winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::Copy() const -{ - // dynamic profile generators added by default - auto settings{ winrt::make_self() }; - settings->_globals = _globals->Copy(); - for (auto warning : _warnings) + // load errors { - settings->_warnings.Append(warning); - } - settings->_loadError = _loadError; - settings->_deserializationErrorMessage = _deserializationErrorMessage; - settings->_userSettingsString = _userSettingsString; - settings->_userSettings = _userSettings; - settings->_defaultSettings = _defaultSettings; + std::vector warnings{ _warnings.Size() }; + _warnings.GetMany(0, warnings); - settings->_defaultTerminals = _defaultTerminals; + settings->_warnings = winrt::single_threaded_vector(std::move(warnings)); + settings->_loadError = _loadError; + settings->_deserializationErrorMessage = _deserializationErrorMessage; + } + + // defterm settings->_currentDefaultTerminal = _currentDefaultTerminal; - _CopyProfileInheritanceTree(settings); - return *settings; } -// Method Description: -// - Copies the inheritance tree for profiles and hooks them up to a clone CascadiaSettings -// Arguments: -// - cloneSettings: the CascadiaSettings we're copying the inheritance tree to -// Return Value: -// - -void CascadiaSettings::_CopyProfileInheritanceTree(winrt::com_ptr& cloneSettings) const -{ - // Our profiles inheritance graph doesn't have a formal root. - // However, if we create a dummy Profile, and set _profiles as the parent, - // we now have a root. So we'll do just that, then copy the inheritance graph - // from the dummyRoot. - auto dummyRootSource{ winrt::make_self() }; - for (const auto& profile : _allProfiles) - { - winrt::com_ptr profileImpl; - profileImpl.copy_from(winrt::get_self(profile)); - Profile::InsertParentHelper(dummyRootSource, profileImpl); - } - - auto dummyRootClone{ winrt::make_self() }; - std::unordered_map> visited{}; - - if (_userDefaultProfileSettings) - { - // profile.defaults must be saved to CascadiaSettings - // So let's do that manually first, and add that to visited - cloneSettings->_userDefaultProfileSettings = Profile::CopySettings(_userDefaultProfileSettings); - visited[_userDefaultProfileSettings.get()] = cloneSettings->_userDefaultProfileSettings; - } - - Profile::CloneInheritanceGraph(dummyRootSource, dummyRootClone, visited); - - // All of the parents of the dummy root clone are _profiles. - // Get the parents and add them to the settings clone. - const auto cloneParents{ dummyRootClone->Parents() }; - for (const auto& profile : cloneParents) - { - cloneSettings->_allProfiles.Append(*profile); - if (!profile->Hidden()) - { - cloneSettings->_activeProfiles.Append(*profile); - } - } -} - // Method Description: // - Finds a profile that matches the given GUID. If there is no profile in this // settings object that matches, returns nullptr. @@ -145,18 +107,14 @@ void CascadiaSettings::_CopyProfileInheritanceTree(winrt::com_ptr // Return Value: // - an iterable collection of all of our Profiles. -IObservableVector CascadiaSettings::AllProfiles() const noexcept +IObservableVector CascadiaSettings::AllProfiles() const noexcept { return _allProfiles; } @@ -178,7 +136,7 @@ IObservableVector Cascadia // - // Return Value: // - an iterable collection of all of our Profiles. -IObservableVector CascadiaSettings::ActiveProfiles() const noexcept +IObservableVector CascadiaSettings::ActiveProfiles() const noexcept { return _activeProfiles; } @@ -189,7 +147,7 @@ IObservableVector Cascadia // - // Return Value: // - the globally configured keybindings -winrt::Microsoft::Terminal::Settings::Model::ActionMap CascadiaSettings::ActionMap() const noexcept +Model::ActionMap CascadiaSettings::ActionMap() const noexcept { return _globals->ActionMap(); } @@ -200,7 +158,7 @@ winrt::Microsoft::Terminal::Settings::Model::ActionMap CascadiaSettings::ActionM // - // Return Value: // - a reference to our global settings -winrt::Microsoft::Terminal::Settings::Model::GlobalAppSettings CascadiaSettings::GlobalSettings() const +Model::GlobalAppSettings CascadiaSettings::GlobalSettings() const { return *_globals; } @@ -211,9 +169,9 @@ winrt::Microsoft::Terminal::Settings::Model::GlobalAppSettings CascadiaSettings: // - // Return Value: // - a reference to our profile.defaults object -winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::ProfileDefaults() const +Model::Profile CascadiaSettings::ProfileDefaults() const { - return *_userDefaultProfileSettings; + return *_baseLayerProfile; } // Method Description: @@ -222,7 +180,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::ProfileDe // - // Return Value: // - a reference to the new profile -winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::CreateNewProfile() +Model::Profile CascadiaSettings::CreateNewProfile() { if (_allProfiles.Size() == std::numeric_limits::max()) { @@ -241,7 +199,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::CreateNew } } - const auto newProfile = _CreateNewProfile(newName); + const auto newProfile = _createNewProfile(newName); _allProfiles.Append(*newProfile); _activeProfiles.Append(*newProfile); return *newProfile; @@ -259,7 +217,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::CreateNew // - source: the Profile object we are duplicating (must not be null) // Return Value: // - a reference to the new profile -winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::DuplicateProfile(const Model::Profile& source) +Model::Profile CascadiaSettings::DuplicateProfile(const Model::Profile& source) { THROW_HR_IF_NULL(E_INVALIDARG, source); @@ -276,7 +234,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate newName = fmt::format(L"{} ({} {})", source.Name(), RS_(L"CopySuffix"), candidateIndex + 2); } - const auto duplicated = _CreateNewProfile(newName); + const auto duplicated = _createNewProfile(newName); static constexpr auto isProfilesDefaultsOrigin = [](const auto& profile) -> bool { return profile && profile.Origin() != OriginTag::ProfilesDefaults; @@ -286,16 +244,19 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate return sub && isProfilesDefaultsOrigin(sub.SourceProfile()); }; -#define DUPLICATE_SETTING_MACRO(settingName) \ - if (source.Has##settingName() || isProfilesDefaultsOrigin(source.settingName##OverrideSource())) \ - { \ - duplicated->settingName(source.settingName()); \ +#define NEEDS_DUPLICATION(settingName) source.Has##settingName() || isProfilesDefaultsOrigin(source.settingName##OverrideSource()) +#define NEEDS_DUPLICATION_SUB(source, settingName) source.Has##settingName() || isProfilesDefaultsOriginSub(source.settingName##OverrideSource()) + +#define DUPLICATE_SETTING_MACRO(settingName) \ + if (NEEDS_DUPLICATION(settingName)) \ + { \ + duplicated->settingName(source.settingName()); \ } -#define DUPLICATE_SETTING_MACRO_SUB(source, target, settingName) \ - if (source.Has##settingName() || isProfilesDefaultsOriginSub(source.settingName##OverrideSource())) \ - { \ - target.settingName(source.settingName()); \ +#define DUPLICATE_SETTING_MACRO_SUB(source, target, settingName) \ + if (NEEDS_DUPLICATION_SUB(source, settingName)) \ + { \ + target.settingName(source.settingName()); \ } // If the source is hidden and the Settings UI creates a @@ -321,7 +282,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate { const auto font = source.FontInfo(); - auto target = duplicated->FontInfo(); + const auto target = duplicated->FontInfo(); DUPLICATE_SETTING_MACRO_SUB(font, target, FontFace); DUPLICATE_SETTING_MACRO_SUB(font, target, FontSize); DUPLICATE_SETTING_MACRO_SUB(font, target, FontWeight); @@ -331,7 +292,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate { const auto appearance = source.DefaultAppearance(); - auto target = duplicated->DefaultAppearance(); + const auto target = duplicated->DefaultAppearance(); DUPLICATE_SETTING_MACRO_SUB(appearance, target, ColorSchemeName); DUPLICATE_SETTING_MACRO_SUB(appearance, target, Foreground); DUPLICATE_SETTING_MACRO_SUB(appearance, target, Background); @@ -351,28 +312,20 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate // UnfocusedAppearance is treated as a single setting, // but requires a little more legwork to duplicate properly - if (source.HasUnfocusedAppearance() || - (source.UnfocusedAppearanceOverrideSource() != nullptr && source.UnfocusedAppearanceOverrideSource().Origin() != OriginTag::ProfilesDefaults)) + if (NEEDS_DUPLICATION(UnfocusedAppearance)) { - // First, get a com_ptr to the source's unfocused appearance - // We need this to be able to call CopyAppearance (it is alright to simply call CopyAppearance here - // instead of needing a separate function like DuplicateAppearance since UnfocusedAppearance is treated - // as a single setting) - winrt::com_ptr sourceUnfocusedAppearanceImpl; - sourceUnfocusedAppearanceImpl.copy_from(winrt::get_self(source.UnfocusedAppearance())); - - // Get a weak ref to the duplicate profile so we can provide a source profile to the new UnfocusedAppearance - // we are about to create - const auto weakRefToDuplicated = weak_ref(*duplicated); - auto duplicatedUnfocusedAppearanceImpl = AppearanceConfig::CopyAppearance(sourceUnfocusedAppearanceImpl, weakRefToDuplicated); + // It is alright to simply call CopyAppearance here instead of needing a separate function + // like DuplicateAppearance since UnfocusedAppearance is treated as a single setting. + const auto unfocusedAppearance = AppearanceConfig::CopyAppearance( + winrt::get_self(source.UnfocusedAppearance()), + winrt::weak_ref(*duplicated)); // Make sure to add the default appearance of the duplicated profile as a parent to the duplicate's UnfocusedAppearance - winrt::com_ptr duplicatedDefaultAppearanceImpl; - duplicatedDefaultAppearanceImpl.copy_from(winrt::get_self(duplicated->DefaultAppearance())); - duplicatedUnfocusedAppearanceImpl->InsertParent(duplicatedDefaultAppearanceImpl); + winrt::com_ptr defaultAppearance; + defaultAppearance.copy_from(winrt::get_self(duplicated->DefaultAppearance())); + unfocusedAppearance->InsertParent(defaultAppearance); - // Finally, set the duplicate's UnfocusedAppearance - duplicated->UnfocusedAppearance(*duplicatedUnfocusedAppearanceImpl); + duplicated->UnfocusedAppearance(*unfocusedAppearance); } if (source.HasConnectionType()) @@ -390,54 +343,33 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate // knew were bad when we called `_ValidateSettings` last. // Return Value: // - a reference to our list of warnings. -IVectorView CascadiaSettings::Warnings() +IVectorView CascadiaSettings::Warnings() const { return _warnings.GetView(); } -void CascadiaSettings::ClearWarnings() -{ - _warnings.Clear(); -} - -void CascadiaSettings::AppendWarning(SettingsLoadWarnings warning) -{ - _warnings.Append(warning); -} - -winrt::Windows::Foundation::IReference CascadiaSettings::GetLoadingError() +winrt::Windows::Foundation::IReference CascadiaSettings::GetLoadingError() const { return _loadError; } -winrt::hstring CascadiaSettings::GetSerializationErrorMessage() +winrt::hstring CascadiaSettings::GetSerializationErrorMessage() const { return _deserializationErrorMessage; } // As used by CreateNewProfile and DuplicateProfile this function // creates a new Profile instance with a random UUID and a given name. -winrt::com_ptr CascadiaSettings::_CreateNewProfile(const std::wstring_view& name) const +winrt::com_ptr CascadiaSettings::_createNewProfile(const std::wstring_view& name) const { - winrt::com_ptr profile; - - if (_userDefaultProfileSettings) - { - profile = _userDefaultProfileSettings->CreateChild(); - } - else - { - profile = winrt::make_self(); - } - // Technically there's Utils::CreateV5Uuid which we could use, but I wanted // truly globally unique UUIDs for profiles created through the settings UI. GUID guid{}; LOG_IF_FAILED(CoCreateGuid(&guid)); + auto profile = CreateChild(_baseLayerProfile); profile->Guid(guid); profile->Name(winrt::hstring{ name }); - return profile; } @@ -451,237 +383,12 @@ winrt::com_ptr CascadiaSettings::_CreateNewProfile(const std::wstring_v // - // Return Value: // - -void CascadiaSettings::_ValidateSettings() +void CascadiaSettings::_validateSettings() { - // Make sure to check that profiles exists at all first and foremost: - _ValidateProfilesExist(); - - // Re-order profiles so that all profiles from the user's settings appear - // before profiles that _weren't_ in the user profiles. - _ReorderProfilesToMatchUserSettingsOrder(); - - // Remove hidden profiles _after_ re-ordering. The re-ordering uses the raw - // json, and will get confused if the profile isn't in the list. - _UpdateActiveProfiles(); - - // Then do some validation on the profiles. The order of these does not - // terribly matter. - _ValidateNoDuplicateProfiles(); - - // Resolve the default profile before we validate that it exists. - _ResolveDefaultProfile(); - _ValidateDefaultProfileExists(); - - // Ensure that all the profile's color scheme names are - // actually the names of schemes we've parsed. If the scheme doesn't exist, - // just use the hardcoded defaults - _ValidateAllSchemesExist(); - - // Ensure all profile's with specified images resources have valid file path. - // This validates icons and background images. - _ValidateMediaResources(); - - // TODO:GH#2548 ensure there's at least one key bound. Display a warning if - // there's _NO_ keys bound to any actions. That's highly irregular, and - // likely an indication of an error somehow. - - // GH#3522 - With variable args to keybindings, it's possible that a user - // set a keybinding without all the required args for an action. Display a - // warning if an action didn't have a required arg. - // This will also catch other keybinding warnings, like from GH#4239 - _ValidateKeybindings(); - - _ValidateColorSchemesInCommands(); - - _ValidateNoGlobalsKey(); -} - -// Method Description: -// - Checks if the settings contain profiles at all. As we'll need to have some -// profiles at all, we'll throw an error if there aren't any profiles. -void CascadiaSettings::_ValidateProfilesExist() -{ - const bool hasProfiles = _allProfiles.Size() > 0; - if (!hasProfiles) - { - // Throw an exception. This is an invalid state, and we want the app to - // be able to gracefully use the default settings. - - // We can't add the warning to the list of warnings here, because this - // object is not going to be returned at any point. - - throw SettingsException(Microsoft::Terminal::Settings::Model::SettingsLoadErrors::NoProfiles); - } -} - -// Method Description: -// - Resolves the "defaultProfile", which can be a profile name, to a GUID -// and stores it back to the globals. -void CascadiaSettings::_ResolveDefaultProfile() -{ - const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() }; - if (!unparsedDefaultProfile.empty()) - { - auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) }; - auto defaultProfileGuid{ til::coalesce_value(maybeParsedDefaultProfile, winrt::guid{}) }; - GlobalSettings().DefaultProfile(defaultProfileGuid); - } -} - -// Method Description: -// - Checks if the "defaultProfile" is set to one of the profiles we -// actually have. If the value is unset, or the value is set to something that -// doesn't exist in the list of profiles, we'll arbitrarily pick the first -// profile to use temporarily as the default. -// - Appends a SettingsLoadWarnings::MissingDefaultProfile to our list of -// warnings if we failed to find the default. -void CascadiaSettings::_ValidateDefaultProfileExists() -{ - const winrt::guid defaultProfileGuid{ GlobalSettings().DefaultProfile() }; - const bool nullDefaultProfile = defaultProfileGuid == winrt::guid{}; - bool defaultProfileNotInProfiles = true; - for (const auto& profile : _allProfiles) - { - if (profile.Guid() == defaultProfileGuid) - { - defaultProfileNotInProfiles = false; - break; - } - } - - if (nullDefaultProfile || defaultProfileNotInProfiles) - { - _warnings.Append(Microsoft::Terminal::Settings::Model::SettingsLoadWarnings::MissingDefaultProfile); - // Use the first profile as the new default - - // _temporarily_ set the default profile to the first profile. Because - // we're adding a warning, this settings change won't be re-serialized. - GlobalSettings().DefaultProfile(_allProfiles.GetAt(0).Guid()); - } -} - -// Method Description: -// - Checks to make sure there aren't any duplicate profiles in the list of -// profiles. If so, we'll remove the subsequent entries (temporarily), as they -// won't be accessible anyways. -// - Appends a SettingsLoadWarnings::DuplicateProfile to our list of warnings if -// we find any such duplicate. -void CascadiaSettings::_ValidateNoDuplicateProfiles() -{ - bool foundDupe = false; - - std::vector indicesToDelete; - - std::set uniqueGuids; - - // Try collecting all the unique guids. If we ever encounter a guid that's - // already in the set, then we need to delete that profile. - for (uint32_t i = 0; i < _allProfiles.Size(); i++) - { - if (!uniqueGuids.insert(_allProfiles.GetAt(i).Guid()).second) - { - foundDupe = true; - indicesToDelete.push_back(i); - } - } - - // Remove all the duplicates we've marked - // Walk backwards, so we don't accidentally shift any of the elements - for (auto iter = indicesToDelete.rbegin(); iter != indicesToDelete.rend(); iter++) - { - _allProfiles.RemoveAt(*iter); - } - - if (foundDupe) - { - _warnings.Append(Microsoft::Terminal::Settings::Model::SettingsLoadWarnings::DuplicateProfile); - } -} - -// Method Description: -// - Re-orders the list of profiles to match what the user would expect them to -// be. Orders profiles to be in the ordering { [profiles from user settings], -// [default profiles that weren't in the user profiles]}. -// - Does not set any warnings. -// Arguments: -// - -// Return Value: -// - -void CascadiaSettings::_ReorderProfilesToMatchUserSettingsOrder() -{ - std::set uniqueGuids; - std::deque guidOrder; - - auto collectGuids = [&](const auto& json) { - for (auto profileJson : _GetProfilesJsonObject(json)) - { - if (profileJson.isObject()) - { - auto guid = implementation::Profile::GetGuidOrGenerateForJson(profileJson); - if (uniqueGuids.insert(guid).second) - { - guidOrder.push_back(guid); - } - } - } - }; - - // Push all the userSettings profiles' GUIDS into the set - collectGuids(_userSettings); - - // Push all the defaultSettings profiles' GUIDS into the set - collectGuids(_defaultSettings); - std::equal_to equals; - // Re-order the list of profiles to match that ordering - // for (gIndex=0 -> uniqueGuids.size) - // pIndex = the pIndex of the profile with guid==guids[gIndex] - // profiles.swap(pIndex <-> gIndex) - // This is O(N^2), which is kinda rough. I'm sure there's a better way - for (uint32_t gIndex = 0; gIndex < guidOrder.size(); gIndex++) - { - const auto guid = guidOrder.at(gIndex); - for (uint32_t pIndex = gIndex; pIndex < _allProfiles.Size(); pIndex++) - { - auto profileGuid = _allProfiles.GetAt(pIndex).Guid(); - if (equals(profileGuid, guid)) - { - auto prof1 = _allProfiles.GetAt(pIndex); - _allProfiles.SetAt(pIndex, _allProfiles.GetAt(gIndex)); - _allProfiles.SetAt(gIndex, prof1); - break; - } - } - } -} - -// Method Description: -// - Updates the list of active profiles from the list of all profiles -// - If there are no active profiles (all profiles are hidden), throw a SettingsException -// - Does not set any warnings. -// Arguments: -// - -// Return Value: -// - -void CascadiaSettings::_UpdateActiveProfiles() -{ - _activeProfiles.Clear(); - for (auto const& profile : _allProfiles) - { - if (!profile.Hidden()) - { - _activeProfiles.Append(profile); - } - } - - // Ensure that we still have some profiles here. If we don't, then throw an - // exception, so the app can use the defaults. - const bool hasProfiles = _activeProfiles.Size() > 0; - if (!hasProfiles) - { - // Throw an exception. This is an invalid state, and we want the app to - // be able to gracefully use the default settings. - throw SettingsException(SettingsLoadErrors::AllProfilesHidden); - } + _validateAllSchemesExist(); + _validateMediaResources(); + _validateKeybindings(); + _validateColorSchemesInCommands(); } // Method Description: @@ -694,24 +401,19 @@ void CascadiaSettings::_UpdateActiveProfiles() // - // - Appends a SettingsLoadWarnings::UnknownColorScheme to our list of warnings if // we find any such duplicate. -void CascadiaSettings::_ValidateAllSchemesExist() +void CascadiaSettings::_validateAllSchemesExist() { + const auto colorSchemes = _globals->ColorSchemes(); bool foundInvalidScheme = false; - for (auto profile : _allProfiles) + + for (const auto& profile : _allProfiles) { - const auto schemeName = profile.DefaultAppearance().ColorSchemeName(); - if (!_globals->ColorSchemes().HasKey(schemeName)) + for (const auto& appearance : std::array{ profile.DefaultAppearance(), profile.UnfocusedAppearance() }) { - // Clear the user set color scheme. We'll just fallback instead. - profile.DefaultAppearance().ClearColorSchemeName(); - foundInvalidScheme = true; - } - if (profile.UnfocusedAppearance()) - { - const auto unfocusedSchemeName = profile.UnfocusedAppearance().ColorSchemeName(); - if (!_globals->ColorSchemes().HasKey(unfocusedSchemeName)) + if (appearance && !colorSchemes.HasKey(appearance.ColorSchemeName())) { - profile.UnfocusedAppearance().ClearColorSchemeName(); + // Clear the user set color scheme. We'll just fallback instead. + appearance.ClearColorSchemeName(); foundInvalidScheme = true; } } @@ -734,65 +436,61 @@ void CascadiaSettings::_ValidateAllSchemesExist() // we find any invalid background images. // - Appends a SettingsLoadWarnings::InvalidIconImage to our list of warnings if // we find any invalid icon images. -void CascadiaSettings::_ValidateMediaResources() +void CascadiaSettings::_validateMediaResources() { bool invalidBackground{ false }; bool invalidIcon{ false }; for (auto profile : _allProfiles) { - if (!profile.DefaultAppearance().BackgroundImagePath().empty()) + 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 { - winrt::Windows::Foundation::Uri imagePath{ profile.DefaultAppearance().ExpandedBackgroundImagePath() }; + winrt::Windows::Foundation::Uri imagePath{ path }; } catch (...) { // reset background image path - profile.DefaultAppearance().BackgroundImagePath(L""); + profile.DefaultAppearance().ClearBackgroundImagePath(); invalidBackground = true; } } if (profile.UnfocusedAppearance()) { - if (!profile.UnfocusedAppearance().BackgroundImagePath().empty()) + 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 { - winrt::Windows::Foundation::Uri imagePath{ profile.UnfocusedAppearance().ExpandedBackgroundImagePath() }; + winrt::Windows::Foundation::Uri imagePath{ path }; } catch (...) { // reset background image path - profile.UnfocusedAppearance().BackgroundImagePath(L""); + profile.UnfocusedAppearance().ClearBackgroundImagePath(); invalidBackground = true; } } } - if (!profile.Icon().empty()) + // Anything longer than 2 wchar_t's _isn't_ an emoji or symbol, + // so treat it as an invalid path. + if (const auto icon = profile.Icon(); icon.size() > 2) { - const auto iconPath{ wil::ExpandEnvironmentStringsW(profile.Icon().c_str()) }; + const auto iconPath{ wil::ExpandEnvironmentStringsW(icon.c_str()) }; try { winrt::Windows::Foundation::Uri imagePath{ iconPath }; } catch (...) { - // Anything longer than 2 wchar_t's _isn't_ an emoji or symbol, - // so treat it as an invalid path. - if (iconPath.size() > 2) - { - // reset icon path - profile.Icon(L""); - invalidIcon = true; - } + profile.ClearIcon(); + invalidIcon = true; } } } @@ -824,27 +522,22 @@ void CascadiaSettings::_ValidateMediaResources() // and attempt to look the profile up by name instead. // Return Value: // - the GUID of the profile corresponding to this combination of index and NewTerminalArgs -winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& newTerminalArgs) const +Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& newTerminalArgs) const { - std::optional profileByIndex, profileByName; if (newTerminalArgs) { - if (newTerminalArgs.ProfileIndex() != nullptr) + if (auto profile = GetProfileByName(newTerminalArgs.Profile())) { - profileByIndex = _GetProfileGuidByIndex(newTerminalArgs.ProfileIndex().Value()); + return profile; } - profileByName = _GetProfileGuidByName(newTerminalArgs.Profile()); - } - - if (profileByName) - { - return FindProfile(*profileByName); - } - - if (profileByIndex) - { - return FindProfile(*profileByIndex); + if (const auto index = newTerminalArgs.ProfileIndex()) + { + if (auto profile = GetProfileByIndex(gsl::narrow(index.Value()))) + { + return profile; + } + } } if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled()) @@ -866,13 +559,12 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::GetProfil } // Method Description: -// - Helper to get the GUID of a profile given a name that could be a guid or an actual name. +// - Helper to get a profile given a name that could be a guid or an actual name. // Arguments: // - name: a guid string _or_ the name of a profile // Return Value: // - the GUID of the profile corresponding to this name -std::optional CascadiaSettings::_GetProfileGuidByName(const winrt::hstring name) const -try +Model::Profile CascadiaSettings::GetProfileByName(const winrt::hstring& name) const { // First, try and parse the "name" as a GUID. If it's a // GUID, and the GUID of one of our profiles, then use that as the @@ -885,10 +577,10 @@ try // it doesn't, it's _definitely_ not a GUID. if (name.size() == 38 && name[0] == L'{') { - const auto newGUID{ Utils::GuidFromString(static_cast(name)) }; - if (FindProfile(newGUID)) + const auto newGUID{ Utils::GuidFromString(name.c_str()) }; + if (auto profile = FindProfile(newGUID)) { - return newGUID; + return profile; } } @@ -899,43 +591,24 @@ try { if (profile.Name() == name) { - return profile.Guid(); + return profile; } } } - return std::nullopt; -} -catch (...) -{ - LOG_CAUGHT_EXCEPTION(); - return std::nullopt; + return nullptr; } // Method Description: -// - Helper to find the profile GUID for a the profile at the given index in the -// list of profiles. If no index is provided, this instead returns the default -// profile's guid. This is used by the NewTabProfile ShortcutActions to -// create a tab for the Nth profile in the list of profiles. +// - Helper to get the profile at the given index in the list of profiles. +// - Returns a nullptr if the index is out of bounds. // Arguments: -// - index: if provided, the index in the list of profiles to get the GUID for. -// If omitted, instead return the default profile's GUID +// - index: The profile index in ActiveProfiles() // Return Value: -// - the Nth profile's GUID, or the default profile's GUID -std::optional CascadiaSettings::_GetProfileGuidByIndex(std::optional index) const +// - the Nth profile +Model::Profile CascadiaSettings::GetProfileByIndex(uint32_t index) const { - if (index) - { - const auto realIndex{ index.value() }; - // If we don't have that many profiles, then do nothing. - if (realIndex >= 0 && - realIndex < gsl::narrow_cast(_activeProfiles.Size())) - { - const auto& selectedProfile = _activeProfiles.GetAt(realIndex); - return selectedProfile.Guid(); - } - } - return std::nullopt; + return index < _activeProfiles.Size() ? _activeProfiles.GetAt(index) : nullptr; } // Method Description: @@ -943,13 +616,18 @@ std::optional CascadiaSettings::_GetProfileGuidByIndex(std::optiona // keybindings, add them to the list of warnings here. If there were warnings // generated in this way, we'll add a AtLeastOneKeybindingWarning, which will // act as a header for the other warnings +// - GH#3522 +// With variable args to keybindings, it's possible that a user +// set a keybinding without all the required args for an action. +// Display a warning if an action didn't have a required arg. +// This will also catch other keybinding warnings, like from GH#4239. // Arguments: // - // Return Value: // - -void CascadiaSettings::_ValidateKeybindings() +void CascadiaSettings::_validateKeybindings() const { - auto keybindingWarnings = _globals->KeybindingsWarnings(); + const auto keybindingWarnings = _globals->KeybindingsWarnings(); if (!keybindingWarnings.empty()) { @@ -969,12 +647,12 @@ void CascadiaSettings::_ValidateKeybindings() // - // - Appends a SettingsLoadWarnings::InvalidColorSchemeInCmd to our list of warnings if // we find any command with an invalid color scheme. -void CascadiaSettings::_ValidateColorSchemesInCommands() +void CascadiaSettings::_validateColorSchemesInCommands() const { bool foundInvalidScheme{ false }; for (const auto& nameAndCmd : _globals->ActionMap().NameMap()) { - if (_HasInvalidColorScheme(nameAndCmd.Value())) + if (_hasInvalidColorScheme(nameAndCmd.Value())) { foundInvalidScheme = true; break; @@ -987,14 +665,14 @@ void CascadiaSettings::_ValidateColorSchemesInCommands() } } -bool CascadiaSettings::_HasInvalidColorScheme(const Model::Command& command) +bool CascadiaSettings::_hasInvalidColorScheme(const Model::Command& command) const { bool invalid{ false }; if (command.HasNestedCommands()) { for (const auto& nested : command.NestedCommands()) { - if (_HasInvalidColorScheme(nested.Value())) + if (_hasInvalidColorScheme(nested.Value())) { invalid = true; break; @@ -1005,7 +683,7 @@ bool CascadiaSettings::_HasInvalidColorScheme(const Model::Command& command) { if (const auto& realArgs = actionAndArgs.Args().try_as()) { - auto cmdImpl{ winrt::get_self(command) }; + const auto cmdImpl{ winrt::get_self(command) }; // no need to validate iterable commands on color schemes // they will be expanded to commands with a valid scheme name if (cmdImpl->IterateOn() != ExpandCommandType::ColorSchemes && @@ -1019,66 +697,6 @@ bool CascadiaSettings::_HasInvalidColorScheme(const Model::Command& command) return invalid; } -// Method Description: -// - Checks for the presence of the legacy "globals" key in the user's -// settings.json. If this key is present, then they've probably got a pre-0.11 -// settings file that won't work as expected anymore. We should warn them -// about that. -// Arguments: -// - -// Return Value: -// - -// - Appends a SettingsLoadWarnings::LegacyGlobalsProperty to our list of warnings if -// we find any invalid background images. -void CascadiaSettings::_ValidateNoGlobalsKey() -{ - // use isMember here. If you use [], you're actually injecting "globals": null. - if (_userSettings.isMember("globals")) - { - _warnings.Append(SettingsLoadWarnings::LegacyGlobalsProperty); - } -} - -// Method Description -// - Replaces known tokens DEFAULT_PROFILE, PRODUCT and VERSION in the settings template -// with their expected values. DEFAULT_PROFILE is updated to match PowerShell Core's GUID -// if such a profile is detected. If it isn't, it'll be set to Windows PowerShell's GUID. -// Arguments: -// - settingsTemplate: a settings template -// Return value: -// - The new settings string. -std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::string_view settingsTemplate) const -{ - // We're using replace_needle_in_haystack_inplace here, because it's more - // efficient to iteratively modify a single string in-place than it is to - // keep copying over the contents and modifying a copy (which - // replace_needle_in_haystack would do). - std::string finalSettings{ settingsTemplate }; - - std::wstring defaultProfileGuid{ DEFAULT_WINDOWS_POWERSHELL_GUID }; - if (const auto psCoreProfileGuid{ _GetProfileGuidByName(hstring{ PowershellCoreProfileGenerator::GetPreferredPowershellProfileName() }) }) - { - defaultProfileGuid = Utils::GuidToString(*psCoreProfileGuid); - } - - til::replace_needle_in_haystack_inplace(finalSettings, - "%DEFAULT_PROFILE%", - til::u16u8(defaultProfileGuid)); - - til::replace_needle_in_haystack_inplace(finalSettings, - "%VERSION%", - til::u16u8(ApplicationVersion())); - til::replace_needle_in_haystack_inplace(finalSettings, - "%PRODUCT%", - til::u16u8(ApplicationDisplayName())); - - til::replace_needle_in_haystack_inplace(finalSettings, - "%COMMAND_PROMPT_LOCALIZED_NAME%", - RS_A(L"CommandPromptDisplayName")); - - return finalSettings; -} - // Method Description: // - Lookup the color scheme for a given profile. If the profile doesn't exist, // or the scheme name listed in the profile doesn't correspond to a scheme, @@ -1087,7 +705,7 @@ std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::strin // - profileGuid: the GUID of the profile to find the scheme for. // Return Value: // - a non-owning pointer to the scheme. -winrt::Microsoft::Terminal::Settings::Model::ColorScheme CascadiaSettings::GetColorSchemeForProfile(const Model::Profile& profile) const +Model::ColorScheme CascadiaSettings::GetColorSchemeForProfile(const Model::Profile& profile) const { if (!profile) { @@ -1104,22 +722,23 @@ winrt::Microsoft::Terminal::Settings::Model::ColorScheme CascadiaSettings::GetCo // - newName: the new name for the color scheme // Return Value: // - -void CascadiaSettings::UpdateColorSchemeReferences(const hstring oldName, const hstring newName) +void CascadiaSettings::UpdateColorSchemeReferences(const winrt::hstring& oldName, const winrt::hstring& newName) { // update profiles.defaults, if necessary - if (_userDefaultProfileSettings && - _userDefaultProfileSettings->DefaultAppearance().HasColorSchemeName() && - _userDefaultProfileSettings->DefaultAppearance().ColorSchemeName() == oldName) + if (_baseLayerProfile && + _baseLayerProfile->DefaultAppearance().HasColorSchemeName() && + _baseLayerProfile->DefaultAppearance().ColorSchemeName() == oldName) { - _userDefaultProfileSettings->DefaultAppearance().ColorSchemeName(newName); + _baseLayerProfile->DefaultAppearance().ColorSchemeName(newName); } // update all profiles referencing this color scheme for (const auto& profile : _allProfiles) { - if (profile.DefaultAppearance().HasColorSchemeName() && profile.DefaultAppearance().ColorSchemeName() == oldName) + const auto defaultAppearance = profile.DefaultAppearance(); + if (defaultAppearance.HasColorSchemeName() && defaultAppearance.ColorSchemeName() == oldName) { - profile.DefaultAppearance().ColorSchemeName(newName); + defaultAppearance.ColorSchemeName(newName); } if (profile.UnfocusedAppearance()) @@ -1155,7 +774,12 @@ winrt::hstring CascadiaSettings::ApplicationVersion() } CATCH_LOG(); - // Try to get the version the old-fashioned way + // Get the product version the old-fashioned way from the localized version compartment. + // + // We explicitly aren't using VS_FIXEDFILEINFO here, because our build pipeline puts + // a non-standard version number into the localized version field. + // For instance the fixed file info might contain "1.12.2109.13002", + // while the localized field might contain "1.11.210830001-release1.11". try { struct LocalizationInfo @@ -1191,38 +815,6 @@ winrt::hstring CascadiaSettings::ApplicationVersion() return RS_(L"ApplicationVersionUnknown"); } -// Method Description: -// - Forces a refresh of all default terminal state. This hits the registry to -// read off the disk, so best to not do it on the UI thread. -// Arguments: -// - -// Return Value: -// - - Updates internal state -void CascadiaSettings::RefreshDefaultTerminals() -{ - _defaultTerminals.Clear(); - - for (const auto& term : Model::DefaultTerminal::Available()) - { - _defaultTerminals.Append(term); - } - - _currentDefaultTerminal = Model::DefaultTerminal::Current(); -} - -// Helper to do the version check -static bool _isOnBuildWithDefTerm() noexcept -{ - OSVERSIONINFOEXW osver{ 0 }; - osver.dwOSVersionInfoSize = sizeof(osver); - osver.dwBuildNumber = 21359; - - DWORDLONG dwlConditionMask = 0; - VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL); - - return VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask); -} - // Method Description: // - Determines if we're on an OS platform that supports // the default terminal handoff functionality. @@ -1232,9 +824,14 @@ static bool _isOnBuildWithDefTerm() noexcept // - True if OS supports default terminal. False otherwise. bool CascadiaSettings::IsDefaultTerminalAvailable() noexcept { - // Cached on first use since the OS version shouldn't change while we're running. - static bool isAvailable = _isOnBuildWithDefTerm(); - return isAvailable; + OSVERSIONINFOEXW osver{}; + osver.dwOSVersionInfoSize = sizeof(osver); + osver.dwBuildNumber = 22000; + + DWORDLONG dwlConditionMask = 0; + VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL); + + return VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE; } // Method Description: @@ -1243,9 +840,12 @@ bool CascadiaSettings::IsDefaultTerminalAvailable() noexcept // - // Return Value: // - an iterable collection of all available terminals that could be the default. -IObservableVector CascadiaSettings::DefaultTerminals() const noexcept +IObservableVector CascadiaSettings::DefaultTerminals() const noexcept { - return _defaultTerminals; + const auto available = DefaultTerminal::Available(); + std::vector terminals{ available.Size(), nullptr }; + available.GetMany(0, terminals); + return winrt::single_threaded_observable_vector(std::move(terminals)); } // Method Description: @@ -1259,8 +859,12 @@ IObservableVector CascadiaSettings::DefaultTer // - // Return Value: // - the selected default terminal application -Settings::Model::DefaultTerminal CascadiaSettings::CurrentDefaultTerminal() const noexcept +Settings::Model::DefaultTerminal CascadiaSettings::CurrentDefaultTerminal() noexcept { + if (!_currentDefaultTerminal) + { + _currentDefaultTerminal = DefaultTerminal::Current(); + } return _currentDefaultTerminal; } @@ -1270,7 +874,7 @@ Settings::Model::DefaultTerminal CascadiaSettings::CurrentDefaultTerminal() cons // - terminal - Terminal from `DefaultTerminals` list to set as default // Return Value: // - -void CascadiaSettings::CurrentDefaultTerminal(Settings::Model::DefaultTerminal terminal) +void CascadiaSettings::CurrentDefaultTerminal(const Model::DefaultTerminal& terminal) { _currentDefaultTerminal = terminal; } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index ee5782277..e690f7208 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -20,163 +20,137 @@ Author(s): #include "CascadiaSettings.g.h" #include "GlobalAppSettings.h" -#include "TerminalWarnings.h" -#include "IDynamicProfileGenerator.h" - #include "Profile.h" -#include "ColorScheme.h" -// fwdecl unittest classes -namespace SettingsModelLocalTests +namespace winrt::Microsoft::Terminal::Settings::Model { - class SerializationTests; - class DeserializationTests; - class ProfileTests; - class ColorSchemeTests; - class KeyBindingsTests; -}; -namespace TerminalAppUnitTests -{ - class DynamicProfileTests; - class JsonTests; -}; - -namespace Microsoft::Terminal::Settings::Model -{ - class SettingsTypedDeserializationException; -}; - -class Microsoft::Terminal::Settings::Model::SettingsTypedDeserializationException final : public std::runtime_error -{ -public: - SettingsTypedDeserializationException(const std::string_view description) : - runtime_error(description.data()) {} -}; + class IDynamicProfileGenerator; +} namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + winrt::com_ptr CreateChild(const winrt::com_ptr& parent); + + class SettingsTypedDeserializationException final : public std::runtime_error + { + public: + SettingsTypedDeserializationException(const char* message) noexcept : + std::runtime_error(message) {} + }; + + struct ParsedSettings + { + winrt::com_ptr globals; + winrt::com_ptr baseLayerProfile; + std::vector> profiles; + std::unordered_map> profilesByGuid; + }; + + struct SettingsLoader + { + static SettingsLoader Default(const std::string_view& userJSON, const std::string_view& inboxJSON); + SettingsLoader(const std::string_view& userJSON, const std::string_view& inboxJSON); + + void GenerateProfiles(); + void ApplyRuntimeInitialSettings(); + void MergeInboxIntoUserSettings(); + void FindFragmentsAndMergeIntoUserSettings(); + void FinalizeLayering(); + bool DisableDeletedProfiles(); + + ParsedSettings inboxSettings; + ParsedSettings userSettings; + bool duplicateProfile = false; + + private: + static std::pair _lineAndColumnFromPosition(const std::string_view& string, const size_t position); + static void _rethrowSerializationExceptionWithLocationInfo(const JsonUtils::DeserializationError& e, const std::string_view& settingsString); + static Json::Value _parseJSON(const std::string_view& content); + static const Json::Value& _getJSONValue(const Json::Value& json, const std::string_view& key) noexcept; + static bool _isValidProfileObject(const Json::Value& profileJson); + gsl::span> _getNonUserOriginProfiles() const; + void _parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings); + void _appendProfile(winrt::com_ptr&& profile, ParsedSettings& settings); + void _executeGenerator(const IDynamicProfileGenerator& generator); + + std::unordered_set _ignoredNamespaces; + // See _getNonUserOriginProfiles(). + size_t _userProfileCount = 0; + }; + struct CascadiaSettings : CascadiaSettingsT { public: - CascadiaSettings(); - explicit CascadiaSettings(const bool addDynamicProfiles); - CascadiaSettings(hstring json); - Model::CascadiaSettings Copy() const; - static Model::CascadiaSettings LoadDefaults(); static Model::CascadiaSettings LoadAll(); static Model::CascadiaSettings LoadUniversal(); - Model::GlobalAppSettings GlobalSettings() const; - Windows::Foundation::Collections::IObservableVector AllProfiles() const noexcept; - Windows::Foundation::Collections::IObservableVector ActiveProfiles() const noexcept; - Model::ActionMap ActionMap() const noexcept; - - static com_ptr FromJson(const Json::Value& json); - void LayerJson(const Json::Value& json); - - void WriteSettingsToDisk() const; - Json::Value ToJson() const; - - static hstring SettingsPath(); - static hstring DefaultSettingsPath(); - Model::Profile ProfileDefaults() const; - + static winrt::hstring SettingsPath(); + static winrt::hstring DefaultSettingsPath(); static winrt::hstring ApplicationDisplayName(); static winrt::hstring ApplicationVersion(); + CascadiaSettings() noexcept = default; + CascadiaSettings(const winrt::hstring& userJSON, const winrt::hstring& inboxJSON); + CascadiaSettings(const std::string_view& userJSON, const std::string_view& inboxJSON = {}); + explicit CascadiaSettings(SettingsLoader&& loader); + + // user settings + Model::CascadiaSettings Copy() const; + Model::GlobalAppSettings GlobalSettings() const; + winrt::Windows::Foundation::Collections::IObservableVector AllProfiles() const noexcept; + winrt::Windows::Foundation::Collections::IObservableVector ActiveProfiles() const noexcept; + Model::ActionMap ActionMap() const noexcept; + void WriteSettingsToDisk() const; + Json::Value ToJson() const; + Model::Profile ProfileDefaults() const; Model::Profile CreateNewProfile(); - Model::Profile FindProfile(const guid& profileGuid) const noexcept; + Model::Profile FindProfile(const winrt::guid& guid) const noexcept; Model::ColorScheme GetColorSchemeForProfile(const Model::Profile& profile) const; - void UpdateColorSchemeReferences(const hstring oldName, const hstring newName); - - Windows::Foundation::Collections::IVectorView Warnings(); - void ClearWarnings(); - void AppendWarning(SettingsLoadWarnings warning); - Windows::Foundation::IReference GetLoadingError(); - hstring GetSerializationErrorMessage(); - + void UpdateColorSchemeReferences(const winrt::hstring& oldName, const winrt::hstring& newName); Model::Profile GetProfileForArgs(const Model::NewTerminalArgs& newTerminalArgs) const; - + Model::Profile GetProfileByName(const winrt::hstring& name) const; + Model::Profile GetProfileByIndex(uint32_t index) const; Model::Profile DuplicateProfile(const Model::Profile& source); - void RefreshDefaultTerminals(); + // load errors + winrt::Windows::Foundation::Collections::IVectorView Warnings() const; + winrt::Windows::Foundation::IReference GetLoadingError() const; + winrt::hstring GetSerializationErrorMessage() const; + + // defterm static bool IsDefaultTerminalAvailable() noexcept; - Windows::Foundation::Collections::IObservableVector DefaultTerminals() const noexcept; - Model::DefaultTerminal CurrentDefaultTerminal() const noexcept; - void CurrentDefaultTerminal(Model::DefaultTerminal terminal); + winrt::Windows::Foundation::Collections::IObservableVector DefaultTerminals() const noexcept; + Model::DefaultTerminal CurrentDefaultTerminal() noexcept; + void CurrentDefaultTerminal(const Model::DefaultTerminal& terminal); private: - com_ptr _globals; - Windows::Foundation::Collections::IObservableVector _allProfiles; - Windows::Foundation::Collections::IObservableVector _activeProfiles; - Windows::Foundation::Collections::IVector _warnings; - Windows::Foundation::IReference _loadError; - hstring _deserializationErrorMessage; + static const std::filesystem::path& _settingsPath(); - Windows::Foundation::Collections::IObservableVector _defaultTerminals; - Model::DefaultTerminal _currentDefaultTerminal; + winrt::com_ptr _createNewProfile(const std::wstring_view& name) const; - std::vector> _profileGenerators; + void _resolveDefaultProfile() const; - std::string _userSettingsString; - Json::Value _userSettings; - Json::Value _defaultSettings; - winrt::com_ptr _userDefaultProfileSettings{ nullptr }; + void _validateSettings(); + void _validateAllSchemesExist(); + void _validateMediaResources(); + void _validateKeybindings() const; + void _validateColorSchemesInCommands() const; + bool _hasInvalidColorScheme(const Model::Command& command) const; - winrt::com_ptr _CreateNewProfile(const std::wstring_view& name) const; + // user settings + winrt::com_ptr _globals; + winrt::com_ptr _baseLayerProfile; + winrt::Windows::Foundation::Collections::IObservableVector _allProfiles; + winrt::Windows::Foundation::Collections::IObservableVector _activeProfiles; - void _LayerOrCreateProfile(const Json::Value& profileJson); - winrt::com_ptr _FindMatchingProfile(const Json::Value& profileJson); - std::optional _FindMatchingProfileIndex(const Json::Value& profileJson); - void _LayerOrCreateColorScheme(const Json::Value& schemeJson); - Json::Value _ParseUtf8JsonString(std::string_view fileData); + // load errors + winrt::Windows::Foundation::Collections::IVector _warnings; + winrt::Windows::Foundation::IReference _loadError; + winrt::hstring _deserializationErrorMessage; - winrt::com_ptr _FindMatchingColorScheme(const Json::Value& schemeJson); - void _ParseJsonString(std::string_view fileData, const bool isDefaultSettings); - static const Json::Value& _GetProfilesJsonObject(const Json::Value& json); - static const Json::Value& _GetDisabledProfileSourcesJsonObject(const Json::Value& json); - bool _PrependSchemaDirective(); - bool _AppendDynamicProfilesToUserSettings(); - std::string _ApplyFirstRunChangesToSettingsTemplate(std::string_view settingsTemplate) const; - void _CopyProfileInheritanceTree(com_ptr& cloneSettings) const; - - void _ApplyDefaultsFromUserSettings(); - - void _LoadDynamicProfiles(); - void _LoadFragmentExtensions(); - void _ApplyJsonStubsHelper(const std::wstring_view directory, const std::unordered_set& ignoredNamespaces); - std::unordered_set _AccumulateJsonFilesInDirectory(const std::wstring_view directory); - void _ParseAndLayerFragmentFiles(const std::unordered_set files, const winrt::hstring source); - - static const std::filesystem::path& _SettingsPath(); - static std::optional _ReadUserSettings(); - - std::optional _GetProfileGuidByName(const hstring) const; - std::optional _GetProfileGuidByIndex(std::optional index) const; - - void _ValidateSettings(); - void _ValidateProfilesExist(); - void _ValidateDefaultProfileExists(); - void _ValidateNoDuplicateProfiles(); - void _ResolveDefaultProfile(); - void _ReorderProfilesToMatchUserSettingsOrder(); - void _UpdateActiveProfiles(); - void _ValidateAllSchemesExist(); - void _ValidateMediaResources(); - void _ValidateKeybindings(); - void _ValidateColorSchemesInCommands(); - void _ValidateNoGlobalsKey(); - - bool _HasInvalidColorScheme(const Model::Command& command); - - friend class SettingsModelLocalTests::SerializationTests; - friend class SettingsModelLocalTests::DeserializationTests; - friend class SettingsModelLocalTests::ProfileTests; - friend class SettingsModelLocalTests::ColorSchemeTests; - friend class SettingsModelLocalTests::KeyBindingsTests; - friend class TerminalAppUnitTests::DynamicProfileTests; - friend class TerminalAppUnitTests::JsonTests; + // defterm + Model::DefaultTerminal _currentDefaultTerminal{ nullptr }; }; } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 2f3248fbc..092401ebc 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -9,11 +9,6 @@ import "DefaultTerminal.idl"; namespace Microsoft.Terminal.Settings.Model { [default_interface] runtimeclass CascadiaSettings { - CascadiaSettings(String json); - CascadiaSettings Copy(); - - void WriteSettingsToDisk(); - static CascadiaSettings LoadDefaults(); static CascadiaSettings LoadAll(); static CascadiaSettings LoadUniversal(); @@ -23,19 +18,24 @@ namespace Microsoft.Terminal.Settings.Model static String ApplicationDisplayName { get; }; static String ApplicationVersion { get; }; + + CascadiaSettings(String userJSON, String inboxJSON); + + CascadiaSettings Copy(); + void WriteSettingsToDisk(); GlobalAppSettings GlobalSettings { get; }; Profile ProfileDefaults { get; }; - Windows.Foundation.Collections.IObservableVector AllProfiles { get; }; - Windows.Foundation.Collections.IObservableVector ActiveProfiles { get; }; + IObservableVector AllProfiles { get; }; + IObservableVector ActiveProfiles { get; }; Profile DuplicateProfile(Profile sourceProfile); ActionMap ActionMap { get; }; - Windows.Foundation.Collections.IVectorView Warnings { get; }; + IVectorView Warnings { get; }; Windows.Foundation.IReference GetLoadingError { get; }; String GetSerializationErrorMessage { get; }; @@ -46,9 +46,8 @@ namespace Microsoft.Terminal.Settings.Model Profile GetProfileForArgs(NewTerminalArgs newTerminalArgs); - void RefreshDefaultTerminals(); static Boolean IsDefaultTerminalAvailable { get; }; - Windows.Foundation.Collections.IObservableVector DefaultTerminals { get; }; + IObservableVector DefaultTerminals { get; }; DefaultTerminal CurrentDefaultTerminal; } } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 36ca2a316..b704be14d 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -4,128 +4,580 @@ #include "pch.h" #include "CascadiaSettings.h" +#include #include #include +#include -// defaults.h is a file containing the default json settings in a std::string_view +#include "AzureCloudShellGenerator.h" +#include "PowershellCoreProfileGenerator.h" +#include "VsDevCmdGenerator.h" +#include "VsDevShellGenerator.h" +#include "WslDistroGenerator.h" + +// The following files are generated at build time into the "Generated Files" directory. +// defaults(-universal).h is a file containing the default json settings in a std::string_view. #include "defaults.h" #include "defaults-universal.h" // userDefault.h is like the above, but with a default template for the user's settings.json. +#include + #include "userDefaults.h" -// Both defaults.h and userDefaults.h are generated at build time into the -// "Generated Files" directory. #include "ApplicationState.h" #include "FileUtils.h" +using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; -using namespace ::Microsoft::Console; -using namespace ::Microsoft::Terminal::Settings::Model; static constexpr std::wstring_view SettingsFilename{ L"settings.json" }; -static constexpr std::wstring_view LegacySettingsFilename{ L"profiles.json" }; - static constexpr std::wstring_view DefaultsFilename{ L"defaults.json" }; -static constexpr std::string_view SchemaKey{ "$schema" }; -static constexpr std::string_view SchemaValue{ "https://aka.ms/terminal-profiles-schema" }; static constexpr std::string_view ProfilesKey{ "profiles" }; static constexpr std::string_view DefaultSettingsKey{ "defaults" }; static constexpr std::string_view ProfilesListKey{ "list" }; -static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" }; -static constexpr std::string_view ActionsKey{ "actions" }; static constexpr std::string_view SchemesKey{ "schemes" }; static constexpr std::string_view NameKey{ "name" }; -static constexpr std::string_view UpdatesKey{ "updates" }; static constexpr std::string_view GuidKey{ "guid" }; -static constexpr std::string_view DisabledProfileSourcesKey{ "disabledProfileSources" }; - -static constexpr std::string_view SettingsSchemaFragment{ "\n" - R"( "$schema": "https://aka.ms/terminal-profiles-schema")" }; - -static constexpr std::string_view jsonExtension{ ".json" }; -static constexpr std::string_view FragmentsSubDirectory{ "\\Fragments" }; +static constexpr std::wstring_view jsonExtension{ L".json" }; +static constexpr std::wstring_view FragmentsSubDirectory{ L"\\Fragments" }; static constexpr std::wstring_view FragmentsPath{ L"\\Microsoft\\Windows Terminal\\Fragments" }; -static constexpr std::string_view AppExtensionHostName{ "com.microsoft.windows.terminal.settings" }; +static constexpr std::wstring_view AppExtensionHostName{ L"com.microsoft.windows.terminal.settings" }; + +// make sure this matches defaults.json. +static constexpr winrt::guid DEFAULT_WINDOWS_POWERSHELL_GUID{ 0x61c54bbd, 0xc2c6, 0x5271, { 0x96, 0xe7, 0x00, 0x9a, 0x87, 0xff, 0x44, 0xbf } }; +static constexpr winrt::guid DEFAULT_COMMAND_PROMPT_GUID{ 0x0caa0dad, 0x35be, 0x5f56, { 0xa8, 0xff, 0xaf, 0xce, 0xee, 0xaa, 0x61, 0x01 } }; // Function Description: // - Extracting the value from an async task (like talking to the app catalog) when we are on the // UI thread causes C++/WinRT to complain quite loudly (and halt execution!) // This templated function extracts the result from a task with chicanery. template -static auto _extractValueFromTaskWithoutMainThreadAwait(TTask&& task) -> decltype(task.get()) +static auto extractValueFromTaskWithoutMainThreadAwait(TTask&& task) -> decltype(task.get()) { - using TVal = decltype(task.get()); - std::optional finalVal{}; - std::condition_variable cv; - std::mutex mtx; + std::optional finalVal; + til::latch latch{ 1 }; - auto waitOnBackground = [&]() -> winrt::fire_and_forget { + const auto _ = [&]() -> winrt::fire_and_forget { co_await winrt::resume_background(); - auto v{ co_await task }; + finalVal.emplace(co_await task); + latch.count_down(); + }(); - std::unique_lock lock{ mtx }; - finalVal.emplace(std::move(v)); - cv.notify_all(); - }; - - std::unique_lock lock{ mtx }; - waitOnBackground(); - cv.wait(lock, [&]() { return finalVal.has_value(); }); - return *finalVal; + latch.wait(); + return finalVal.value(); } -static std::tuple _LineAndColumnFromPosition(const std::string_view string, ptrdiff_t position) +// Concatenates the two given strings (!) and returns them as a path. +// You better make sure there's a path separator at the end of lhs or at the start of rhs. +static std::filesystem::path buildPath(const std::wstring_view& lhs, const std::wstring_view& rhs) { - size_t line = 1, column = position + 1; - auto lastNL = string.find_last_of('\n', position); - if (lastNL != std::string::npos) - { - column = (position - lastNL); - line = std::count(string.cbegin(), string.cbegin() + lastNL + 1, '\n') + 1; - } - - return { line, column }; + std::wstring buffer; + buffer.reserve(lhs.size() + rhs.size()); + buffer.append(lhs); + buffer.append(rhs); + return { std::move(buffer) }; } -static void _CatchRethrowSerializationExceptionWithLocationInfo(std::string_view settingsString) +// This is a convenience method used by the CascadiaSettings constructor. +// It runs some basic settings layering without relying on external programs or files. +// This makes it suitable for most unit tests. +SettingsLoader SettingsLoader::Default(const std::string_view& userJSON, const std::string_view& inboxJSON) { - std::string msg; + SettingsLoader loader{ userJSON, inboxJSON }; + loader.MergeInboxIntoUserSettings(); + loader.FinalizeLayering(); + return loader; +} + +// The SettingsLoader class is an internal implementation detail of CascadiaSettings. +// Member methods aren't safe against misuse and you need to ensure to call them in a specific order. +// See CascadiaSettings::LoadAll() for a specific usage example. +// +// This constructor only handles parsing the two given JSON strings. +// At a minimum you should do at least everything that SettingsLoader::Default does. +SettingsLoader::SettingsLoader(const std::string_view& userJSON, const std::string_view& inboxJSON) +{ + _parse(OriginTag::InBox, {}, inboxJSON, inboxSettings); try { - throw; + _parse(OriginTag::User, {}, userJSON, userSettings); } catch (const JsonUtils::DeserializationError& e) { - static constexpr std::string_view basicHeader{ "* Line {line}, Column {column}\n{message}" }; - static constexpr std::string_view keyedHeader{ "* Line {line}, Column {column} ({key})\n{message}" }; + _rethrowSerializationExceptionWithLocationInfo(e, userJSON); + } - std::string jsonValueAsString{ "array or object" }; - try + if (const auto sources = userSettings.globals->DisabledProfileSources()) + { + _ignoredNamespaces.reserve(sources.Size()); + for (const auto& id : sources) { - jsonValueAsString = e.jsonValue.asString(); - if (e.jsonValue.isString()) + _ignoredNamespaces.emplace(id); + } + } + + // See member description of _userProfileCount. + _userProfileCount = userSettings.profiles.size(); +} + +// Generate dynamic profiles and add them to the list of "inbox" profiles +// (meaning profiles specified by the application rather by the user). +void SettingsLoader::GenerateProfiles() +{ + _executeGenerator(PowershellCoreProfileGenerator{}); + _executeGenerator(WslDistroGenerator{}); + _executeGenerator(AzureCloudShellGenerator{}); + _executeGenerator(VsDevCmdGenerator{}); + _executeGenerator(VsDevShellGenerator{}); +} + +// A new settings.json gets a special treatment: +// 1. The default profile is a PowerShell 7+ one, if one was generated, +// and falls back to the standard PowerShell 5 profile otherwise. +// 2. cmd.exe gets a localized name. +void SettingsLoader::ApplyRuntimeInitialSettings() +{ + // 1. + { + const auto preferredPowershellProfile = PowershellCoreProfileGenerator::GetPreferredPowershellProfileName(); + auto guid = DEFAULT_WINDOWS_POWERSHELL_GUID; + + for (const auto& profile : inboxSettings.profiles) + { + if (profile->Name() == preferredPowershellProfile) { - jsonValueAsString = fmt::format("\"{}\"", jsonValueAsString); + guid = profile->Guid(); + break; } } - catch (...) + + userSettings.globals->DefaultProfile(guid); + } + + // 2. + { + for (const auto& profile : userSettings.profiles) { - // discard: we're in the middle of error handling + if (profile->Guid() == DEFAULT_COMMAND_PROMPT_GUID) + { + profile->Name(RS_(L"CommandPromptDisplayName")); + break; + } + } + } +} + +// Adds profiles from .inboxSettings as parents of matching profiles in .userSettings. +// That way the user profiles will get appropriate defaults from the generators (like icons and such). +// If a matching profile doesn't exist yet in .userSettings, one will be created. +void SettingsLoader::MergeInboxIntoUserSettings() +{ + for (const auto& profile : inboxSettings.profiles) + { + if (const auto [it, inserted] = userSettings.profilesByGuid.emplace(profile->Guid(), profile); !inserted) + { + // If inserted is false, we got a matching user profile with identical GUID. + // --> The generated profile is a parent of the existing user profile. + it->second->InsertParent(profile); + } + else + { + // If inserted is true, then this is a generated profile that doesn't exist in the user's settings. + // While emplace() has already created an appropriate entry in .profilesByGuid, we still need to + // add it to .profiles (which is basically a sorted list of .profilesByGuid's values). + // + // When a user modifies a profile they shouldn't modify the (static/constant) + // inbox profile of course. That's why we need to call CreateChild here. + userSettings.profiles.emplace_back(CreateChild(profile)); + } + } +} + +// Searches AppData/ProgramData and app extension directories for settings JSON files. +// If such JSON files are found, they're read and their contents added to .userSettings. +// +// Of course it would be more elegant to add fragments to .inboxSettings first and then have MergeInboxIntoUserSettings +// merge them. Unfortunately however the "updates" key in fragment profiles make this impossible: +// The targeted profile might be one that got created as part of SettingsLoader::MergeInboxIntoUserSettings. +// Additionally the GUID in "updates" will conflict with existing GUIDs in .inboxSettings. +void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() +{ + ParsedSettings fragmentSettings; + + const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source) { + for (const auto& fragmentExt : std::filesystem::directory_iterator{ path }) + { + if (fragmentExt.path().extension() == jsonExtension) + { + try + { + const auto content = ReadUTF8File(fragmentExt.path()); + _parse(OriginTag::Fragment, source, content, fragmentSettings); + + for (const auto& fragmentProfile : fragmentSettings.profiles) + { + if (const auto updates = fragmentProfile->Updates(); updates != winrt::guid{}) + { + if (const auto it = userSettings.profilesByGuid.find(updates); it != userSettings.profilesByGuid.end()) + { + it->second->InsertParent(0, fragmentProfile); + } + } + else + { + _appendProfile(CreateChild(fragmentProfile), userSettings); + } + } + + for (const auto& kv : fragmentSettings.globals->ColorSchemes()) + { + userSettings.globals->AddColorScheme(kv.Value()); + } + } + CATCH_LOG(); + } + } + }; + + for (const auto& rfid : std::array{ FOLDERID_LocalAppData, FOLDERID_ProgramData }) + { + wil::unique_cotaskmem_string folder; + THROW_IF_FAILED(SHGetKnownFolderPath(rfid, 0, nullptr, &folder)); + + const auto fragmentPath = buildPath(folder.get(), FragmentsPath); + + if (std::filesystem::is_directory(fragmentPath)) + { + for (const auto& fragmentExtFolder : std::filesystem::directory_iterator{ fragmentPath }) + { + const auto filename = fragmentExtFolder.path().filename(); + const auto& source = filename.native(); + + if (!_ignoredNamespaces.count(std::wstring_view{ source }) && fragmentExtFolder.is_directory()) + { + parseAndLayerFragmentFiles(fragmentExtFolder.path(), winrt::hstring{ source }); + } + } + } + } + + // Search through app extensions + // Gets the catalog of extensions with the name "com.microsoft.windows.terminal.settings" + const auto catalog = winrt::Windows::ApplicationModel::AppExtensions::AppExtensionCatalog::Open(AppExtensionHostName); + const auto extensions = extractValueFromTaskWithoutMainThreadAwait(catalog.FindAllAsync()); + + for (const auto& ext : extensions) + { + const auto packageName = ext.Package().Id().FamilyName(); + if (_ignoredNamespaces.count(std::wstring_view{ packageName })) + { + continue; } - msg = fmt::format(" Have: {}\n Expected: {}", jsonValueAsString, e.expectedType); + // Likewise, getting the public folder from an extension is an async operation. + auto foundFolder = extractValueFromTaskWithoutMainThreadAwait(ext.GetPublicFolderAsync()); + if (!foundFolder) + { + continue; + } - auto [l, c] = _LineAndColumnFromPosition(settingsString, e.jsonValue.getOffsetStart()); - msg = fmt::format((e.key ? keyedHeader : basicHeader), - fmt::arg("line", l), - fmt::arg("column", c), - fmt::arg("key", e.key.value_or("")), - fmt::arg("message", msg)); - throw SettingsTypedDeserializationException{ msg }; + // the StorageFolder class has its own methods for obtaining the files within the folder + // however, all those methods are Async methods + // you may have noticed that we need to resort to clunky implementations for async operations + // (they are in extractValueFromTaskWithoutMainThreadAwait) + // so for now we will just take the folder path and access the files that way + const auto path = buildPath(foundFolder.Path(), FragmentsSubDirectory); + + if (std::filesystem::is_directory(path)) + { + parseAndLayerFragmentFiles(path, packageName); + } + } +} + +// Call this method before passing SettingsLoader to the CascadiaSettings constructor. +// It layers all remaining objects onto each other (those that aren't covered +// by MergeInboxIntoUserSettings/FindFragmentsAndMergeIntoUserSettings). +void SettingsLoader::FinalizeLayering() +{ + // Layer default globals -> user globals + userSettings.globals->InsertParent(inboxSettings.globals); + userSettings.globals->_FinalizeInheritance(); + // Layer default profile defaults -> user profile defaults + userSettings.baseLayerProfile->InsertParent(inboxSettings.baseLayerProfile); + userSettings.baseLayerProfile->_FinalizeInheritance(); + // Layer user profile defaults -> user profiles + for (const auto& profile : userSettings.profiles) + { + profile->InsertParent(0, userSettings.baseLayerProfile); + profile->_FinalizeInheritance(); + } +} + +// Let's say a user doesn't know that they need to write `"hidden": true` in +// order to prevent a profile from showing up (and a settings UI doesn't exist). +// Naturally they would open settings.json and try to remove the profile object. +// This section of code recognizes if a profile was seen before and marks it as +// `"hidden": true` by default and thus ensures the behavior the user expects: +// Profiles won't show up again after they've been removed from settings.json. +bool SettingsLoader::DisableDeletedProfiles() +{ + const auto& state = winrt::get_self(ApplicationState::SharedInstance()); + auto generatedProfileIds = state->GeneratedProfiles(); + bool newGeneratedProfiles = false; + + for (const auto& profile : _getNonUserOriginProfiles()) + { + if (generatedProfileIds.emplace(profile->Guid()).second) + { + newGeneratedProfiles = true; + } + else + { + profile->Deleted(true); + profile->Hidden(true); + } + } + + if (newGeneratedProfiles) + { + state->GeneratedProfiles(generatedProfileIds); + } + + return newGeneratedProfiles; +} + +// Give a string of length N and a position of [0,N) this function returns +// the line/column within the string, similar to how text editors do it. +// Newlines are considered part of the current line (as per POSIX). +std::pair SettingsLoader::_lineAndColumnFromPosition(const std::string_view& string, const size_t position) +{ + size_t line = 1; + size_t column = 0; + + for (;;) + { + const auto p = string.find('\n', column); + if (p >= position) + { + break; + } + + column = p + 1; + line++; + } + + return { line, position - column + 1 }; +} + +// Formats a JSON exception for humans to read and throws that. +void SettingsLoader::_rethrowSerializationExceptionWithLocationInfo(const JsonUtils::DeserializationError& e, const std::string_view& settingsString) +{ + std::string jsonValueAsString; + try + { + jsonValueAsString = e.jsonValue.asString(); + if (e.jsonValue.isString()) + { + jsonValueAsString = fmt::format("\"{}\"", jsonValueAsString); + } + } + catch (...) + { + jsonValueAsString = "array or object"; + } + + const auto [line, column] = _lineAndColumnFromPosition(settingsString, static_cast(e.jsonValue.getOffsetStart())); + + fmt::memory_buffer msg; + fmt::format_to(msg, "* Line {}, Column {}", line, column); + if (e.key) + { + fmt::format_to(msg, " ({})", *e.key); + } + fmt::format_to(msg, "\n Have: {}\n Expected: {}\0", jsonValueAsString, e.expectedType); + + throw SettingsTypedDeserializationException{ msg.data() }; +} + +// Simply parses the given content to a Json::Value. +Json::Value SettingsLoader::_parseJSON(const std::string_view& content) +{ + Json::Value json; + std::string errs; + const std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; + + if (!reader->parse(content.data(), content.data() + content.size(), &json, &errs)) + { + throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); + } + + return json; +} + +// A helper method similar to Json::Value::operator[], but compatible with std::string_view. +const Json::Value& SettingsLoader::_getJSONValue(const Json::Value& json, const std::string_view& key) noexcept +{ + if (json.isObject()) + { + if (const auto val = json.find(key.data(), key.data() + key.size())) + { + return *val; + } + } + + return Json::Value::nullSingleton(); +} + +// Returns true if the given Json::Value looks like a profile. +// We introduced a bug (GH#9962, fixed in GH#9964) that would result in one or +// more nameless, guid-less profiles being emitted into the user's settings file. +// Those profiles would show up in the list as "Default" later. +bool SettingsLoader::_isValidProfileObject(const Json::Value& profileJson) +{ + return profileJson.isObject() && + (profileJson.isMember(NameKey.data(), NameKey.data() + NameKey.size()) || // has a name (can generate a guid) + profileJson.isMember(GuidKey.data(), GuidKey.data() + GuidKey.size())); // or has a guid +} + +// We treat userSettings.profiles as an append-only array and will +// append profiles into the userSettings as necessary in this function. +// _userProfileCount stores the number of profiles that were in userJSON during construction. +// +// Thus no matter how many profiles are added later on, the following condition holds true: +// The userSettings.profiles in the range [0, _userProfileCount) contain all profiles specified by the user. +// In turn all profiles in the range [_userProfileCount, ∞) contain newly generated/added profiles. +// gsl::make_span(userSettings.profiles).subspan(_userProfileCount) gets us the latter range. +gsl::span> SettingsLoader::_getNonUserOriginProfiles() const +{ + return gsl::make_span(userSettings.profiles).subspan(_userProfileCount); +} + +// Parses the given JSON string ("content") and fills a ParsedSettings instance with it. +void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings) +{ + const auto json = content.empty() ? Json::Value{ Json::ValueType::objectValue } : _parseJSON(content); + const auto& profilesObject = _getJSONValue(json, ProfilesKey); + const auto& defaultsObject = _getJSONValue(profilesObject, DefaultSettingsKey); + const auto& profilesArray = profilesObject.isArray() ? profilesObject : _getJSONValue(profilesObject, ProfilesListKey); + + // globals + { + settings.globals = GlobalAppSettings::FromJson(json); + + if (const auto& schemes = _getJSONValue(json, SchemesKey)) + { + for (const auto& schemeJson : schemes) + { + if (schemeJson.isObject()) + { + if (const auto scheme = ColorScheme::FromJson(schemeJson)) + { + settings.globals->AddColorScheme(*scheme); + } + } + } + } + } + + // profiles.defaults + { + settings.baseLayerProfile = Profile::FromJson(defaultsObject); + // Remove the `guid` member from the default settings. + // That will hyper-explode, so just don't let them do that. + settings.baseLayerProfile->ClearGuid(); + settings.baseLayerProfile->Origin(OriginTag::ProfilesDefaults); + } + + // profiles.list + { + const auto size = profilesArray.size(); + + // NOTE: This function is supposed to *replace* the contents of ParsedSettings. Don't break this promise. + // SettingsLoader::FindFragmentsAndMergeIntoUserSettings relies on this. + settings.profiles.clear(); + settings.profiles.reserve(size); + + settings.profilesByGuid.clear(); + settings.profilesByGuid.reserve(size); + + for (const auto& profileJson : profilesArray) + { + if (_isValidProfileObject(profileJson)) + { + auto profile = Profile::FromJson(profileJson); + profile->Origin(origin); + + // The Guid() generation below depends on the value of Source(). + // --> Provide one if we got one. + if (!source.empty()) + { + profile->Source(source); + } + + // The Guid() getter generates one from Name() and Source() if none exists otherwise. + // We want to ensure that every profile has a GUID no matter what, not just to + // cache the value, but also to make them consistently identifiable later on. + if (!profile->HasGuid()) + { + profile->Guid(profile->Guid()); + } + + _appendProfile(std::move(profile), settings); + } + } + } +} + +// Adds a profile to the ParsedSettings instance. Takes ownership of the profile. +// It ensures no duplicate GUIDs are added to the ParsedSettings instance. +void SettingsLoader::_appendProfile(winrt::com_ptr&& profile, ParsedSettings& settings) +{ + // FYI: The static_cast ensures we don't move the profile into + // `profilesByGuid`, even though we still need it later for `profiles`. + if (settings.profilesByGuid.emplace(profile->Guid(), static_cast&>(profile)).second) + { + settings.profiles.emplace_back(profile); + } + else + { + duplicateProfile = true; + } +} + +// As the name implies it executes a generator. +// Generated profiles are added to .inboxSettings. Used by GenerateProfiles(). +void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator) +{ + const auto generatorNamespace = generator.GetNamespace(); + if (_ignoredNamespaces.count(generatorNamespace)) + { + return; + } + + const auto previousSize = inboxSettings.profiles.size(); + + try + { + generator.GenerateProfiles(inboxSettings.profiles); + } + CATCH_LOG_MSG("Dynamic Profile Namespace: \"%.*s\"", gsl::narrow(generatorNamespace.size()), generatorNamespace.data()) + + // If the generator produced some profiles we're going to give them default attributes. + // By setting the Origin/Source/etc. here, we deduplicate some code and ensure they aren't missing accidentally. + if (inboxSettings.profiles.size() > previousSize) + { + const winrt::hstring source{ generatorNamespace }; + + for (const auto& profile : gsl::span(inboxSettings.profiles).subspan(previousSize)) + { + profile->Origin(OriginTag::Generated); + profile->Source(source); + } } } @@ -141,156 +593,69 @@ static void _CatchRethrowSerializationExceptionWithLocationInfo(std::string_view // profiles inserted into their list of profiles. // Return Value: // - a unique_ptr containing a new CascadiaSettings object. -winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::LoadAll() +Model::CascadiaSettings CascadiaSettings::LoadAll() +try { - try + const auto settingsString = ReadUTF8FileIfExists(_settingsPath()).value_or(std::string{}); + const auto firstTimeSetup = settingsString.empty(); + const auto settingsStringView = firstTimeSetup ? UserSettingsJson : settingsString; + auto mustWriteToDisk = firstTimeSetup; + + SettingsLoader loader{ settingsStringView, DefaultJson }; + + // Generate dynamic profiles and add them as parents of user profiles. + // That way the user profiles will get appropriate defaults from the generators (like icons and such). + loader.GenerateProfiles(); + + // ApplyRuntimeInitialSettings depends on generated profiles. + // --> ApplyRuntimeInitialSettings must be called after GenerateProfiles. + if (firstTimeSetup) { - auto settings = LoadDefaults(); - auto resultPtr = winrt::get_self(settings); - resultPtr->ClearWarnings(); + loader.ApplyRuntimeInitialSettings(); + } - // GH 3588, we need this below to know if the user chose something that wasn't our default. - // Collect it up here in case it gets modified by any of the other layers between now and when - // the user's preferences are loaded and layered. - const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().DefaultProfile(); + loader.MergeInboxIntoUserSettings(); + // Fragments might reference user profiles created by a generator. + // --> FindFragmentsAndMergeIntoUserSettings must be called after MergeInboxIntoUserSettings. + loader.FindFragmentsAndMergeIntoUserSettings(); + loader.FinalizeLayering(); - std::optional fileData = _ReadUserSettings(); + // DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles. + // Coincidentally this is also the time we should write the new settings.json + // to disk (so that it contains the new profiles for manual editing by the user). + mustWriteToDisk |= loader.DisableDeletedProfiles(); - // Make sure the file isn't totally empty. If it is, we'll treat the file - // like it doesn't exist at all. - const bool fileHasData = fileData && !fileData->empty(); - bool needToWriteFile = false; - if (fileHasData) - { - resultPtr->_ParseJsonString(*fileData, false); - } + // If this throws, the app will catch it and use the default settings. + const auto settings = winrt::make_self(std::move(loader)); - // Load profiles from dynamic profile generators. _userSettings should be - // created by now, because we're going to check in there for any generators - // that should be disabled (if the user had any settings.) - resultPtr->_LoadDynamicProfiles(); + // If we created the file, or found new dynamic profiles, write the user + // settings string back to the file. + if (mustWriteToDisk) + { try { - resultPtr->_LoadFragmentExtensions(); - } - CATCH_LOG(); - - if (!fileHasData) - { - // We didn't find the user settings. We'll need to create a file - // to use as the user defaults. - // For now, just parse our user settings template as their user settings. - auto userSettings{ resultPtr->_ApplyFirstRunChangesToSettingsTemplate(UserSettingsJson) }; - resultPtr->_ParseJsonString(userSettings, false); - needToWriteFile = true; - } - - try - { - // See microsoft/terminal#2325: find the defaultSettings from the user's - // settings. Layer those settings upon all the existing profiles we have - // (defaults and dynamic profiles). We'll also set - // _userDefaultProfileSettings here. When we LayerJson below to apply the - // user settings, we'll make sure to use these defaultSettings _before_ any - // profiles the user might have. - resultPtr->_ApplyDefaultsFromUserSettings(); - - // Apply the user's settings - resultPtr->LayerJson(resultPtr->_userSettings); + settings->WriteSettingsToDisk(); } catch (...) { - _CatchRethrowSerializationExceptionWithLocationInfo(resultPtr->_userSettingsString); + LOG_CAUGHT_EXCEPTION(); + settings->_warnings.Append(SettingsLoadWarnings::FailedToWriteToSettings); } - - // Let's say a user doesn't know that they need to write `"hidden": true` in - // order to prevent a profile from showing up (and a settings UI doesn't exist). - // Naturally they would open settings.json and try to remove the profile object. - // This section of code recognizes if a profile was seen before and marks it as - // `"hidden": true` by default and thus ensures the behavior the user expects: - // Profiles won't show up again after they've been removed from settings.json. - { - const auto state = winrt::get_self(ApplicationState::SharedInstance()); - auto generatedProfiles = state->GeneratedProfiles(); - bool generatedProfilesChanged = false; - - for (const auto& profile : resultPtr->_allProfiles) - { - const auto profileImpl = winrt::get_self(profile); - - if (generatedProfiles.emplace(profileImpl->Guid()).second) - { - generatedProfilesChanged = true; - } - else if (profileImpl->Origin() != OriginTag::User) - { - profileImpl->Deleted(true); - profileImpl->Hidden(true); - } - } - - if (generatedProfilesChanged) - { - state->GeneratedProfiles(generatedProfiles); - } - } - - // After layering the user settings, check if there are any new profiles - // that need to be inserted into their user settings file. - needToWriteFile = resultPtr->_AppendDynamicProfilesToUserSettings() || needToWriteFile; - - if (needToWriteFile) - { - // For safety's sake, we need to re-parse the JSON document to ensure that - // all future patches are applied with updated object offsets. - resultPtr->_ParseJsonString(resultPtr->_userSettingsString, false); - } - - // Make sure there's a $schema at the top of the file. - needToWriteFile = resultPtr->_PrependSchemaDirective() || needToWriteFile; - - // TODO:GH#2721 If powershell core is installed, we need to set that to the - // default profile, but only when the settings file was newly created. We'll - // re-write the segment of the user settings for "default profile" to have - // the powershell core GUID instead. - - // If we created the file, or found new dynamic profiles, write the user - // settings string back to the file. - if (needToWriteFile) - { - // If AppendDynamicProfilesToUserSettings (or the pwsh check above) - // changed the file, then our local settings JSON is no longer accurate. - // We should re-parse, but not re-layer - resultPtr->_ParseJsonString(resultPtr->_userSettingsString, false); - - try - { - WriteUTF8FileAtomic(_SettingsPath(), resultPtr->_userSettingsString); - } - catch (...) - { - resultPtr->AppendWarning(SettingsLoadWarnings::FailedToWriteToSettings); - } - } - - // If this throws, the app will catch it and use the default settings - resultPtr->_ValidateSettings(); - - return *resultPtr; - } - catch (const SettingsException& ex) - { - auto settings{ winrt::make_self() }; - settings->_loadError = ex.Error(); - return *settings; - } - catch (const SettingsTypedDeserializationException& e) - { - auto settings{ winrt::make_self() }; - std::string_view what{ e.what() }; - settings->_deserializationErrorMessage = til::u8u16(what); - return *settings; } + + return *settings; +} +catch (const SettingsException& ex) +{ + const auto settings{ winrt::make_self() }; + settings->_loadError = ex.Error(); + return *settings; +} +catch (const SettingsTypedDeserializationException& e) +{ + const auto settings{ winrt::make_self() }; + settings->_deserializationErrorMessage = til::u8u16(e.what()); + return *settings; } // Function Description: @@ -299,37 +664,9 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings:: // - // Return Value: // - a unique_ptr to a CascadiaSettings with the connection types and settings for Universal terminal -winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::LoadUniversal() +Model::CascadiaSettings CascadiaSettings::LoadUniversal() { - // We're going to do this ourselves because we want to exclude almost everything - // from the special Universal-for-developers configuration - - try - { - // Create settings and get the universal defaults loaded up. - auto resultPtr = winrt::make_self(); - resultPtr->_ParseJsonString(DefaultUniversalJson, true); - resultPtr->LayerJson(resultPtr->_defaultSettings); - - // Now validate. - // If this throws, the app will catch it and use the default settings - resultPtr->_ValidateSettings(); - - return *resultPtr; - } - catch (const SettingsException& ex) - { - auto settings{ winrt::make_self() }; - settings->_loadError = ex.Error(); - return *settings; - } - catch (const SettingsTypedDeserializationException& e) - { - auto settings{ winrt::make_self() }; - std::string_view what{ e.what() }; - settings->_deserializationErrorMessage = til::u8u16(what); - return *settings; - } + return *winrt::make_self(std::string_view{}, DefaultUniversalJson); } // Function Description: @@ -339,761 +676,80 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings:: // - // Return Value: // - a unique_ptr to a CascadiaSettings with the settings from defaults.json -winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::LoadDefaults() +Model::CascadiaSettings CascadiaSettings::LoadDefaults() { - auto resultPtr{ winrt::make_self() }; - - // We already have the defaults in memory, because we stamp them into a - // header as part of the build process. We don't need to bother with reading - // them from a file (and the potential that could fail) - resultPtr->_ParseJsonString(DefaultJson, true); - resultPtr->LayerJson(resultPtr->_defaultSettings); - resultPtr->_ResolveDefaultProfile(); - resultPtr->_UpdateActiveProfiles(); - - // tag these profiles as in-box - for (const auto& profile : resultPtr->AllProfiles()) - { - const auto profileImpl{ winrt::get_self(profile) }; - profileImpl->Origin(OriginTag::InBox); - } - - return *resultPtr; + return *winrt::make_self(std::string_view{}, DefaultJson); } -// Method Description: -// - Runs each of the configured dynamic profile generators (DPGs). Adds -// profiles from any DPGs that ran to the end of our list of profiles. -// - Uses the Json::Value _userSettings to check which DPGs should not be run. -// If the user settings has any namespaces in the "disabledProfileSources" -// property, we'll ensure that any DPGs with a matching namespace _don't_ run. -// Arguments: -// - -// Return Value: -// - -void CascadiaSettings::_LoadDynamicProfiles() +CascadiaSettings::CascadiaSettings(const winrt::hstring& userJSON, const winrt::hstring& inboxJSON) : + CascadiaSettings{ SettingsLoader::Default(til::u16u8(userJSON), til::u16u8(inboxJSON)) } { - std::unordered_set ignoredNamespaces; - const auto disabledProfileSources = CascadiaSettings::_GetDisabledProfileSourcesJsonObject(_userSettings); - if (disabledProfileSources.isArray()) - { - for (const auto& json : disabledProfileSources) - { - ignoredNamespaces.emplace(JsonUtils::GetValue(json)); - } - } +} - for (auto& generator : _profileGenerators) - { - const std::wstring generatorNamespace{ generator->GetNamespace() }; +CascadiaSettings::CascadiaSettings(const std::string_view& userJSON, const std::string_view& inboxJSON) : + CascadiaSettings{ SettingsLoader::Default(userJSON, inboxJSON) } +{ +} - if (ignoredNamespaces.find(generatorNamespace) != ignoredNamespaces.end()) +CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) +{ + std::vector allProfiles; + std::vector activeProfiles; + std::vector warnings; + + allProfiles.reserve(loader.userSettings.profiles.size()); + activeProfiles.reserve(loader.userSettings.profiles.size()); + + for (const auto& profile : loader.userSettings.profiles) + { + // If a generator stops producing a certain profile (e.g. WSL or PowerShell were removed) or + // a profile from a fragment doesn't exist anymore, we should also stop including the + // matching user's profile in _allProfiles (since they aren't functional anyways). + // + // A user profile has a valid, dynamic parent if it has a parent with identical source. + if (const auto source = profile->Source(); !source.empty()) { - // namespace should be ignored - } - else - { - try + const auto& parents = profile->Parents(); + if (std::none_of(parents.begin(), parents.end(), [&](const auto& parent) { return parent->Source() == source; })) { - auto profiles = generator->GenerateProfiles(); - for (auto& profile : profiles) - { - profile.Source(generatorNamespace); - - _allProfiles.Append(profile); - } - } - CATCH_LOG_MSG("Dynamic Profile Namespace: \"%ls\"", generatorNamespace.data()); - } - } -} - -// Method Description: -// - Searches the local app data folder, global app data folder and app -// extensions for json stubs we should use to create new profiles, -// modify existing profiles or add new color schemes -// - If the user settings has any namespaces in the "disabledProfileSources" -// property, we'll ensure that the corresponding folders do not get searched -void CascadiaSettings::_LoadFragmentExtensions() -{ - // First, accumulate the namespaces the user wants to ignore - std::unordered_set ignoredNamespaces; - const auto disabledProfileSources = CascadiaSettings::_GetDisabledProfileSourcesJsonObject(_userSettings); - if (disabledProfileSources.isArray()) - { - for (const auto& json : disabledProfileSources) - { - ignoredNamespaces.emplace(JsonUtils::GetValue(json)); - } - } - - // Search through the local app data folder - wil::unique_cotaskmem_string localAppDataFolder; - THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder)); - auto localAppDataFragments = std::wstring(localAppDataFolder.get()) + FragmentsPath.data(); - - if (std::filesystem::exists(localAppDataFragments)) - { - _ApplyJsonStubsHelper(localAppDataFragments, ignoredNamespaces); - } - - // Search through the program data folder - wil::unique_cotaskmem_string programDataFolder; - THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData, 0, nullptr, &programDataFolder)); - auto programDataFragments = std::wstring(programDataFolder.get()) + FragmentsPath.data(); - if (std::filesystem::exists(programDataFragments)) - { - _ApplyJsonStubsHelper(programDataFragments, ignoredNamespaces); - } - - // Search through app extensions - // Gets the catalog of extensions with the name "com.microsoft.windows.terminal.settings" - const auto catalog = Windows::ApplicationModel::AppExtensions::AppExtensionCatalog::Open(winrt::to_hstring(AppExtensionHostName)); - - auto extensions = _extractValueFromTaskWithoutMainThreadAwait(catalog.FindAllAsync()); - - for (const auto& ext : extensions) - { - // Only apply the stubs if the package name is not in ignored namespaces - if (ignoredNamespaces.find(ext.Package().Id().FamilyName().c_str()) == ignoredNamespaces.end()) - { - // Likewise, getting the public folder from an extension is an async operation - // So we use another mutex and condition variable - auto foundFolder = _extractValueFromTaskWithoutMainThreadAwait(ext.GetPublicFolderAsync()); - - if (foundFolder) - { - // the StorageFolder class has its own methods for obtaining the files within the folder - // however, all those methods are Async methods - // you may have noticed that we need to resort to clunky implementations for async operations - // (they are in _extractValueFromTaskWithoutMainThreadAwait) - // so for now we will just take the folder path and access the files that way - auto path = winrt::to_string(foundFolder.Path()); - path.append(FragmentsSubDirectory); - - // If the directory exists, use the fragments in it - if (std::filesystem::exists(path)) - { - const auto jsonFiles = _AccumulateJsonFilesInDirectory(til::u8u16(path)); - - // Provide the package name as the source - _ParseAndLayerFragmentFiles(jsonFiles, ext.Package().Id().FamilyName().c_str()); - } - } - } - } -} - -// Method Description: -// - Helper function to apply json stubs in the local app data folder and the global program data folder -// Arguments: -// - The directory to find json files in -// - The set of ignored namespaces -void CascadiaSettings::_ApplyJsonStubsHelper(const std::wstring_view directory, const std::unordered_set& ignoredNamespaces) -{ - // The json files should be within subdirectories where the subdirectory name is the app name - for (const auto& fragmentExtFolder : std::filesystem::directory_iterator(directory)) - { - // We only want the parent folder name as the source (not the full path) - const auto source = fragmentExtFolder.path().filename().wstring(); - - // Only apply the stubs if the parent folder name is not in ignored namespaces - // (also make sure this is a directory for sanity) - if (std::filesystem::is_directory(fragmentExtFolder) && ignoredNamespaces.find(source) == ignoredNamespaces.end()) - { - const auto jsonFiles = _AccumulateJsonFilesInDirectory(fragmentExtFolder.path().c_str()); - _ParseAndLayerFragmentFiles(jsonFiles, winrt::hstring{ source }); - } - } -} - -// Method Description: -// - Finds all the json files within the given directory -// Arguments: -// - directory: the directory to search -// Return Value: -// - A set containing all the found file data -std::unordered_set CascadiaSettings::_AccumulateJsonFilesInDirectory(const std::wstring_view directory) -{ - std::unordered_set jsonFiles; - - for (const auto& fragmentExt : std::filesystem::directory_iterator(directory)) - { - if (fragmentExt.path().extension() == jsonExtension) - { - try - { - jsonFiles.emplace(ReadUTF8File(fragmentExt.path())); - } - CATCH_LOG(); - } - } - return jsonFiles; -} - -// Method Description: -// - Given a set of json files, uses them to modify existing profiles, -// create new profiles, and create new color schemes -// Arguments: -// - files: the set of json files (each item in the set is the file data) -// - source: the location the files came from -void CascadiaSettings::_ParseAndLayerFragmentFiles(const std::unordered_set files, const winrt::hstring source) -{ - for (const auto& file : files) - { - // A file could have many new profiles/many profiles it wants to modify/many new color schemes - // so we first parse the entire file into one json object - auto fullFile = _ParseUtf8JsonString(file.data()); - - if (fullFile.isMember(JsonKey(ProfilesKey))) - { - // Now we separately get each stub that modifies/adds a profile - // We intentionally don't use a const reference here because we modify - // the profile stub by giving it a guid so we can call _FindMatchingProfile - for (auto& profileStub : fullFile[JsonKey(ProfilesKey)]) - { - if (profileStub.isMember(JsonKey(UpdatesKey))) - { - // This stub is meant to be a modification to an existing profile, - // try to find the matching profile - profileStub[JsonKey(GuidKey)] = profileStub[JsonKey(UpdatesKey)]; - auto matchingProfile = _FindMatchingProfile(profileStub); - if (matchingProfile) - { - try - { - // We found a matching profile, create a child of it and put the modifications there - // (we add a new inheritance layer) - auto childImpl{ matchingProfile->CreateChild() }; - childImpl->LayerJson(profileStub); - childImpl->Origin(OriginTag::Fragment); - - // replace parent in _profiles with child - _allProfiles.SetAt(_FindMatchingProfileIndex(matchingProfile->ToJson()).value(), *childImpl); - } - catch (...) - { - } - } - } - else - { - // This is a new profile, check that it meets our minimum requirements first - // (it must have at least a name) - if (profileStub.isMember(JsonKey(NameKey))) - { - try - { - auto newProfile = Profile::FromJson(profileStub); - // Make sure to give the new profile a source, then we add it to our list of profiles - // We don't make modifications to the user's settings file yet, that will happen when - // _AppendDynamicProfilesToUserSettings() is called later - newProfile->Source(source); - newProfile->Origin(OriginTag::Fragment); - _allProfiles.Append(*newProfile); - } - catch (...) - { - } - } - } + continue; } } - if (fullFile.isMember(JsonKey(SchemesKey))) + allProfiles.emplace_back(*profile); + if (!profile->Hidden()) { - // Now we separately get each stub that adds a color scheme - for (const auto& schemeStub : fullFile[JsonKey(SchemesKey)]) - { - if (_FindMatchingColorScheme(schemeStub)) - { - // We do not allow modifications to existing color schemes - } - else - { - // This is a new color scheme, add it only if it specifies _all_ the fields - if (ColorScheme::ValidateColorScheme(schemeStub)) - { - const auto newScheme = ColorScheme::FromJson(schemeStub); - _globals->AddColorScheme(*newScheme); - } - } - } - } - } -} - -// Method Description: -// - Attempts to read the given data as a string of JSON and parse that JSON -// into a Json::Value. -// - Will ignore leading UTF-8 BOMs. -// - Additionally, will store the parsed JSON in this object, as either our -// _defaultSettings or our _userSettings, depending on isDefaultSettings. -// - Does _not_ apply the json onto our current settings. Callers should make -// sure to call LayerJson to ensure the settings are applied. -// Arguments: -// - fileData: the string to parse as JSON data -// - isDefaultSettings: if true, we should store the parsed JSON as our -// defaultSettings. Otherwise, we'll store the parsed JSON as our user -// settings. -// Return Value: -// - -void CascadiaSettings::_ParseJsonString(std::string_view fileData, const bool isDefaultSettings) -{ - // Parse the json data into either our defaults or user settings. We'll keep - // these original json values around for later, in case we need to parse - // their raw contents again. - Json::Value& root = isDefaultSettings ? _defaultSettings : _userSettings; - - root = _ParseUtf8JsonString(fileData); - - // If this is the user settings, also store away the original settings - // string. We'll need to keep it around so we can modify it without - // re-serializing their settings. - if (!isDefaultSettings) - { - _userSettingsString = fileData; - } -} - -// Method Description: -// - Attempts to read the given data as a string of JSON and parse that JSON -// into a Json::Value -// - Will ignore leading UTF-8 BOMs -// Arguments: -// - fileData: the string to parse as JSON data -// Return value: -// - the parsed json value -Json::Value CascadiaSettings::_ParseUtf8JsonString(std::string_view fileData) -{ - Json::Value result; - const auto actualDataStart = fileData.data(); - const auto actualDataEnd = fileData.data() + fileData.size(); - - std::string errs; // This string will receive any error text from failing to parse. - std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; - - // `parse` will return false if it fails. - if (!reader->parse(actualDataStart, actualDataEnd, &result, &errs)) - { - // This will be caught by App::_TryLoadSettings, who will display - // the text to the user. - throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); - } - return result; -} - -// Method Description: -// - Determines whether the user's settings file is missing a schema directive -// and, if so, inserts one. -// - Assumes that the body of the root object is at an indentation of 4 spaces, and -// therefore each member should be indented 4 spaces. If the user's settings -// have a different indentation, we'll still insert valid json, it'll just be -// indented incorrectly. -// Arguments: -// - -// Return Value: -// - true iff we've made changes to the _userSettingsString that should be persisted. -bool CascadiaSettings::_PrependSchemaDirective() -{ - if (_userSettings.isMember(JsonKey(SchemaKey))) - { - return false; - } - - // start points at the opening { for the root object. - auto offset = _userSettings.getOffsetStart() + 1; - _userSettingsString.insert(offset, SettingsSchemaFragment); - offset += SettingsSchemaFragment.size(); - if (_userSettings.size() > 0) - { - _userSettingsString.insert(offset, ","); - } - return true; -} - -// Method Description: -// - Finds all the dynamic profiles we've generated that _don't_ exist in the -// user's settings. Generates a minimal blob of json for them, and inserts -// them into the user's settings at the end of the list of profiles. -// - Does not reformat the user's settings file. -// - Does not write the file! Only modifies in-place the _userSettingsString -// member. Callers should make sure to persist these changes (see WriteSettingsToDisk). -// - Assumes that the `profiles` object is at an indentation of 4 spaces, and -// therefore each profile should be indented 8 spaces. If the user's settings -// have a different indentation, we'll still insert valid json, it'll just be -// indented incorrectly. -// Arguments: -// - -// Return Value: -// - true iff we've made changes to the _userSettingsString that should be persisted. -bool CascadiaSettings::_AppendDynamicProfilesToUserSettings() -{ - // - Find the set of profiles that weren't either in the default profiles or - // in the user profiles. TODO:GH#2723 Do this in not O(N^2) - // - For each of those profiles, - // * Diff them from the default profile - // * Serialize that diff - // * Insert that diff to the end of the list of profiles. - - Json::StreamWriterBuilder wbuilder; - // Use 4 spaces to indent instead of \t - wbuilder.settings_["indentation"] = " "; - wbuilder.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons - - static const auto isInJsonObj = [](const auto& profile, const auto& json) { - for (auto profileJson : _GetProfilesJsonObject(json)) - { - if (profileJson.isObject()) - { - const auto profileImpl = winrt::get_self(profile); - if (profileImpl->ShouldBeLayered(profileJson)) - { - return true; - } - // If the profileJson doesn't have a GUID, then it might be in - // the file still. We returned false because it shouldn't be - // layered, but it might be a name-only profile. - } - } - return false; - }; - - // Get the index in the user settings string of the _last_ profile. - // We want to start inserting profiles immediately following the last profile. - const auto userProfilesObj = _GetProfilesJsonObject(_userSettings); - const auto numProfiles = userProfilesObj.size(); - const auto lastProfile = userProfilesObj[numProfiles - 1]; - size_t currentInsertIndex = lastProfile.getOffsetLimit(); - // Find the position of the first non-tab/space character before the last profile... - const auto lastProfileIndentStartsAt{ _userSettingsString.find_last_not_of(" \t", lastProfile.getOffsetStart() - 1) }; - // ... and impute the user's preferred indentation. - // (we're taking a copy because a string_view into a string we mutate is a no-no.) - const std::string indentation{ _userSettingsString, lastProfileIndentStartsAt + 1, lastProfile.getOffsetStart() - lastProfileIndentStartsAt - 1 }; - - bool changedFile = false; - - for (const auto& profile : _allProfiles) - { - // Skip profiles that are: - // * hidden - // Because when a user manually removes profiles from settings.json, - // we mark them as hidden in LoadAll(). Adding those profiles right - // back into settings.json would feel confusing, while the - // profile that was just erased is added right back. - // * in the user settings or the default settings - // Because we don't want to add profiles which are already - // in the settings.json (explicitly or implicitly). - if (profile.Deleted() || isInJsonObj(profile, _userSettings) || isInJsonObj(profile, _defaultSettings)) - { - continue; - } - - // Generate a diff for the profile, that contains the minimal set of - // changes to re-create this profile. - const auto profileImpl = winrt::get_self(profile); - const auto diff = profileImpl->GenerateStub(); - - auto profileSerialization = Json::writeString(wbuilder, diff); - - // Add the user's indent to the start of each line - profileSerialization.insert(0, indentation); - // Get the first newline - size_t pos = profileSerialization.find("\n"); - // for each newline... - while (pos != std::string::npos) - { - // Insert 8 spaces immediately following the current newline - profileSerialization.insert(pos + 1, indentation); - // Get the next newline - pos = profileSerialization.find("\n", pos + indentation.size() + 1); - } - - // Write a comma, newline to the file - changedFile = true; - _userSettingsString.insert(currentInsertIndex, ","); - currentInsertIndex++; - _userSettingsString.insert(currentInsertIndex, "\n"); - currentInsertIndex++; - - // Write the profile's serialization to the file - _userSettingsString.insert(currentInsertIndex, profileSerialization); - currentInsertIndex += profileSerialization.size(); - } - - return changedFile; -} - -// Function Description: -// - Given a json serialization of a profile, this function will determine -// whether it is "well-formed". We introduced a bug (GH#9962, fixed in GH#9964) -// that would result in one or more nameless, guid-less profiles being emitted -// into the user's settings file. Those profiles would show up in the list as -// "Default" later. -static bool _IsValidProfileObject(const Json::Value& profileJson) -{ - return profileJson.isMember(&*NameKey.cbegin(), (&*NameKey.cbegin()) + NameKey.size()) || // has a name (can generate a guid) - profileJson.isMember(&*GuidKey.cbegin(), (&*GuidKey.cbegin()) + GuidKey.size()); // or has a guid -} - -// Method Description: -// - Create a new instance of this class from a serialized JsonObject. -// Arguments: -// - json: an object which should be a serialization of a CascadiaSettings object. -// Return Value: -// - a new CascadiaSettings instance created from the values in `json` -winrt::com_ptr CascadiaSettings::FromJson(const Json::Value& json) -{ - auto resultPtr = winrt::make_self(); - resultPtr->LayerJson(json); - return resultPtr; -} - -// Method Description: -// - Layer values from the given json object on top of the existing properties -// of this object. For any keys we're expecting to be able to parse in the -// given object, we'll parse them and replace our settings with values from -// the new json object. Properties that _aren't_ in the json object will _not_ -// be replaced. -// Arguments: -// - json: an object which should be a partial serialization of a CascadiaSettings object. -// Return Value: -// -void CascadiaSettings::LayerJson(const Json::Value& json) -{ - // add a new inheritance layer, and apply json values to child - _globals = _globals->CreateChild(); - _globals->LayerJson(json); - - if (auto schemes{ json[SchemesKey.data()] }) - { - for (auto schemeJson : schemes) - { - if (schemeJson.isObject()) - { - _LayerOrCreateColorScheme(schemeJson); - } + activeProfiles.emplace_back(*profile); } } - for (auto profileJson : _GetProfilesJsonObject(json)) + if (allProfiles.empty()) { - if (profileJson.isObject() && _IsValidProfileObject(profileJson)) - { - _LayerOrCreateProfile(profileJson); - } + throw SettingsException(SettingsLoadErrors::NoProfiles); } -} - -// Method Description: -// - Given a partial json serialization of a Profile object, either layers that -// json on a matching Profile we already have, or creates a new Profile -// object from those settings. -// - For profiles that were created from a dynamic profile source, they'll have -// both a guid and source guid that must both match. If a user profile with a -// source set does not find a matching profile at load time, the profile -// should be ignored. -// Arguments: -// - json: an object which may be a partial serialization of a Profile object. -// Return Value: -// - -void CascadiaSettings::_LayerOrCreateProfile(const Json::Value& profileJson) -{ - // Layer the json on top of an existing profile, if we have one: - winrt::com_ptr profile{ nullptr }; - auto profileIndex{ _FindMatchingProfileIndex(profileJson) }; - if (profileIndex) + if (activeProfiles.empty()) { - auto parentProj{ _allProfiles.GetAt(*profileIndex) }; - auto parent{ winrt::get_self(parentProj) }; - - if (_userDefaultProfileSettings) - { - // We don't actually need to CreateChild() here. - // When we loaded Profile.Defaults, we created an empty child already. - // So this just populates the empty child - parent->LayerJson(profileJson); - profile.copy_from(parent); - } - else - { - // otherwise, add a new inheritance layer - auto childImpl{ parent->CreateChild() }; - childImpl->LayerJson(profileJson); - - // replace parent in _profiles with child - _allProfiles.SetAt(*profileIndex, *childImpl); - profile = std::move(childImpl); - } - } - else - { - // If this JSON represents a dynamic profile, we _shouldn't_ create the - // profile here. We only want to create profiles for profiles without a - // `source`. Dynamic profiles _must_ be layered on an existing profile. - if (!Profile::IsDynamicProfileObject(profileJson)) - { - profile = winrt::make_self(); - - // GH#2325: If we have a set of default profile settings, set that as my parent. - // We _won't_ have these settings yet for defaults, dynamic profiles. - if (_userDefaultProfileSettings) - { - Profile::InsertParentHelper(profile, _userDefaultProfileSettings, 0); - } - - profile->LayerJson(profileJson); - _allProfiles.Append(*profile); - } + throw SettingsException(SettingsLoadErrors::AllProfilesHidden); } - if (profile && _userDefaultProfileSettings) + if (loader.duplicateProfile) { - // If we've loaded defaults{} we're in the "user settings" phase for sure - profile->Origin(OriginTag::User); - } -} - -// Method Description: -// - Finds a profile from our list of profiles that matches the given json -// object. Uses Profile::ShouldBeLayered to determine if the Json::Value is a -// match or not. This method should be used to find a profile to layer the -// given settings upon. -// - Returns nullptr if no such match exists. -// Arguments: -// - json: an object which may be a partial serialization of a Profile object. -// Return Value: -// - a Profile that can be layered with the given json object, iff such a -// profile exists. -winrt::com_ptr CascadiaSettings::_FindMatchingProfile(const Json::Value& profileJson) -{ - auto index{ _FindMatchingProfileIndex(profileJson) }; - if (index) - { - auto profile{ _allProfiles.GetAt(*index) }; - auto profileImpl{ winrt::get_self(profile) }; - return profileImpl->get_strong(); - } - return nullptr; -} - -// Method Description: -// - Finds a profile from our list of profiles that matches the given json -// object. Uses Profile::ShouldBeLayered to determine if the Json::Value is a -// match or not. This method should be used to find a profile to layer the -// given settings upon. -// - Returns nullopt if no such match exists. -// Arguments: -// - json: an object which may be a partial serialization of a Profile object. -// Return Value: -// - The index for the matching Profile, iff it exists. Otherwise, nullopt. -std::optional CascadiaSettings::_FindMatchingProfileIndex(const Json::Value& profileJson) -{ - for (uint32_t i = 0; i < _allProfiles.Size(); ++i) - { - const auto profile{ _allProfiles.GetAt(i) }; - const auto profileImpl = winrt::get_self(profile); - if (profileImpl->ShouldBeLayered(profileJson)) - { - return i; - } - } - return std::nullopt; -} - -// Method Description: -// - Finds the "default profile settings" if they exist in the users settings, -// and applies them to the existing profiles. The "default profile settings" -// are settings that should be applied to every profile a user has, with the -// option of being overridden by explicit values in the profile. This should -// be called _after_ the defaults have been parsed and dynamic profiles have -// been generated, but before the other user profiles have been loaded. -// Arguments: -// - -// Return Value: -// - -void CascadiaSettings::_ApplyDefaultsFromUserSettings() -{ - // If `profiles` was an object, then look for the `defaults` object - // underneath it for the default profile settings. - // If there isn't one, we still want to add an empty "default" profile to the inheritance tree. - Json::Value defaultSettings{ Json::ValueType::objectValue }; - if (const auto profiles{ _userSettings[JsonKey(ProfilesKey)] }) - { - if (profiles.isObject() && !profiles[JsonKey(DefaultSettingsKey)].empty()) - { - defaultSettings = profiles[JsonKey(DefaultSettingsKey)]; - } + warnings.emplace_back(Model::SettingsLoadWarnings::DuplicateProfile); } - // Remove the `guid` member from the default settings. That'll - // hyper-explode, so just don't let them do that. - defaultSettings.removeMember({ "guid" }); + // SettingsLoader and ParsedSettings are supposed to always + // create these two members. We don't want null-pointer exceptions. + assert(loader.userSettings.globals != nullptr); + assert(loader.userSettings.baseLayerProfile != nullptr); - _userDefaultProfileSettings = winrt::make_self(); - _userDefaultProfileSettings->LayerJson(defaultSettings); - _userDefaultProfileSettings->Origin(OriginTag::ProfilesDefaults); + _globals = loader.userSettings.globals; + _baseLayerProfile = loader.userSettings.baseLayerProfile; + _allProfiles = winrt::single_threaded_observable_vector(std::move(allProfiles)); + _activeProfiles = winrt::single_threaded_observable_vector(std::move(activeProfiles)); + _warnings = winrt::single_threaded_vector(std::move(warnings)); - const auto numOfProfiles{ _allProfiles.Size() }; - for (uint32_t profileIndex = 0; profileIndex < numOfProfiles; ++profileIndex) - { - // create a child, so we inherit from the defaults.json layer - auto parentProj{ _allProfiles.GetAt(profileIndex) }; - auto parentImpl{ winrt::get_self(parentProj) }; - auto childImpl{ parentImpl->CreateChild() }; - - // Add profile.defaults as the _first_ parent to the child - Profile::InsertParentHelper(childImpl, _userDefaultProfileSettings, 0); - - // replace parent in _profiles with child - _allProfiles.SetAt(profileIndex, *childImpl); - } -} - -// Method Description: -// - Given a partial json serialization of a ColorScheme object, either layers that -// json on a matching ColorScheme we already have, or creates a new ColorScheme -// object from those settings. -// Arguments: -// - json: an object which should be a partial serialization of a ColorScheme object. -// Return Value: -// - -void CascadiaSettings::_LayerOrCreateColorScheme(const Json::Value& schemeJson) -{ - // Layer the json on top of an existing profile, if we have one: - auto pScheme = _FindMatchingColorScheme(schemeJson); - if (pScheme) - { - pScheme->LayerJson(schemeJson); - } - else - { - const auto scheme = ColorScheme::FromJson(schemeJson); - _globals->AddColorScheme(*scheme); - } -} - -// Method Description: -// - Finds a color scheme from our list of color schemes that matches the given -// json object. Uses ColorScheme::GetNameFromJson to find the name and then -// performs a lookup in the global map. This method should be used to find a -// color scheme to layer the given settings upon. -// - Returns nullptr if no such match exists. -// Arguments: -// - json: an object which should be a partial serialization of a ColorScheme object. -// Return Value: -// - a ColorScheme that can be layered with the given json object, iff such a -// color scheme exists. -winrt::com_ptr CascadiaSettings::_FindMatchingColorScheme(const Json::Value& schemeJson) -{ - if (auto schemeName = ColorScheme::GetNameFromJson(schemeJson)) - { - if (auto scheme{ _globals->ColorSchemes().TryLookup(*schemeName) }) - { - return winrt::get_self(scheme)->get_strong(); - } - } - return nullptr; + _resolveDefaultProfile(); + _validateSettings(); } // Method Description: @@ -1102,26 +758,12 @@ winrt::com_ptr CascadiaSettings::_FindMatchingColorScheme(const Jso // - // Return Value: // - Returns a path in 80% of cases. I measured! -const std::filesystem::path& CascadiaSettings::_SettingsPath() +const std::filesystem::path& CascadiaSettings::_settingsPath() { static const auto path = GetBaseSettingsPath() / SettingsFilename; return path; } -// Method Description: -// - Reads the content in UTF-8 encoding of our settings file using the Win32 APIs -// Arguments: -// - -// Return Value: -// - an optional with the content of the file if we were able to open it, -// otherwise the optional will be empty. -// If the file exists, but we fail to read it, this can throw an exception -// from reading the file -std::optional CascadiaSettings::_ReadUserSettings() -{ - return ReadUTF8FileIfExists(_SettingsPath()); -} - // function Description: // - Returns the full path to the settings file, either within the application // package, or in its unpackaged location. This path is under the "Local @@ -1134,7 +776,7 @@ std::optional CascadiaSettings::_ReadUserSettings() // - the full path to the settings file winrt::hstring CascadiaSettings::SettingsPath() { - return winrt::hstring{ _SettingsPath().wstring() }; + return winrt::hstring{ _settingsPath().native() }; } winrt::hstring CascadiaSettings::DefaultSettingsPath() @@ -1149,44 +791,12 @@ winrt::hstring CascadiaSettings::DefaultSettingsPath() // directory as the exe, that will work for unpackaged scenarios as well. So // let's try that. - std::wstring exePathString; - THROW_IF_FAILED(wil::GetModuleFileNameW(nullptr, exePathString)); + const auto exePathString = wil::GetModuleFileNameW(nullptr); std::filesystem::path path{ exePathString }; path.replace_filename(DefaultsFilename); - return winrt::hstring{ path.wstring() }; -} -// Function Description: -// - Gets the object in the given JSON object under the "profiles" key. Returns -// null if there's no "profiles" key. -// Arguments: -// - json: the json object to get the profiles from. -// Return Value: -// - the Json::Value representing the profiles property from the given object -const Json::Value& CascadiaSettings::_GetProfilesJsonObject(const Json::Value& json) -{ - const auto& profilesProperty = json[JsonKey(ProfilesKey)]; - return profilesProperty.isArray() ? - profilesProperty : - profilesProperty[JsonKey(ProfilesListKey)]; -} - -// Function Description: -// - Gets the object in the given JSON object under the "disabledProfileSources" -// key. Returns null if there's no "disabledProfileSources" key. -// Arguments: -// - json: the json object to get the disabled profile sources from. -// Return Value: -// - the Json::Value representing the `disabledProfileSources` property from the -// given object -const Json::Value& CascadiaSettings::_GetDisabledProfileSourcesJsonObject(const Json::Value& json) -{ - if (!json) - { - return Json::Value::nullSingleton(); - } - return json[JsonKey(DisabledProfileSourcesKey)]; + return winrt::hstring{ path.native() }; } // Method Description: @@ -1199,15 +809,13 @@ const Json::Value& CascadiaSettings::_GetDisabledProfileSourcesJsonObject(const // - void CascadiaSettings::WriteSettingsToDisk() const { - const auto settingsPath = _SettingsPath(); + const auto settingsPath = _settingsPath(); - try { // create a timestamped backup file - const auto backupSettingsPath = fmt::format(L"{}.{:%Y-%m-%dT%H-%M-%S}.backup", settingsPath.wstring(), fmt::localtime(std::time(nullptr))); - WriteUTF8File(backupSettingsPath, _userSettingsString); + const auto backupSettingsPath = fmt::format(L"{}.{:%Y-%m-%dT%H-%M-%S}.backup", settingsPath.native(), fmt::localtime(std::time(nullptr))); + LOG_IF_WIN32_BOOL_FALSE(CopyFileW(settingsPath.c_str(), backupSettingsPath.c_str(), TRUE)); } - CATCH_LOG(); // write current settings to current settings file Json::StreamWriterBuilder wbuilder; @@ -1218,14 +826,10 @@ void CascadiaSettings::WriteSettingsToDisk() const WriteUTF8FileAtomic(settingsPath, styledString); // Persists the default terminal choice - // - // GH#10003 - Only do this if _currentDefaultTerminal was actually - // initialized. It's only initialized when Launch.cpp calls - // `CascadiaSettings::RefreshDefaultTerminals`. We really don't need it - // otherwise. + // GH#10003 - Only do this if _currentDefaultTerminal was actually initialized. if (_currentDefaultTerminal) { - Model::DefaultTerminal::Current(_currentDefaultTerminal); + DefaultTerminal::Current(_currentDefaultTerminal); } } @@ -1238,24 +842,19 @@ void CascadiaSettings::WriteSettingsToDisk() const Json::Value CascadiaSettings::ToJson() const { // top-level json object - // directly inject "globals", "$schema", and "disabledProfileSources" into here Json::Value json{ _globals->ToJson() }; - JsonUtils::SetValueForKey(json, SchemaKey, JsonKey(SchemaValue)); - if (_userSettings.isMember(JsonKey(DisabledProfileSourcesKey))) - { - json[JsonKey(DisabledProfileSourcesKey)] = _userSettings[JsonKey(DisabledProfileSourcesKey)]; - } + json["$help"] = "https://aka.ms/terminal-documentation"; + json["$schema"] = "https://aka.ms/terminal-profiles-schema"; // "profiles" will always be serialized as an object Json::Value profiles{ Json::ValueType::objectValue }; - profiles[JsonKey(DefaultSettingsKey)] = _userDefaultProfileSettings ? _userDefaultProfileSettings->ToJson() : - Json::ValueType::objectValue; + profiles[JsonKey(DefaultSettingsKey)] = _baseLayerProfile ? _baseLayerProfile->ToJson() : Json::ValueType::objectValue; Json::Value profilesList{ Json::ValueType::arrayValue }; for (const auto& entry : _allProfiles) { if (!entry.Deleted()) { - const auto prof{ winrt::get_self(entry) }; + const auto prof{ winrt::get_self(entry) }; profilesList.append(prof->ToJson()); } } @@ -1268,10 +867,30 @@ Json::Value CascadiaSettings::ToJson() const Json::Value schemes{ Json::ValueType::arrayValue }; for (const auto& entry : _globals->ColorSchemes()) { - const auto scheme{ winrt::get_self(entry.Value()) }; + const auto scheme{ winrt::get_self(entry.Value()) }; schemes.append(scheme->ToJson()); } json[JsonKey(SchemesKey)] = schemes; return json; } + +// Method Description: +// - Resolves the "defaultProfile", which can be a profile name, to a GUID +// and stores it back to the globals. +void CascadiaSettings::_resolveDefaultProfile() const +{ + if (const auto unparsedDefaultProfile = _globals->UnparsedDefaultProfile(); !unparsedDefaultProfile.empty()) + { + if (const auto profile = GetProfileByName(unparsedDefaultProfile)) + { + _globals->DefaultProfile(profile.Guid()); + return; + } + + _warnings.Append(SettingsLoadWarnings::MissingDefaultProfile); + } + + // Use the first profile as the new default. + GlobalSettings().DefaultProfile(_allProfiles.GetAt(0).Guid()); +} diff --git a/src/cascadia/TerminalSettingsModel/ColorScheme.cpp b/src/cascadia/TerminalSettingsModel/ColorScheme.cpp index ddbe2269e..76ba3de6f 100644 --- a/src/cascadia/TerminalSettingsModel/ColorScheme.cpp +++ b/src/cascadia/TerminalSettingsModel/ColorScheme.cpp @@ -3,7 +3,6 @@ #include "pch.h" #include "ColorScheme.h" -#include "DefaultSettings.h" #include "../../types/inc/Utils.hpp" #include "../../types/inc/colorTable.hpp" #include "Utils.h" @@ -41,25 +40,16 @@ static constexpr std::array TableColors = { "brightWhite" }; -ColorScheme::ColorScheme() : - ColorScheme(L"", DEFAULT_FOREGROUND, DEFAULT_BACKGROUND, DEFAULT_CURSOR_COLOR) +ColorScheme::ColorScheme() noexcept : + ColorScheme{ winrt::hstring{} } { - Utils::InitializeCampbellColorTable(_table); } -ColorScheme::ColorScheme(winrt::hstring name) : - ColorScheme(name, DEFAULT_FOREGROUND, DEFAULT_BACKGROUND, DEFAULT_CURSOR_COLOR) -{ - Utils::InitializeCampbellColorTable(_table); -} - -ColorScheme::ColorScheme(winrt::hstring name, til::color defaultFg, til::color defaultBg, til::color cursorColor) : - _Name{ name }, - _Foreground{ defaultFg }, - _Background{ defaultBg }, - _SelectionBackground{ DEFAULT_FOREGROUND }, - _CursorColor{ cursorColor } +ColorScheme::ColorScheme(const winrt::hstring& name) noexcept : + _Name{ name } { + const auto table = Utils::CampbellColorTable(); + std::copy_n(table.data(), table.size(), _table.data()); } winrt::com_ptr ColorScheme::Copy() const @@ -79,30 +69,11 @@ winrt::com_ptr ColorScheme::Copy() const // Arguments: // - json: an object which should be a serialization of a ColorScheme object. // Return Value: -// - a new ColorScheme instance created from the values in `json` +// - Returns nullptr for invalid JSON. winrt::com_ptr ColorScheme::FromJson(const Json::Value& json) { - auto result = winrt::make_self(); - result->LayerJson(json); - return result; -} - -// Method Description: -// - Returns true if we think the provided json object represents an instance of -// the same object as this object. If true, we should layer that json object -// on us, instead of creating a new object. -// Arguments: -// - json: The json object to query to see if it's the same -// Return Value: -// - true iff the json object has the same `name` as we do. -bool ColorScheme::ShouldBeLayered(const Json::Value& json) const -{ - std::wstring nameFromJson{}; - if (JsonUtils::GetValueForKey(json, NameKey, nameFromJson)) - { - return nameFromJson == _Name; - } - return false; + auto result = winrt::make_self(uninitialized_t{}); + return result->_layerJson(json) ? result : nullptr; } // Method Description: @@ -112,21 +83,27 @@ bool ColorScheme::ShouldBeLayered(const Json::Value& json) const // the new json object. Properties that _aren't_ in the json object will _not_ // be replaced. // Arguments: -// - json: an object which should be a partial serialization of a ColorScheme object. +// - json: an object which should be a full serialization of a ColorScheme object. // Return Value: -// -void ColorScheme::LayerJson(const Json::Value& json) +// - Returns true if the given JSON was valid. +bool ColorScheme::_layerJson(const Json::Value& json) { - JsonUtils::GetValueForKey(json, NameKey, _Name); + // Required fields + auto isValid = JsonUtils::GetValueForKey(json, NameKey, _Name); + + // Optional fields (they have defaults in ColorScheme.h) JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground); JsonUtils::GetValueForKey(json, BackgroundKey, _Background); JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _SelectionBackground); JsonUtils::GetValueForKey(json, CursorColorKey, _CursorColor); + // Required fields for (unsigned int i = 0; i < TableColors.size(); ++i) { - JsonUtils::GetValueForKey(json, til::at(TableColors, i), _table.at(i)); + isValid &= JsonUtils::GetValueForKey(json, til::at(TableColors, i), til::at(_table, i)); } + + return isValid; } // Method Description: @@ -147,7 +124,7 @@ Json::Value ColorScheme::ToJson() const for (unsigned int i = 0; i < TableColors.size(); ++i) { - JsonUtils::SetValueForKey(json, til::at(TableColors, i), _table.at(i)); + JsonUtils::SetValueForKey(json, til::at(TableColors, i), til::at(_table, i)); } return json; @@ -155,9 +132,7 @@ Json::Value ColorScheme::ToJson() const winrt::com_array ColorScheme::Table() const noexcept { - winrt::com_array result{ base::checked_cast(_table.size()) }; - std::transform(_table.begin(), _table.end(), result.begin(), [](til::color c) -> winrt::Microsoft::Terminal::Core::Color { return c; }); - return result; + return winrt::com_array{ _table }; } // Method Description: @@ -167,44 +142,8 @@ winrt::com_array ColorScheme::Table() c // - value: the color value we are setting the color table color to // Return Value: // - none -void ColorScheme::SetColorTableEntry(uint8_t index, const winrt::Microsoft::Terminal::Core::Color& value) noexcept +void ColorScheme::SetColorTableEntry(uint8_t index, const Core::Color& value) noexcept { - THROW_HR_IF(E_INVALIDARG, index > _table.size() - 1); + THROW_HR_IF(E_INVALIDARG, index >= _table.size()); _table[index] = value; } - -// Method Description: -// - Validates a given color scheme -// - A color scheme is valid if it has a name and defines all the colors -// Arguments: -// - The color scheme to validate -// Return Value: -// - true if the scheme is valid, false otherwise -bool ColorScheme::ValidateColorScheme(const Json::Value& scheme) -{ - for (const auto& key : TableColors) - { - if (!scheme.isMember(JsonKey(key))) - { - return false; - } - } - if (!scheme.isMember(JsonKey(NameKey))) - { - return false; - } - return true; -} - -// Method Description: -// - Parse the name from the JSON representation of a ColorScheme. -// Arguments: -// - json: an object which should be a serialization of a ColorScheme object. -// Return Value: -// - the name of the color scheme represented by `json` as a std::wstring optional -// i.e. the value of the `name` property. -// - returns std::nullopt if `json` doesn't have the `name` property -std::optional ColorScheme::GetNameFromJson(const Json::Value& json) -{ - return JsonUtils::GetValueForKey>(json, NameKey); -} diff --git a/src/cascadia/TerminalSettingsModel/ColorScheme.h b/src/cascadia/TerminalSettingsModel/ColorScheme.h index a7eed8ce7..4d5e580b0 100644 --- a/src/cascadia/TerminalSettingsModel/ColorScheme.h +++ b/src/cascadia/TerminalSettingsModel/ColorScheme.h @@ -15,38 +15,28 @@ Author(s): --*/ #pragma once + #include "../../inc/conattrs.hpp" #include "inc/cppwinrt_utils.h" +#include "DefaultSettings.h" #include "ColorScheme.g.h" -// fwdecl unittest classes -namespace SettingsModelLocalTests -{ - class SettingsTests; - class ColorSchemeTests; -}; - -// Use this macro to quick implement both the getter and setter for a color property. -// This should only be used for color types where there's no logic in the -// getter/setter beyond just accessing/updating the value. -// This takes advantage of til::color -#define WINRT_TERMINAL_COLOR_PROPERTY(name, ...) \ -public: \ - winrt::Microsoft::Terminal::Core::Color name() const noexcept { return _##name; } \ - void name(const winrt::Microsoft::Terminal::Core::Color& value) noexcept { _##name = value; } \ - \ -private: \ - til::color _##name{ __VA_ARGS__ }; - namespace winrt::Microsoft::Terminal::Settings::Model::implementation { struct ColorScheme : ColorSchemeT { + // A ColorScheme constructed with uninitialized_t + // leaves _table uninitialized. + struct uninitialized_t + { + }; + public: - ColorScheme(); - ColorScheme(hstring name); - ColorScheme(hstring name, til::color defaultFg, til::color defaultBg, til::color cursorColor); + ColorScheme() noexcept; + explicit ColorScheme(uninitialized_t) noexcept {} + explicit ColorScheme(const winrt::hstring& name) noexcept; + com_ptr Copy() const; hstring ToString() @@ -55,29 +45,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } static com_ptr FromJson(const Json::Value& json); - bool ShouldBeLayered(const Json::Value& json) const; - void LayerJson(const Json::Value& json); - Json::Value ToJson() const; - static std::optional GetNameFromJson(const Json::Value& json); - - com_array Table() const noexcept; - void SetColorTableEntry(uint8_t index, const winrt::Microsoft::Terminal::Core::Color& value) noexcept; - - static bool ValidateColorScheme(const Json::Value& scheme); + com_array Table() const noexcept; + void SetColorTableEntry(uint8_t index, const Core::Color& value) noexcept; WINRT_PROPERTY(winrt::hstring, Name); - WINRT_TERMINAL_COLOR_PROPERTY(Foreground); // defined in constructor - WINRT_TERMINAL_COLOR_PROPERTY(Background); // defined in constructor - WINRT_TERMINAL_COLOR_PROPERTY(SelectionBackground); // defined in constructor - WINRT_TERMINAL_COLOR_PROPERTY(CursorColor); // defined in constructor + WINRT_PROPERTY(Core::Color, Foreground, static_cast(DEFAULT_FOREGROUND)); + WINRT_PROPERTY(Core::Color, Background, static_cast(DEFAULT_BACKGROUND)); + WINRT_PROPERTY(Core::Color, SelectionBackground, static_cast(DEFAULT_FOREGROUND)); + WINRT_PROPERTY(Core::Color, CursorColor, static_cast(DEFAULT_CURSOR_COLOR)); private: - std::array _table; + bool _layerJson(const Json::Value& json); - friend class SettingsModelLocalTests::SettingsTests; - friend class SettingsModelLocalTests::ColorSchemeTests; + std::array _table; }; } diff --git a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.cpp b/src/cascadia/TerminalSettingsModel/DynamicProfileUtils.cpp similarity index 50% rename from src/cascadia/TerminalSettingsModel/DefaultProfileUtils.cpp rename to src/cascadia/TerminalSettingsModel/DynamicProfileUtils.cpp index 46b16e702..57f2e49f6 100644 --- a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.cpp +++ b/src/cascadia/TerminalSettingsModel/DynamicProfileUtils.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT license. #include "pch.h" -#include "DefaultProfileUtils.h" +#include "DynamicProfileUtils.h" #include "../../types/inc/utils.hpp" static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///ProfileIcons/" }; @@ -15,20 +15,16 @@ static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" }; // - name: the name of the new profile. // Return Value: // - A Profile, ready to be filled in -winrt::Microsoft::Terminal::Settings::Model::Profile CreateDefaultProfile(const std::wstring_view name) +winrt::com_ptr CreateDynamicProfile(const std::wstring_view& name) { - const winrt::guid profileGuid{ Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, - gsl::as_bytes(gsl::make_span(name))) }; - - auto newProfile = winrt::make_self(profileGuid); - newProfile->Name(winrt::hstring{ name }); + const auto profileGuid = Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(name))); std::wstring iconPath{ PACKAGED_PROFILE_ICON_PATH }; iconPath.append(Microsoft::Console::Utils::GuidToString(profileGuid)); iconPath.append(PACKAGED_PROFILE_ICON_EXTENSION); - newProfile->Icon(winrt::hstring{ iconPath }); - newProfile->Origin(winrt::Microsoft::Terminal::Settings::Model::OriginTag::Generated); - - return *newProfile; + auto profile = winrt::make_self(profileGuid); + profile->Name(winrt::hstring{ name }); + profile->Icon(winrt::hstring{ iconPath }); + return profile; } diff --git a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.h b/src/cascadia/TerminalSettingsModel/DynamicProfileUtils.h similarity index 84% rename from src/cascadia/TerminalSettingsModel/DefaultProfileUtils.h rename to src/cascadia/TerminalSettingsModel/DynamicProfileUtils.h index 5a401d120..cf3e6e9e1 100644 --- a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.h +++ b/src/cascadia/TerminalSettingsModel/DynamicProfileUtils.h @@ -23,4 +23,4 @@ Author(s): // uuidv5 properties: name format is UTF-16LE bytes static constexpr GUID TERMINAL_PROFILE_NAMESPACE_GUID = { 0x2bde4a90, 0xd05f, 0x401c, { 0x94, 0x92, 0xe4, 0x8, 0x84, 0xea, 0xd1, 0xd8 } }; -winrt::Microsoft::Terminal::Settings::Model::Profile CreateDefaultProfile(const std::wstring_view name); +winrt::com_ptr CreateDynamicProfile(const std::wstring_view& name); diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.cpp b/src/cascadia/TerminalSettingsModel/FileUtils.cpp index 81fbb6cee..cf273d5a5 100644 --- a/src/cascadia/TerminalSettingsModel/FileUtils.cpp +++ b/src/cascadia/TerminalSettingsModel/FileUtils.cpp @@ -11,7 +11,7 @@ static constexpr std::string_view Utf8Bom{ u8"\uFEFF" }; static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" }; -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { // Returns a path like C:\Users\\AppData\Local\Packages\\LocalState // You can put your settings.json or state.json in this directory. @@ -106,7 +106,7 @@ namespace Microsoft::Terminal::Settings::Model } } - void WriteUTF8File(const std::filesystem::path& path, const std::string_view content) + void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content) { wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; THROW_LAST_ERROR_IF(!file); @@ -121,7 +121,7 @@ namespace Microsoft::Terminal::Settings::Model } } - void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content) + void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content) { // GH#10787: rename() will replace symbolic links themselves and not the path they point at. // It's thus important that we first resolve them before generating temporary path. diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.h b/src/cascadia/TerminalSettingsModel/FileUtils.h index d2e2eb53c..c003228c3 100644 --- a/src/cascadia/TerminalSettingsModel/FileUtils.h +++ b/src/cascadia/TerminalSettingsModel/FileUtils.h @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { std::filesystem::path GetBaseSettingsPath(); std::string ReadUTF8File(const std::filesystem::path& path); std::optional ReadUTF8FileIfExists(const std::filesystem::path& path); - void WriteUTF8File(const std::filesystem::path& path, const std::string_view content); - void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content); + void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content); + void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content); } diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.cpp b/src/cascadia/TerminalSettingsModel/FontConfig.cpp index 2d5f1bcd9..5d57eb355 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/FontConfig.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "FontConfig.h" #include "FontConfig.g.cpp" + #include "TerminalSettingsSerializationHelpers.h" #include "JsonUtils.h" @@ -25,7 +26,7 @@ winrt::Microsoft::Terminal::Settings::Model::implementation::FontConfig::FontCon { } -winrt::com_ptr FontConfig::CopyFontInfo(const winrt::com_ptr source, winrt::weak_ref sourceProfile) +winrt::com_ptr FontConfig::CopyFontInfo(const FontConfig* source, winrt::weak_ref sourceProfile) { auto fontInfo{ winrt::make_self(std::move(sourceProfile)) }; fontInfo->_FontFace = source->_FontFace; diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.h b/src/cascadia/TerminalSettingsModel/FontConfig.h index 61f816dba..6c53ed4ea 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.h +++ b/src/cascadia/TerminalSettingsModel/FontConfig.h @@ -32,7 +32,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { public: FontConfig(winrt::weak_ref sourceProfile); - static winrt::com_ptr CopyFontInfo(const winrt::com_ptr source, winrt::weak_ref sourceProfile); + static winrt::com_ptr CopyFontInfo(const FontConfig* source, winrt::weak_ref sourceProfile); Json::Value ToJson() const; void LayerJson(const Json::Value& json); bool HasAnyOptionSet() const; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 4a69f148a..4a8860e3d 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -4,9 +4,7 @@ #include "pch.h" #include "GlobalAppSettings.h" #include "../../types/inc/Utils.hpp" -#include "../../inc/DefaultSettings.h" #include "JsonUtils.h" -#include "TerminalSettingsSerializationHelpers.h" #include "KeyChordSerialization.h" #include "GlobalAppSettings.g.cpp" @@ -52,6 +50,7 @@ static constexpr std::string_view WindowingBehaviorKey{ "windowingBehavior" }; static constexpr std::string_view TrimBlockSelectionKey{ "trimBlockSelection" }; static constexpr std::string_view AlwaysShowNotificationIconKey{ "alwaysShowNotificationIcon" }; static constexpr std::string_view MinimizeToNotificationAreaKey{ "minimizeToNotificationArea" }; +static constexpr std::string_view DisabledProfileSourcesKey{ "disabledProfileSources" }; static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" }; @@ -60,26 +59,6 @@ static constexpr std::string_view SoftwareRenderingKey{ "experimental.rendering. static constexpr std::string_view ForceVTInputKey{ "experimental.input.forceVT" }; static constexpr std::string_view DetectURLsKey{ "experimental.detectURLs" }; -#ifdef _DEBUG -static constexpr bool debugFeaturesDefault{ true }; -#else -static constexpr bool debugFeaturesDefault{ false }; -#endif - -bool GlobalAppSettings::_getDefaultDebugFeaturesValue() -{ - return debugFeaturesDefault; -} - -GlobalAppSettings::GlobalAppSettings() : - _actionMap{ winrt::make_self() }, - _keybindingsWarnings{}, - _validDefaultProfile{ false }, - _defaultProfile{} -{ - _colorSchemes = winrt::single_threaded_map(); -} - // Method Description: // - Copies any extraneous data from the parent before completing a CreateChild call // Arguments: @@ -88,13 +67,17 @@ GlobalAppSettings::GlobalAppSettings() : // - void GlobalAppSettings::_FinalizeInheritance() { - // Globals only ever has 1 parent - FAIL_FAST_IF(_parents.size() > 1); - for (auto parent : _parents) + for (const auto& parent : _parents) { _actionMap->InsertParent(parent->_actionMap); - _keybindingsWarnings = std::move(parent->_keybindingsWarnings); - _colorSchemes = std::move(parent->_colorSchemes); + _keybindingsWarnings.insert(_keybindingsWarnings.end(), parent->_keybindingsWarnings.begin(), parent->_keybindingsWarnings.end()); + for (const auto& [k, v] : parent->_colorSchemes) + { + if (!_colorSchemes.HasKey(k)) + { + _colorSchemes.Insert(k, v); + } + } } } @@ -137,12 +120,12 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_DetectURLs = _DetectURLs; globals->_MinimizeToNotificationArea = _MinimizeToNotificationArea; globals->_AlwaysShowNotificationIcon = _AlwaysShowNotificationIcon; - + globals->_DisabledProfileSources = _DisabledProfileSources; globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile; - globals->_validDefaultProfile = _validDefaultProfile; + globals->_defaultProfile = _defaultProfile; globals->_actionMap = _actionMap->Copy(); - std::copy(_keybindingsWarnings.begin(), _keybindingsWarnings.end(), std::back_inserter(globals->_keybindingsWarnings)); + globals->_keybindingsWarnings = _keybindingsWarnings; if (_colorSchemes) { @@ -153,9 +136,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const } } - // Globals only ever has 1 parent - FAIL_FAST_IF(_parents.size() > 1); - for (auto parent : _parents) + for (const auto& parent : _parents) { globals->InsertParent(parent->Copy()); } @@ -168,69 +149,18 @@ winrt::Windows::Foundation::Collections::IMapView GlobalAppSettings::_getUnparsedDefaultProfileImpl() const -{ - /*return user set value*/ - if (_UnparsedDefaultProfile) - { - return _UnparsedDefaultProfile; - } - - /*user set value was not set*/ - /*iterate through parents to find a value*/ - for (auto parent : _parents) - { - if (auto val{ parent->_getUnparsedDefaultProfileImpl() }) - { - return val; - } - } - - /*no value was found*/ - return std::nullopt; -} #pragma endregion winrt::Microsoft::Terminal::Settings::Model::ActionMap GlobalAppSettings::ActionMap() const noexcept @@ -253,92 +183,53 @@ winrt::com_ptr GlobalAppSettings::FromJson(const Json::Value& void GlobalAppSettings::LayerJson(const Json::Value& json) { - // _validDefaultProfile keeps track of whether we've verified that DefaultProfile points to something - // CascadiaSettings::_ResolveDefaultProfile performs a validation and updates DefaultProfile() with the - // resolved value, then making it valid. - _validDefaultProfile = false; JsonUtils::GetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile); - JsonUtils::GetValueForKey(json, AlwaysShowTabsKey, _AlwaysShowTabs); - JsonUtils::GetValueForKey(json, ConfirmCloseAllKey, _ConfirmCloseAllTabs); - JsonUtils::GetValueForKey(json, InitialRowsKey, _InitialRows); - JsonUtils::GetValueForKey(json, InitialColsKey, _InitialCols); - JsonUtils::GetValueForKey(json, InitialPositionKey, _InitialPosition); - JsonUtils::GetValueForKey(json, CenterOnLaunchKey, _CenterOnLaunch); - JsonUtils::GetValueForKey(json, ShowTitleInTitlebarKey, _ShowTitleInTitlebar); - JsonUtils::GetValueForKey(json, ShowTabsInTitlebarKey, _ShowTabsInTitlebar); - JsonUtils::GetValueForKey(json, WordDelimitersKey, _WordDelimiters); - JsonUtils::GetValueForKey(json, CopyOnSelectKey, _CopyOnSelect); - JsonUtils::GetValueForKey(json, InputServiceWarningKey, _InputServiceWarning); - JsonUtils::GetValueForKey(json, CopyFormattingKey, _CopyFormatting); - JsonUtils::GetValueForKey(json, WarnAboutLargePasteKey, _WarnAboutLargePaste); - JsonUtils::GetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste); - JsonUtils::GetValueForKey(json, FirstWindowPreferenceKey, _FirstWindowPreference); - JsonUtils::GetValueForKey(json, LaunchModeKey, _LaunchMode); - JsonUtils::GetValueForKey(json, LanguageKey, _Language); - JsonUtils::GetValueForKey(json, ThemeKey, _Theme); - JsonUtils::GetValueForKey(json, TabWidthModeKey, _TabWidthMode); - JsonUtils::GetValueForKey(json, UseAcrylicInTabRowKey, _UseAcrylicInTabRow); - JsonUtils::GetValueForKey(json, SnapToGridOnResizeKey, _SnapToGridOnResize); - // GetValueForKey will only override the current value if the key exists JsonUtils::GetValueForKey(json, DebugFeaturesKey, _DebugFeaturesEnabled); - JsonUtils::GetValueForKey(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering); - JsonUtils::GetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering); JsonUtils::GetValueForKey(json, ForceVTInputKey, _ForceVTInput); - JsonUtils::GetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin); - JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); - // GH#8076 - when adding enum values to this key, we also changed it from // "useTabSwitcher" to "tabSwitcherMode". Continue supporting // "useTabSwitcher", but prefer "tabSwitcherMode" JsonUtils::GetValueForKey(json, LegacyUseTabSwitcherModeKey, _TabSwitcherMode); JsonUtils::GetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode); - JsonUtils::GetValueForKey(json, DisableAnimationsKey, _DisableAnimations); - JsonUtils::GetValueForKey(json, StartupActionsKey, _StartupActions); - JsonUtils::GetValueForKey(json, FocusFollowMouseKey, _FocusFollowMouse); - JsonUtils::GetValueForKey(json, WindowingBehaviorKey, _WindowingBehavior); - JsonUtils::GetValueForKey(json, TrimBlockSelectionKey, _TrimBlockSelection); - JsonUtils::GetValueForKey(json, DetectURLsKey, _DetectURLs); - JsonUtils::GetValueForKey(json, MinimizeToNotificationAreaKey, _MinimizeToNotificationArea); - JsonUtils::GetValueForKey(json, AlwaysShowNotificationIconKey, _AlwaysShowNotificationIcon); + JsonUtils::GetValueForKey(json, DisabledProfileSourcesKey, _DisabledProfileSources); - // This is a helper lambda to get the keybindings and commands out of both - // and array of objects. We'll use this twice, once on the legacy - // `keybindings` key, and again on the newer `bindings` key. - auto parseBindings = [this, &json](auto jsonKey) { + static constexpr std::array bindingsKeys{ LegacyKeybindingsKey, ActionsKey }; + for (const auto& jsonKey : bindingsKeys) + { if (auto bindings{ json[JsonKey(jsonKey)] }) { auto warnings = _actionMap->LayerJson(bindings); @@ -351,9 +242,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) // list of warnings. _keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end()); } - }; - parseBindings(LegacyKeybindingsKey); - parseBindings(ActionsKey); + } } // Method Description: @@ -381,7 +270,7 @@ void GlobalAppSettings::RemoveColorScheme(hstring schemeName) // - // Return Value: // - -std::vector GlobalAppSettings::KeybindingsWarnings() const +const std::vector& GlobalAppSettings::KeybindingsWarnings() const { return _keybindingsWarnings; } @@ -433,7 +322,8 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, TrimBlockSelectionKey, _TrimBlockSelection); JsonUtils::SetValueForKey(json, DetectURLsKey, _DetectURLs); JsonUtils::SetValueForKey(json, MinimizeToNotificationAreaKey, _MinimizeToNotificationArea); - JsonUtils::SetValueForKey(json, AlwaysShowNotificationIconKey, _AlwaysShowNotificationIcon); + JsonUtils::SetValueForKey(json, AlwaysShowNotificationIconKey, _AlwaysShowNotificationIcon); + JsonUtils::SetValueForKey(json, DisabledProfileSourcesKey, _DisabledProfileSources); // clang-format on json[JsonKey(ActionsKey)] = _actionMap->ToJson(); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 53f47856c..87d87445d 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -34,7 +34,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct GlobalAppSettings : GlobalAppSettingsT, IInheritable { public: - GlobalAppSettings(); void _FinalizeInheritance() override; com_ptr Copy() const; @@ -49,16 +48,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ToJson() const; - std::vector KeybindingsWarnings() const; + const std::vector& KeybindingsWarnings() const; - // These are implemented manually to handle the string/GUID exchange - // by higher layers in the app. + // This DefaultProfile() setter is called by CascadiaSettings, + // when it parses UnparsedDefaultProfile in _finalizeSettings(). void DefaultProfile(const guid& defaultProfile) noexcept; guid DefaultProfile() const; - bool HasUnparsedDefaultProfile() const; - winrt::hstring UnparsedDefaultProfile() const; - void UnparsedDefaultProfile(const hstring& value); - void ClearUnparsedDefaultProfile(); // TODO GH#9207: Remove this once we have a GlobalAppSettingsViewModel in TerminalSettingsEditor void SetInvertedDisableAnimationsValue(bool invertedDisableAnimationsValue) @@ -90,7 +85,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ForceFullRepaintRendering, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, SoftwareRendering, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ForceVTInput, false); - INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DebugFeaturesEnabled, _getDefaultDebugFeaturesValue()); + INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DebugFeaturesEnabled, debugFeaturesDefault); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, StartOnUserLogin, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysOnTop, false); INHERITABLE_SETTING(Model::GlobalAppSettings, Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::InOrder); @@ -102,21 +97,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DetectURLs, true); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, MinimizeToNotificationArea, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysShowNotificationIcon, false); + INHERITABLE_SETTING(Model::GlobalAppSettings, winrt::Windows::Foundation::Collections::IVector, DisabledProfileSources, nullptr); + INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L""); private: - guid _defaultProfile; - std::optional _UnparsedDefaultProfile{ std::nullopt }; - bool _validDefaultProfile; +#ifdef NDEBUG + static constexpr bool debugFeaturesDefault{ false }; +#else + static constexpr bool debugFeaturesDefault{ true }; +#endif - com_ptr _actionMap; + winrt::guid _defaultProfile; + winrt::com_ptr _actionMap{ winrt::make_self() }; std::vector _keybindingsWarnings; - - Windows::Foundation::Collections::IMap _colorSchemes; - - std::optional _getUnparsedDefaultProfileImpl() const; - static bool _getDefaultDebugFeaturesValue(); - - friend class SettingsModelLocalTests::DeserializationTests; - friend class SettingsModelLocalTests::ColorSchemeTests; + Windows::Foundation::Collections::IMap _colorSchemes{ winrt::single_threaded_map() }; }; } diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index aabcda7b3..ea1d7de79 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -83,6 +83,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, DetectURLs); INHERITABLE_SETTING(Boolean, MinimizeToNotificationArea); INHERITABLE_SETTING(Boolean, AlwaysShowNotificationIcon); + INHERITABLE_SETTING(IVector, DisabledProfileSources); Windows.Foundation.Collections.IMapView ColorSchemes(); void AddColorScheme(ColorScheme scheme); diff --git a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h index 2b5504965..a0455ecce 100644 --- a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h @@ -20,18 +20,16 @@ Author(s): --*/ #pragma once + #include "Profile.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { - class IDynamicProfileGenerator; + class IDynamicProfileGenerator + { + public: + virtual ~IDynamicProfileGenerator(){}; + virtual std::wstring_view GetNamespace() const noexcept = 0; + virtual void GenerateProfiles(std::vector>& profiles) const = 0; + }; }; - -class Microsoft::Terminal::Settings::Model::IDynamicProfileGenerator -{ -public: - virtual ~IDynamicProfileGenerator() = 0; - virtual std::wstring_view GetNamespace() = 0; - virtual std::vector GenerateProfiles() = 0; -}; -inline Microsoft::Terminal::Settings::Model::IDynamicProfileGenerator::~IDynamicProfileGenerator() {} diff --git a/src/cascadia/TerminalSettingsModel/IInheritable.h b/src/cascadia/TerminalSettingsModel/IInheritable.h index a39ea9dd9..c6281e5e3 100644 --- a/src/cascadia/TerminalSettingsModel/IInheritable.h +++ b/src/cascadia/TerminalSettingsModel/IInheritable.h @@ -48,13 +48,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void InsertParent(com_ptr parent) { - _parents.push_back(parent); + _parents.emplace_back(std::move(parent)); } void InsertParent(size_t index, com_ptr parent) { auto pos{ _parents.begin() + index }; - _parents.insert(pos, parent); + _parents.emplace(pos, std::move(parent)); } const std::vector>& Parents() diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index 58124d84b..cc72ce143 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -678,7 +678,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { GUID FromJson(const Json::Value& json) { - return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json))); + return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)).c_str()); } bool CanConvert(const Json::Value& json) diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 3c56c6620..8f5ab68c7 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -44,7 +44,7 @@ Command.idl - + GlobalAppSettings.idl @@ -123,7 +123,7 @@ Command.idl - + GlobalAppSettings.idl @@ -254,15 +254,15 @@ we can include in the code directly. This way, we don't need to worry about failing to load the default settings at runtime. --> - + - + - + diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index b3efcaa70..149d48af1 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -24,7 +24,7 @@ - + profileGeneration @@ -69,7 +69,7 @@ - + profileGeneration @@ -123,4 +123,4 @@ {81a6314f-aa5b-4533-a499-13bc3a5c4af0} - \ No newline at end of file + diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp index 7baae6b0a..15048022b 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp @@ -8,7 +8,7 @@ #include "../../types/inc/utils.hpp" #include "../../inc/DefaultSettings.h" #include "Utils.h" -#include "DefaultProfileUtils.h" +#include "DynamicProfileUtils.h" // These four are headers we do not want proliferating, so they're not in the PCH. #include @@ -289,7 +289,7 @@ static std::vector _collectPowerShellInstances() // - PowerShell Core 574e775e-4f2a-5b96-ac1e-a2962a402336 static constexpr winrt::guid PowershellCoreGuid{ 0x574e775e, 0x4f2a, 0x5b96, { 0xac, 0x1e, 0xa2, 0x96, 0x2a, 0x40, 0x23, 0x36 } }; -std::wstring_view PowershellCoreProfileGenerator::GetNamespace() +std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept { return PowershellCoreGeneratorNamespace; } @@ -300,34 +300,33 @@ std::wstring_view PowershellCoreProfileGenerator::GetNamespace() // - // Return Value: // - a vector with the PowerShell Core profile, if available. -std::vector PowershellCoreProfileGenerator::GenerateProfiles() +void PowershellCoreProfileGenerator::GenerateProfiles(std::vector>& profiles) const { - std::vector profiles; + const auto psInstances = _collectPowerShellInstances(); + bool first = true; - auto psInstances = _collectPowerShellInstances(); for (const auto& psI : psInstances) { const auto name = psI.Name(); - auto profile{ CreateDefaultProfile(name) }; - profile.Commandline(psI.executablePath.wstring()); - profile.StartingDirectory(DEFAULT_STARTING_DIRECTORY); - profile.DefaultAppearance().ColorSchemeName(L"Campbell"); + auto profile{ CreateDynamicProfile(name) }; + profile->Commandline(winrt::hstring{ psI.executablePath.native() }); + profile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY }); + profile->DefaultAppearance().ColorSchemeName(L"Campbell"); + profile->Icon(winrt::hstring{ WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON }); + + if (first) + { + // Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID. + // This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store" + // (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version. + profile->Guid(PowershellCoreGuid); + profile->Name(winrt::hstring{ POWERSHELL_PREFERRED_PROFILE_NAME }); + + first = false; + } - profile.Icon(WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON); profiles.emplace_back(std::move(profile)); } - - if (profiles.size() > 0) - { - // Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID. - // This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store" - // (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version. - auto firstProfile = profiles.begin(); - firstProfile->Guid(PowershellCoreGuid); - firstProfile->Name(POWERSHELL_PREFERRED_PROFILE_NAME); - } - - return profiles; } // Function Description: diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h index d6bb84635..eaec9dace 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h @@ -18,17 +18,14 @@ Author(s): #include "IDynamicProfileGenerator.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { - class PowershellCoreProfileGenerator : public Microsoft::Terminal::Settings::Model::IDynamicProfileGenerator + class PowershellCoreProfileGenerator final : public IDynamicProfileGenerator { public: static const std::wstring_view GetPreferredPowershellProfileName(); - PowershellCoreProfileGenerator() = default; - ~PowershellCoreProfileGenerator() = default; - std::wstring_view GetNamespace() override; - - std::vector GenerateProfiles() override; + std::wstring_view GetNamespace() const noexcept override; + void GenerateProfiles(std::vector>& profiles) const override; }; }; diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index c76247a9f..287ce8970 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -5,9 +5,7 @@ #include "Profile.h" #include "JsonUtils.h" #include "../../types/inc/Utils.hpp" -#include -#include "LegacyProfileGeneratorNamespaces.h" #include "TerminalSettingsSerializationHelpers.h" #include "AppearanceConfig.h" #include "FontConfig.h" @@ -22,6 +20,7 @@ using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::Foundation; using namespace ::Microsoft::Console; +static constexpr std::string_view UpdatesKey{ "updates" }; static constexpr std::string_view NameKey{ "name" }; static constexpr std::string_view GuidKey{ "guid" }; static constexpr std::string_view SourceKey{ "source" }; @@ -47,13 +46,7 @@ static constexpr std::string_view TabColorKey{ "tabColor" }; static constexpr std::string_view BellStyleKey{ "bellStyle" }; static constexpr std::string_view UnfocusedAppearanceKey{ "unfocusedAppearance" }; -static constexpr std::wstring_view DesktopWallpaperEnum{ L"desktopWallpaper" }; - -Profile::Profile() -{ -} - -Profile::Profile(guid guid) : +Profile::Profile(guid guid) noexcept : _Guid(guid) { } @@ -79,61 +72,81 @@ void Profile::DeleteUnfocusedAppearance() _UnfocusedAppearance = std::nullopt; } -winrt::com_ptr Profile::CopySettings(winrt::com_ptr source) +// See CopyInheritanceGraph (singular) for more information. +// This does the same, but runs it on a list of graph nodes and clones each sub-graph. +void Profile::CopyInheritanceGraphs(std::unordered_map>& visited, const std::vector>& source, std::vector>& target) { - auto profile{ winrt::make_self() }; + for (const auto& sourceProfile : source) + { + target.emplace_back(sourceProfile->CopyInheritanceGraph(visited)); + } +} - profile->_Deleted = source->_Deleted; - profile->_Guid = source->_Guid; - profile->_Name = source->_Name; - profile->_Source = source->_Source; - profile->_Hidden = source->_Hidden; - profile->_Icon = source->_Icon; - profile->_CloseOnExit = source->_CloseOnExit; - profile->_TabTitle = source->_TabTitle; - profile->_TabColor = source->_TabColor; - profile->_SuppressApplicationTitle = source->_SuppressApplicationTitle; - profile->_UseAcrylic = source->_UseAcrylic; - profile->_ScrollState = source->_ScrollState; - profile->_Padding = source->_Padding; - profile->_Commandline = source->_Commandline; - profile->_StartingDirectory = source->_StartingDirectory; - profile->_AntialiasingMode = source->_AntialiasingMode; - profile->_ForceFullRepaintRendering = source->_ForceFullRepaintRendering; - profile->_SoftwareRendering = source->_SoftwareRendering; - profile->_HistorySize = source->_HistorySize; - profile->_SnapOnInput = source->_SnapOnInput; - profile->_AltGrAliasing = source->_AltGrAliasing; - profile->_BellStyle = source->_BellStyle; - profile->_ConnectionType = source->_ConnectionType; - profile->_Origin = source->_Origin; +// A profile and its IInheritable parents basically behave like a directed acyclic graph (DAG). +// Cloning a DAG requires us to prevent the duplication of already cloned nodes (or profiles). +// This is where "visited" comes into play: It contains previously cloned sub-graphs of profiles and "interns" them. +winrt::com_ptr& Profile::CopyInheritanceGraph(std::unordered_map>& visited) const +{ + // The operator[] is usually considered to suck, because it implicitly creates entries + // in maps/sets if the entry doesn't exist yet, which is often an unwanted behavior. + // But in this case it's just perfect. We want to return a reference to the profile if it's + // been created before and create a cloned profile if it doesn't. With the operator[] + // we can just assign the returned reference allowing us to write some lean code. + auto& clone = visited[this]; - // Copy over the font info - const auto weakRefToProfile = weak_ref(*profile); - winrt::com_ptr sourceFontInfoImpl; - sourceFontInfoImpl.copy_from(winrt::get_self(source->_FontInfo)); - auto copiedFontInfo = FontConfig::CopyFontInfo(sourceFontInfoImpl, weakRefToProfile); - profile->_FontInfo = *copiedFontInfo; + if (!clone) + { + clone = CopySettings(); + CopyInheritanceGraphs(visited, _parents, clone->_parents); + clone->_FinalizeInheritance(); + } - // Copy over the appearance - winrt::com_ptr sourceDefaultAppearanceImpl; - sourceDefaultAppearanceImpl.copy_from(winrt::get_self(source->_DefaultAppearance)); - auto copiedDefaultAppearance = AppearanceConfig::CopyAppearance(sourceDefaultAppearanceImpl, weakRefToProfile); - profile->_DefaultAppearance = *copiedDefaultAppearance; + return clone; +} - if (source->_UnfocusedAppearance.has_value()) +winrt::com_ptr Profile::CopySettings() const +{ + const auto profile = winrt::make_self(); + const auto weakProfile = winrt::make_weak(*profile); + const auto fontInfo = FontConfig::CopyFontInfo(winrt::get_self(_FontInfo), weakProfile); + const auto defaultAppearance = AppearanceConfig::CopyAppearance(winrt::get_self(_DefaultAppearance), weakProfile); + + profile->_Deleted = _Deleted; + profile->_Updates = _Updates; + profile->_Guid = _Guid; + profile->_Name = _Name; + profile->_Source = _Source; + profile->_Hidden = _Hidden; + profile->_Icon = _Icon; + profile->_CloseOnExit = _CloseOnExit; + profile->_TabTitle = _TabTitle; + profile->_TabColor = _TabColor; + profile->_SuppressApplicationTitle = _SuppressApplicationTitle; + profile->_UseAcrylic = _UseAcrylic; + profile->_ScrollState = _ScrollState; + profile->_Padding = _Padding; + profile->_Commandline = _Commandline; + profile->_StartingDirectory = _StartingDirectory; + profile->_AntialiasingMode = _AntialiasingMode; + profile->_ForceFullRepaintRendering = _ForceFullRepaintRendering; + profile->_SoftwareRendering = _SoftwareRendering; + profile->_HistorySize = _HistorySize; + profile->_SnapOnInput = _SnapOnInput; + profile->_AltGrAliasing = _AltGrAliasing; + profile->_BellStyle = _BellStyle; + profile->_ConnectionType = _ConnectionType; + profile->_Origin = _Origin; + profile->_FontInfo = *fontInfo; + profile->_DefaultAppearance = *defaultAppearance; + + if (_UnfocusedAppearance) { Model::AppearanceConfig unfocused{ nullptr }; - if (source->_UnfocusedAppearance.value() != nullptr) + if (*_UnfocusedAppearance) { - // Copy over the unfocused appearance - winrt::com_ptr sourceUnfocusedAppearanceImpl; - sourceUnfocusedAppearanceImpl.copy_from(winrt::get_self(source->_UnfocusedAppearance.value())); - auto copiedUnfocusedAppearance = AppearanceConfig::CopyAppearance(sourceUnfocusedAppearanceImpl, weakRefToProfile); - - // Make sure to add the default appearance as a parent - copiedUnfocusedAppearance->InsertParent(copiedDefaultAppearance); - unfocused = *copiedUnfocusedAppearance; + const auto appearance = AppearanceConfig::CopyAppearance(winrt::get_self(*_UnfocusedAppearance), weakProfile); + appearance->InsertParent(defaultAppearance); + unfocused = *appearance; } profile->_UnfocusedAppearance = unfocused; } @@ -141,104 +154,6 @@ winrt::com_ptr Profile::CopySettings(winrt::com_ptr source) return profile; } -// Method Description: -// - Creates a copy of the inheritance graph by performing a depth-first traversal recursively. -// Profiles are recorded as visited via the "visited" parameter. -// Unvisited Profiles are copied into the "cloneGraph" parameter, then marked as visited. -// Arguments: -// - sourceGraph - the graph of Profile's we're cloning -// - cloneGraph - the clone of sourceGraph that is being constructed -// - visited - a map of which Profiles have been visited, and, if so, a reference to the Profile's clone -// Return Value: -// - a clone in both inheritance structure and Profile values of sourceGraph -winrt::com_ptr Profile::CloneInheritanceGraph(winrt::com_ptr sourceGraph, winrt::com_ptr cloneGraph, std::unordered_map>& visited) -{ - // If this is an unexplored Profile - // and we have parents... - if (visited.find(sourceGraph.get()) == visited.end() && !sourceGraph->_parents.empty()) - { - // iterate through all of our parents to copy them - for (const auto& sourceParent : sourceGraph->_parents) - { - // If we visited this Profile already... - auto kv{ visited.find(sourceParent.get()) }; - if (kv != visited.end()) - { - // add this Profile's clone as a parent - InsertParentHelper(cloneGraph, kv->second); - } - else - { - // We have not visited this Profile yet, - // copy contents of sourceParent to clone - winrt::com_ptr clone{ CopySettings(sourceParent) }; - - // add the new copy to the cloneGraph - InsertParentHelper(cloneGraph, clone); - - // copy the sub-graph at "clone" - CloneInheritanceGraph(sourceParent, clone, visited); - - // mark clone as "visited" - // save it to the map in case somebody else references it - visited[sourceParent.get()] = clone; - } - } - } - - // we have no more to explore down this path. - return cloneGraph; -} - -// Method Description: -// - Inserts a parent profile into a child profile, at the specified index if one was provided -// - Makes sure to call _FinalizeInheritance after inserting the parent -// Arguments: -// - child: the child profile to insert the parent into -// - parent: the parent profile to insert into the child -// - index: an optional index value to insert the parent into -void Profile::InsertParentHelper(winrt::com_ptr child, winrt::com_ptr parent, std::optional index) -{ - if (index) - { - child->InsertParent(index.value(), parent); - } - else - { - child->InsertParent(parent); - } - child->_FinalizeInheritance(); -} - -// Method Description: -// - Generates a Json::Value which is a "stub" of this profile. This stub will -// have enough information that it could be layered with this profile. -// - This method is used during dynamic profile generation - if a profile is -// ever generated that didn't already exist in the user's settings, we'll add -// this stub to the user's settings file, so the user has an easy point to -// modify the generated profile. -// Arguments: -// - -// Return Value: -// - A json::Value with a guid, name and source (if applicable). -Json::Value Profile::GenerateStub() const -{ - Json::Value stub; - - ///// Profile-specific settings ///// - stub[JsonKey(GuidKey)] = winrt::to_string(Utils::GuidToString(Guid())); - - stub[JsonKey(NameKey)] = winrt::to_string(Name()); - - const auto source{ Source() }; - if (!source.empty()) - { - stub[JsonKey(SourceKey)] = winrt::to_string(source); - } - - return stub; -} - // Method Description: // - Create a new instance of this class from a serialized JsonObject. // Arguments: @@ -252,71 +167,6 @@ winrt::com_ptr>(json, GuidKey) }; - const auto otherSource{ JsonUtils::GetValueForKey>(json, SourceKey) }; - if (otherGuid) - { - if (otherGuid.value() != Guid()) - { - return false; - } - } - else - { - // If the other json object didn't have a GUID, - // check if we auto-generate the same guid using the name and source. - const auto otherName{ JsonUtils::GetValueForKey>(json, NameKey) }; - if (Guid() != _GenerateGuidForProfile(otherName ? *otherName : L"Default", otherSource ? *otherSource : L"")) - { - return false; - } - } - - // For profiles with a `source`, also check the `source` property. - bool sourceMatches = false; - const auto mySource{ Source() }; - if (!mySource.empty()) - { - if (otherSource.has_value()) - { - // If we have a source and the other has a source, compare them! - sourceMatches = *otherSource == mySource; - } - else - { - // Special case the legacy dynamic profiles here. In this case, - // `this` is a dynamic profile with a source, and our _source is one - // of the legacy DPG namespaces. We're looking to see if the other - // json object has the same guid, but _no_ "source" - if (mySource == WslGeneratorNamespace || - mySource == AzureGeneratorNamespace || - mySource == PowershellCoreGeneratorNamespace) - { - sourceMatches = true; - } - } - } - else - { - // We do not have a source. The only way we match is if source is unset or set to "". - sourceMatches = (!otherSource.has_value() || otherSource.value() == L""); - } - - return sourceMatches; -} - // Method Description: // - Layer values from the given json object on top of the existing properties // of this object. For any keys we're expecting to be able to parse in the @@ -341,6 +191,7 @@ void Profile::LayerJson(const Json::Value& json) // Profile-specific Settings JsonUtils::GetValueForKey(json, NameKey, _Name); + JsonUtils::GetValueForKey(json, UpdatesKey, _Updates); JsonUtils::GetValueForKey(json, GuidKey, _Guid); JsonUtils::GetValueForKey(json, HiddenKey, _Hidden); JsonUtils::GetValueForKey(json, SourceKey, _Source); @@ -459,27 +310,13 @@ std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory) return wil::ExpandEnvironmentStringsW(directory.c_str()); } -// Function Description: -// - Returns true if the given JSON object represents a dynamic profile object. -// If it is a dynamic profile object, we should make sure to only layer the -// object on a matching profile from a dynamic source. -// Arguments: -// - json: the partial serialization of a profile object to check -// Return Value: -// - true iff the object has a non-null `source` property -bool Profile::IsDynamicProfileObject(const Json::Value& json) -{ - const auto& source = json.isMember(JsonKey(SourceKey)) ? json[JsonKey(SourceKey)] : Json::Value::null; - return !source.isNull(); -} - // Function Description: // - Generates a unique guid for a profile, given the name. For an given name, will always return the same GUID. // Arguments: // - name: The name to generate a unique GUID from // Return Value: // - a uuidv5 GUID generated from the given name. -winrt::guid Profile::_GenerateGuidForProfile(const hstring& name, const hstring& source) noexcept +winrt::guid Profile::_GenerateGuidForProfile(const std::wstring_view& name, const std::wstring_view& source) noexcept { // If we have a _source, then we can from a dynamic profile generator. Use // our source to build the namespace guid, instead of using the default GUID. @@ -493,27 +330,6 @@ winrt::guid Profile::_GenerateGuidForProfile(const hstring& name, const hstring& return { Utils::CreateV5Uuid(namespaceGuid, gsl::as_bytes(gsl::make_span(name))) }; } -// Function Description: -// - Parses the given JSON object to get its GUID. If the json object does not -// have a `guid` set, we'll generate one, using the `name` field. -// Arguments: -// - json: the JSON object to get a GUID from, or generate a unique GUID for -// (given the `name`) -// Return Value: -// - The json's `guid`, or a guid synthesized for it. -winrt::guid Profile::GetGuidOrGenerateForJson(const Json::Value& json) noexcept -{ - if (const auto guid{ JsonUtils::GetValueForKey>(json, GuidKey) }) - { - return { guid.value() }; - } - - const auto name{ JsonUtils::GetValueForKey(json, NameKey) }; - const auto source{ JsonUtils::GetValueForKey(json, SourceKey) }; - - return Profile::_GenerateGuidForProfile(name, source); -} - // Method Description: // - Create a new serialized JsonObject from an instance of this class // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/Profile.h b/src/cascadia/TerminalSettingsModel/Profile.h index c84dadefc..9e54b0e7e 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.h +++ b/src/cascadia/TerminalSettingsModel/Profile.h @@ -76,8 +76,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct Profile : ProfileT, IInheritable { public: - Profile(); - Profile(guid guid); + Profile() noexcept = default; + Profile(guid guid) noexcept; void CreateUnfocusedAppearance(); void DeleteUnfocusedAppearance(); @@ -87,19 +87,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return Name(); } - static com_ptr CloneInheritanceGraph(com_ptr oldProfile, com_ptr newProfile, std::unordered_map>& visited); - static com_ptr CopySettings(com_ptr source); - static void InsertParentHelper(com_ptr child, com_ptr parent, std::optional index = std::nullopt); + static void CopyInheritanceGraphs(std::unordered_map>& visited, const std::vector>& source, std::vector>& target); + winrt::com_ptr& CopyInheritanceGraph(std::unordered_map>& visited) const; + winrt::com_ptr CopySettings() const; - Json::Value GenerateStub() const; static com_ptr FromJson(const Json::Value& json); - bool ShouldBeLayered(const Json::Value& json) const; void LayerJson(const Json::Value& json); - static bool IsDynamicProfileObject(const Json::Value& json); Json::Value ToJson() const; hstring EvaluatedStartingDirectory() const; - static guid GetGuidOrGenerateForJson(const Json::Value& json) noexcept; Model::IAppearanceConfig DefaultAppearance(); Model::FontConfig FontInfo(); @@ -109,6 +105,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation WINRT_PROPERTY(bool, Deleted, false); WINRT_PROPERTY(OriginTag, Origin, OriginTag::None); + WINRT_PROPERTY(guid, Updates); INHERITABLE_SETTING(Model::Profile, guid, Guid, _GenerateGuidForProfile(Name(), Source())); INHERITABLE_SETTING(Model::Profile, hstring, Name, L"Default"); INHERITABLE_SETTING(Model::Profile, hstring, Source); @@ -124,7 +121,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::Profile, bool, SuppressApplicationTitle, false); INHERITABLE_SETTING(Model::Profile, bool, UseAcrylic, false); - INHERITABLE_SETTING(Model::Profile, double, AcrylicOpacity, 0.5); INHERITABLE_SETTING(Model::Profile, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible); INHERITABLE_SETTING(Model::Profile, hstring, Padding, DEFAULT_PADDING); @@ -149,7 +145,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::FontConfig _FontInfo{ winrt::make(weak_ref(*this)) }; static std::wstring EvaluateStartingDirectory(const std::wstring& directory); - static guid _GenerateGuidForProfile(const hstring& name, const hstring& source) noexcept; + static guid _GenerateGuidForProfile(const std::wstring_view& name, const std::wstring_view& source) noexcept; friend class SettingsModelLocalTests::DeserializationTests; friend class SettingsModelLocalTests::ProfileTests; diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index c69d57280..670303ff1 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -50,17 +50,11 @@ namespace Microsoft.Terminal.Settings.Model Boolean Deleted { get; }; OriginTag Origin { get; }; + INHERITABLE_PROFILE_SETTING(Guid, Guid); INHERITABLE_PROFILE_SETTING(String, Name); - - Boolean HasGuid(); - Guid Guid; - INHERITABLE_PROFILE_SETTING(String, Source); - - Boolean HasConnectionType(); - Guid ConnectionType; - INHERITABLE_PROFILE_SETTING(Boolean, Hidden); + INHERITABLE_PROFILE_SETTING(Guid, ConnectionType); INHERITABLE_PROFILE_SETTING(String, Icon); INHERITABLE_PROFILE_SETTING(CloseOnExitMode, CloseOnExit); INHERITABLE_PROFILE_SETTING(String, TabTitle); diff --git a/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl b/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl index 6f55c0bf7..c8aea6b95 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl +++ b/src/cascadia/TerminalSettingsModel/TerminalWarnings.idl @@ -8,20 +8,19 @@ namespace Microsoft.Terminal.Settings.Model enum SettingsLoadWarnings { MissingDefaultProfile = 0, - DuplicateProfile = 1, - UnknownColorScheme = 2, - InvalidBackgroundImage = 3, - InvalidIcon = 4, - AtLeastOneKeybindingWarning = 5, - TooManyKeysForChord = 6, - MissingRequiredParameter = 7, - LegacyGlobalsProperty = 8, - FailedToParseCommandJson = 9, - FailedToWriteToSettings = 10, - InvalidColorSchemeInCmd = 11, - InvalidSplitSize = 12, - FailedToParseStartupActions = 13, - FailedToParseSubCommands = 14, + DuplicateProfile, + UnknownColorScheme, + InvalidBackgroundImage, + InvalidIcon, + AtLeastOneKeybindingWarning, + TooManyKeysForChord, + MissingRequiredParameter, + FailedToParseCommandJson, + FailedToWriteToSettings, + InvalidColorSchemeInCmd, + InvalidSplitSize, + FailedToParseStartupActions, + FailedToParseSubCommands, WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder. }; diff --git a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp index 182c71968..d21ae8ff2 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp @@ -4,7 +4,7 @@ #include "pch.h" #include "VsDevCmdGenerator.h" -using namespace Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Settings::Model; std::wstring VsDevCmdGenerator::GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const { diff --git a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.h b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.h index 2f74191bc..7545bf079 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.h +++ b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.h @@ -16,12 +16,12 @@ Author(s): #pragma once #include "BaseVisualStudioGenerator.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { - class VsDevCmdGenerator : public BaseVisualStudioGenerator + class VsDevCmdGenerator final : public BaseVisualStudioGenerator { public: - std::wstring_view GetNamespace() override + std::wstring_view GetNamespace() const noexcept override { return std::wstring_view{ L"Windows.Terminal.VisualStudio.CommandPrompt" }; } diff --git a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp index ef78fb79d..5849e7dfd 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp @@ -5,7 +5,7 @@ #include "VsDevShellGenerator.h" #include "VsSetupConfiguration.h" -using namespace Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Settings::Model; std::wstring VsDevShellGenerator::GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const { diff --git a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.h b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.h index c30dda66b..d8c027dd4 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.h +++ b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.h @@ -16,12 +16,12 @@ Author(s): #pragma once #include "BaseVisualStudioGenerator.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { - class VsDevShellGenerator : public BaseVisualStudioGenerator + class VsDevShellGenerator final : public BaseVisualStudioGenerator { public: - std::wstring_view GetNamespace() override + std::wstring_view GetNamespace() const noexcept override { return std::wstring_view{ L"Windows.Terminal.VisualStudio.Powershell" }; } diff --git a/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.cpp b/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.cpp index 1683f5cfd..47e965757 100644 --- a/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.cpp +++ b/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.cpp @@ -4,7 +4,7 @@ #include "pch.h" #include "VsSetupConfiguration.h" -using namespace Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Settings::Model; std::vector VsSetupConfiguration::QueryInstances() { diff --git a/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.h b/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.h index 711ecf632..849b91b10 100644 --- a/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.h +++ b/src/cascadia/TerminalSettingsModel/VsSetupConfiguration.h @@ -17,7 +17,7 @@ Author(s): #include "Setup.Configuration.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { /// /// See https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration?view=visualstudiosdk-2019 diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp index 799602941..b0cfadfbd 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp @@ -5,13 +5,11 @@ #include "WslDistroGenerator.h" #include "LegacyProfileGeneratorNamespaces.h" - -#include "../../types/inc/utils.hpp" #include "../../inc/DefaultSettings.h" -#include "Utils.h" + #include #include -#include "DefaultProfileUtils.h" +#include "DynamicProfileUtils.h" static constexpr std::wstring_view DockerDistributionPrefix{ L"docker-desktop" }; @@ -31,21 +29,29 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; // - Alpine 1777cdf0-b2c4-5a63-a204-eb60f349ea7c // - Ubuntu-18.04 c6eaf9f4-32a7-5fdc-b5cf-066e8a4b1e40 -std::wstring_view WslDistroGenerator::GetNamespace() +std::wstring_view WslDistroGenerator::GetNamespace() const noexcept { return WslGeneratorNamespace; } +static winrt::com_ptr makeProfile(const std::wstring& distName) +{ + const auto WSLDistro{ CreateDynamicProfile(distName) }; + WSLDistro->Commandline(winrt::hstring{ L"wsl.exe -d " + distName }); + WSLDistro->DefaultAppearance().ColorSchemeName(L"Campbell"); + WSLDistro->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY }); + WSLDistro->Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png"); + return WSLDistro; +} + // Method Description: // - Enumerates all the installed WSL distros to create profiles for them. // Arguments: // - // Return Value: // - a vector with all distros for all the installed WSL distros -static std::vector legacyGenerate() +static void legacyGenerate(std::vector>& profiles) { - std::vector profiles; - wil::unique_handle readPipe; wil::unique_handle writePipe; SECURITY_ATTRIBUTES sa{ sizeof(sa), nullptr, true }; @@ -77,7 +83,7 @@ static std::vector legacyGenerate() break; case WAIT_ABANDONED: case WAIT_TIMEOUT: - return profiles; + return; case WAIT_FAILED: THROW_LAST_ERROR(); default: @@ -90,7 +96,7 @@ static std::vector legacyGenerate() } else if (exitCode != 0) { - return profiles; + return; } DWORD bytesAvailable; THROW_IF_WIN32_BOOL_FALSE(PeekNamedPipe(readPipe.get(), nullptr, NULL, nullptr, &bytesAvailable, nullptr)); @@ -117,7 +123,7 @@ static std::vector legacyGenerate() std::wstring distName; std::getline(wlinestream, distName, L'\r'); - if (distName.substr(0, std::min(distName.size(), DockerDistributionPrefix.size())) == DockerDistributionPrefix) + if (til::starts_with(distName, DockerDistributionPrefix)) { // Docker for Windows creates some utility distributions to handle Docker commands. // Pursuant to GH#3556, because they are _not_ user-facing we want to hide them. @@ -131,17 +137,10 @@ static std::vector legacyGenerate() { distName.resize(firstChar); } - auto WSLDistro{ CreateDefaultProfile(distName) }; - WSLDistro.Commandline(L"wsl.exe -d " + distName); - WSLDistro.DefaultAppearance().ColorSchemeName(L"Campbell"); - WSLDistro.StartingDirectory(DEFAULT_STARTING_DIRECTORY); - WSLDistro.Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png"); - profiles.emplace_back(WSLDistro); + profiles.emplace_back(makeProfile(distName)); } } - - return profiles; } // Function Description: @@ -151,9 +150,8 @@ static std::vector legacyGenerate() // - names: a list of distro names to turn into profiles // Return Value: // - the list of profiles we've generated. -static std::vector namesToProfiles(const std::vector& names) +static void namesToProfiles(const std::vector& names, std::vector>& profiles) { - std::vector profiles; for (const auto& distName : names) { if (til::starts_with(distName, DockerDistributionPrefix)) @@ -163,15 +161,8 @@ static std::vector namesToProfiles(const std::vector& nam continue; } - auto WSLDistro{ CreateDefaultProfile(distName) }; - - WSLDistro.Commandline(L"wsl.exe -d " + distName); - WSLDistro.DefaultAppearance().ColorSchemeName(L"Campbell"); - WSLDistro.StartingDirectory(DEFAULT_STARTING_DIRECTORY); - WSLDistro.Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png"); - profiles.emplace_back(WSLDistro); + profiles.emplace_back(makeProfile(distName)); } - return profiles; } // Function Description: @@ -304,7 +295,7 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey, // - // Return Value: // - A list of WSL profiles. -std::vector WslDistroGenerator::GenerateProfiles() +void WslDistroGenerator::GenerateProfiles(std::vector>& profiles) const { wil::unique_hkey wslRootKey{ openWslRegKey() }; if (wslRootKey) @@ -316,10 +307,10 @@ std::vector WslDistroGenerator::GenerateProfiles() names.reserve(guidStrings.size()); if (getWslNames(wslRootKey, guidStrings, names)) { - return namesToProfiles(names); + return namesToProfiles(names, profiles); } } } - return legacyGenerate(); + legacyGenerate(profiles); } diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h index 4d85debb4..b46aac820 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h @@ -15,16 +15,15 @@ Author(s): --*/ #pragma once + #include "IDynamicProfileGenerator.h" -namespace Microsoft::Terminal::Settings::Model +namespace winrt::Microsoft::Terminal::Settings::Model { - class WslDistroGenerator : public Microsoft::Terminal::Settings::Model::IDynamicProfileGenerator + class WslDistroGenerator final : public IDynamicProfileGenerator { public: - WslDistroGenerator() = default; - ~WslDistroGenerator() = default; - std::wstring_view GetNamespace() override; - std::vector GenerateProfiles() override; + std::wstring_view GetNamespace() const noexcept override; + void GenerateProfiles(std::vector>& profiles) const override; }; }; diff --git a/src/cascadia/TerminalSettingsModel/userDefaults.json b/src/cascadia/TerminalSettingsModel/userDefaults.json index 13cda81aa..28ed3beef 100644 --- a/src/cascadia/TerminalSettingsModel/userDefaults.json +++ b/src/cascadia/TerminalSettingsModel/userDefaults.json @@ -1,75 +1,31 @@ -// This file was initially generated by %PRODUCT% %VERSION% -// It should still be usable in newer versions, but newer versions might have additional -// settings, help text, or changes that you will not see unless you clear this file -// and let us generate a new one for you. - -// To view the default settings, hold "alt" while clicking on the "Settings" button. -// For documentation on these settings, see: https://aka.ms/terminal-documentation { - "$schema": "https://aka.ms/terminal-profiles-schema", - - "defaultProfile": "%DEFAULT_PROFILE%", - - // You can add more global application settings here. - // To learn more about global settings, visit https://aka.ms/terminal-global-settings - - // If enabled, selections are automatically copied to your clipboard. + // "defaultProfile" is filled in by CascadiaSettings, depending on + // what dynamic profiles are present during the first launch. "copyOnSelect": false, - - // If enabled, formatted data is also copied to your clipboard "copyFormatting": false, - - // A profile specifies a command to execute paired with information about how it should look and feel. - // Each one of them will appear in the 'New Tab' dropdown, - // and can be invoked from the commandline with `wt.exe -p xxx` - // To learn more about profiles, visit https://aka.ms/terminal-profile-settings "profiles": { - "defaults": - { - // Put settings here that you want to apply to all profiles. - }, "list": [ { - // Make changes here to the powershell.exe profile. "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", "name": "Windows PowerShell", "commandline": "powershell.exe", "hidden": false }, { - // Make changes here to the cmd.exe profile. + // "name" is filled in by CascadiaSettings as a localized string. "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", - "name": "%COMMAND_PROMPT_LOCALIZED_NAME%", "commandline": "cmd.exe", "hidden": false } ] }, - - // Add custom color schemes to this array. - // To learn more about color schemes, visit https://aka.ms/terminal-color-schemes - "schemes": [], - - // Add custom actions and keybindings to this array. - // To unbind a key combination from your defaults.json, set the command to "unbound". - // To learn more about actions and keybindings, visit https://aka.ms/terminal-keybindings "actions": [ - // Copy and paste are bound to Ctrl+Shift+C and Ctrl+Shift+V in your defaults.json. - // These two lines additionally bind them to Ctrl+C and Ctrl+V. - // To learn more about selection, visit https://aka.ms/terminal-selection { "command": {"action": "copy", "singleLine": false }, "keys": "ctrl+c" }, { "command": "paste", "keys": "ctrl+v" }, - - // Press Ctrl+Shift+F to open the search box { "command": "find", "keys": "ctrl+shift+f" }, - - // Press Alt+Shift+D to open a new pane. - // - "split": "auto" makes this pane open in the direction that provides the most surface area. - // - "splitMode": "duplicate" makes the new pane use the focused pane's profile. - // To learn more about panes, visit https://aka.ms/terminal-panes { "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" } ] } diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp index 3cffb107c..db1e07435 100644 --- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp @@ -379,7 +379,6 @@ namespace ControlUnitTests // For this test, don't use any modifiers const auto modifiers = ControlKeyStates(); const Control::MouseButtonState leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown }; - const Control::MouseButtonState noMouseDown{}; const til::size fontSize{ 9, 21 }; @@ -530,7 +529,6 @@ namespace ControlUnitTests // For this test, don't use any modifiers const auto modifiers = ControlKeyStates(); const Control::MouseButtonState leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown }; - const Control::MouseButtonState noMouseDown{}; const til::size fontSize{ 9, 21 }; @@ -742,7 +740,6 @@ namespace ControlUnitTests // For this test, don't use any modifiers const auto modifiers = ControlKeyStates(); const Control::MouseButtonState leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown }; - const Control::MouseButtonState noMouseDown{}; const til::size fontSize{ 9, 21 }; diff --git a/src/cascadia/ut_app/DynamicProfileTests.cpp b/src/cascadia/ut_app/DynamicProfileTests.cpp deleted file mode 100644 index c7d6daf6c..000000000 --- a/src/cascadia/ut_app/DynamicProfileTests.cpp +++ /dev/null @@ -1,673 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "../TerminalSettingsModel/ColorScheme.h" -#include "../TerminalSettingsModel/Profile.h" -#include "../TerminalSettingsModel/CascadiaSettings.h" -#include "../TerminalSettingsModel/LegacyProfileGeneratorNamespaces.h" - -#include "../LocalTests_SettingsModel/JsonTestClass.h" - -#include "TestDynamicProfileGenerator.h" - -using namespace Microsoft::Console; -using namespace winrt::Microsoft::Terminal::Settings::Model; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using namespace WEX::Common; - -namespace TerminalAppUnitTests -{ - class DynamicProfileTests : public JsonTestClass - { - BEGIN_TEST_CLASS(DynamicProfileTests) - TEST_CLASS_PROPERTY(L"ActivationContext", L"TerminalApp.Unit.Tests.manifest") - END_TEST_CLASS() - - TEST_CLASS_SETUP(ClassSetup) - { - InitializeJsonReader(); - return true; - } - - TEST_METHOD(TestSimpleGenerate); - - // Simple test of CascadiaSettings generating profiles with _LoadDynamicProfiles - TEST_METHOD(TestSimpleGenerateMultipleGenerators); - - // Make sure we gen GUIDs for profiles without guids - TEST_METHOD(TestGenGuidsForProfiles); - - // Profiles without a source should not be layered on those with one - TEST_METHOD(DontLayerUserProfilesOnDynamicProfiles); - TEST_METHOD(DoLayerUserProfilesOnDynamicsWhenSourceMatches); - - // Make sure profiles that are disabled in _userSettings don't get generated - TEST_METHOD(TestDontRunDisabledGenerators); - - // Make sure profiles that are disabled in _userSettings don't get generated - TEST_METHOD(TestLegacyProfilesMigrate); - - // Both these do similar things: - // This makes sure that a profile with a `source` _only_ layers, it won't create a new profile - TEST_METHOD(UserProfilesWithInvalidSourcesAreIgnored); - // This does the same, but by disabling a profile source - TEST_METHOD(UserProfilesFromDisabledSourcesDontAppear); - }; - - void DynamicProfileTests::TestSimpleGenerate() - { - TestDynamicProfileGenerator gen{ L"Terminal.App.UnitTest" }; - gen.pfnGenerate = []() { - std::vector profiles; - Profile p0; - p0.Name(L"profile0"); - profiles.push_back(p0); - return profiles; - }; - - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest", gen.GetNamespace()); - std::vector profiles = gen.GenerateProfiles(); - VERIFY_ARE_EQUAL(1u, profiles.size()); - VERIFY_ARE_EQUAL(L"profile0", profiles.at(0).Name()); - VERIFY_IS_FALSE(profiles.at(0).HasGuid()); - } - - void DynamicProfileTests::TestSimpleGenerateMultipleGenerators() - { - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - gen0->pfnGenerate = []() { - std::vector profiles; - Profile p0; - p0.Name(L"profile0"); - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - gen1->pfnGenerate = []() { - std::vector profiles; - Profile p0; - p0.Name(L"profile1"); - profiles.push_back(p0); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).HasGuid()); - - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(1).Name()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - } - - void DynamicProfileTests::TestGenGuidsForProfiles() - { - // We'll generate GUIDs in the Profile::Guid getter. We should make sure that - // the GUID generated for a dynamic profile (with a source) is different - // than that of a profile without a source. - - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - gen0->pfnGenerate = []() { - std::vector profiles; - Profile p0; - p0.Name(L"profile0"); // this is _allProfiles.at(2) - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - gen1->pfnGenerate = []() { - std::vector profiles; - Profile p0, p1; - p0.Name(L"profile0"); // this is _allProfiles.at(3) - p1.Name(L"profile1"); // this is _allProfiles.at(4) - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - - Profile p0, p1; - p0.Name(L"profile0"); // this is _allProfiles.GetAt(0) - p1.Name(L"profile1"); // this is _allProfiles.GetAt(1) - settings->_allProfiles.Append(p0); - settings->_allProfiles.Append(p1); - - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(5u, settings->_allProfiles.Size()); - - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).Source().empty()); - - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(1).Name()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).Source().empty()); - - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(2).Name()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).Source().empty()); - - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(3).Name()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).Source().empty()); - - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(4).Name()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(4).HasGuid()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(4).Source().empty()); - - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(), - settings->_allProfiles.GetAt(1).Guid()); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(), - settings->_allProfiles.GetAt(2).Guid()); - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(), - settings->_allProfiles.GetAt(3).Guid()); - - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(1).Guid(), - settings->_allProfiles.GetAt(4).Guid()); - - VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(3).Guid(), - settings->_allProfiles.GetAt(4).Guid()); - } - - void DynamicProfileTests::DontLayerUserProfilesOnDynamicProfiles() - { - winrt::guid guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - winrt::guid guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); - - const std::string userProfiles{ R"( - { - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" - }, - { - "name" : "profile1", - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" - } - ] - })" }; - - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - gen0->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - p0.Name(L"profile0"); // this is _allProfiles.at(0) - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - gen1->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - Profile p1 = winrt::make(guid1); - p0.Name(L"profile0"); // this is _allProfiles.at(1) - p1.Name(L"profile1"); // this is _allProfiles.at(2) - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - - Log::Comment(NoThrowString().Format( - L"All profiles with the same name have the same GUID. However, they" - L" will not be layered, because they have different sources")); - - // parse userProfiles as the user settings - settings->_ParseJsonString(userProfiles, false); - VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size(), L"Just parsing the user settings doesn't actually layer them"); - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(5u, settings->_allProfiles.Size()); - - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).Source().empty()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).Source().empty()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(4).Source().empty()); - - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.0", settings->_allProfiles.GetAt(0).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(1).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(2).Source()); - - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(4).HasGuid()); - - VERIFY_ARE_EQUAL(guid0, settings->_allProfiles.GetAt(0).Guid()); - VERIFY_ARE_EQUAL(guid0, settings->_allProfiles.GetAt(1).Guid()); - VERIFY_ARE_EQUAL(guid1, settings->_allProfiles.GetAt(2).Guid()); - VERIFY_ARE_EQUAL(guid0, settings->_allProfiles.GetAt(3).Guid()); - VERIFY_ARE_EQUAL(guid1, settings->_allProfiles.GetAt(4).Guid()); - } - - void DynamicProfileTests::DoLayerUserProfilesOnDynamicsWhenSourceMatches() - { - winrt::guid guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - winrt::guid guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); - - const std::string userProfiles{ R"( - { - "profiles": [ - { - "name" : "profile0FromUserSettings", // this is _allProfiles.at(0) - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "source": "Terminal.App.UnitTest.0" - }, - { - "name" : "profile1FromUserSettings", // this is _allProfiles.at(2) - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", - "source": "Terminal.App.UnitTest.1" - } - ] - })" }; - - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - gen0->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - p0.Name(L"profile0"); // this is _allProfiles.at(0) - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - gen1->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - Profile p1 = winrt::make(guid1); - p0.Name(L"profile0"); // this is _allProfiles.at(1) - p1.Name(L"profile1"); // this is _allProfiles.at(2) - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - - Log::Comment(NoThrowString().Format( - L"All profiles with the same name have the same GUID. However, they" - L" will not be layered, because they have different source's")); - - // parse userProfiles as the user settings - settings->_ParseJsonString(userProfiles, false); - VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size(), L"Just parsing the user settings doesn't actually layer them"); - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).Source().empty()); - - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.0", settings->_allProfiles.GetAt(0).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(1).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(2).Source()); - - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid()); - - VERIFY_ARE_EQUAL(guid0, settings->_allProfiles.GetAt(0).Guid()); - VERIFY_ARE_EQUAL(guid0, settings->_allProfiles.GetAt(1).Guid()); - VERIFY_ARE_EQUAL(guid1, settings->_allProfiles.GetAt(2).Guid()); - - VERIFY_ARE_EQUAL(L"profile0FromUserSettings", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile1FromUserSettings", settings->_allProfiles.GetAt(2).Name()); - } - - void DynamicProfileTests::TestDontRunDisabledGenerators() - { - const std::string settings0String{ R"( - { - "disabledProfileSources": ["Terminal.App.UnitTest.0"] - })" }; - const std::string settings1String{ R"( - { - "disabledProfileSources": ["Terminal.App.UnitTest.0", "Terminal.App.UnitTest.1"] - })" }; - - const auto settings0Json = VerifyParseSucceeded(settings0String); - - auto gen0GenerateFn = []() { - std::vector profiles; - Profile p0; - p0.Name(L"profile0"); - profiles.push_back(p0); - return profiles; - }; - - auto gen1GenerateFn = []() { - std::vector profiles; - Profile p0, p1; - p0.Name(L"profile1"); - p1.Name(L"profile2"); - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - - auto gen2GenerateFn = []() { - std::vector profiles; - Profile p0, p1; - p0.Name(L"profile3"); - p1.Name(L"profile4"); - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - - { - Log::Comment(NoThrowString().Format( - L"Case 1: Disable a single profile generator")); - auto settings = winrt::make_self(false); - - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - auto gen2 = std::make_unique(L"Terminal.App.UnitTest.2"); - gen0->pfnGenerate = gen0GenerateFn; - gen1->pfnGenerate = gen1GenerateFn; - gen2->pfnGenerate = gen2GenerateFn; - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - settings->_profileGenerators.emplace_back(std::move(gen2)); - - // Parse as the user settings: - settings->_ParseJsonString(settings0String, false); - settings->_LoadDynamicProfiles(); - - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).Source().empty()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(0).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_allProfiles.GetAt(1).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.2", settings->_allProfiles.GetAt(2).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.2", settings->_allProfiles.GetAt(3).Source()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(3).Name()); - } - - { - Log::Comment(NoThrowString().Format( - L"Case 2: Disable multiple profile generators")); - auto settings = winrt::make_self(false); - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - auto gen2 = std::make_unique(L"Terminal.App.UnitTest.2"); - gen0->pfnGenerate = gen0GenerateFn; - gen1->pfnGenerate = gen1GenerateFn; - gen2->pfnGenerate = gen2GenerateFn; - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - settings->_profileGenerators.emplace_back(std::move(gen2)); - - // Parse as the user settings: - settings->_ParseJsonString(settings1String, false); - settings->_LoadDynamicProfiles(); - - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).Source().empty()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.2", settings->_allProfiles.GetAt(0).Source()); - VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.2", settings->_allProfiles.GetAt(1).Source()); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(1).Name()); - } - } - - void DynamicProfileTests::TestLegacyProfilesMigrate() - { - winrt::guid guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}"); - winrt::guid guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - winrt::guid guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); - winrt::guid guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}"); - winrt::guid guid4 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-4444-49a3-80bd-e8fdd045185c}"); - - const std::string settings0String{ R"( - { - "profiles": [ - { - // This pwsh profile does not have a source, but should still be layered - "name" : "profile0FromUserSettings", // this is _allProfiles.at(0) - "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}" - }, - { - // This Azure profile does not have a source, but should still be layered - "name" : "profile3FromUserSettings", // this is _allProfiles.at(3) - "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" - }, - { - // This profile did not come from a dynamic source - "name" : "profile4FromUserSettings", // this is _allProfiles.at(4) - "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" - }, - { - // This WSL profile does not have a source, but should still be layered - "name" : "profile1FromUserSettings", // this is _allProfiles.at(1) - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" - }, - { - // This WSL profile does have a source, and should be layered - "name" : "profile2FromUserSettings", // this is _allProfiles.at(2) - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", - "source": "Windows.Terminal.Wsl" - } - ] - })" }; - - auto gen0 = std::make_unique(L"Windows.Terminal.PowershellCore"); - gen0->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - p0.Name(L"profile0"); - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Windows.Terminal.Wsl"); - gen1->pfnGenerate = [guid2, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid1); - Profile p1 = winrt::make(guid2); - p0.Name(L"profile1"); - p1.Name(L"profile2"); - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - auto gen2 = std::make_unique(L"Windows.Terminal.Azure"); - gen2->pfnGenerate = [guid3]() { - std::vector profiles; - Profile p0 = winrt::make(guid3); - p0.Name(L"profile3"); - profiles.push_back(p0); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - settings->_profileGenerators.emplace_back(std::move(gen2)); - - settings->_ParseJsonString(settings0String, false); - VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size()); - - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).Source().empty()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.PowershellCore", settings->_allProfiles.GetAt(0).Source()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.Wsl", settings->_allProfiles.GetAt(1).Source()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.Wsl", settings->_allProfiles.GetAt(2).Source()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.Azure", settings->_allProfiles.GetAt(3).Source()); - VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile1", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile2", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(3).Name()); - - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(5u, settings->_allProfiles.Size()); - - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(0).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).Source().empty()); - VERIFY_IS_FALSE(settings->_allProfiles.GetAt(3).Source().empty()); - VERIFY_IS_TRUE(settings->_allProfiles.GetAt(4).Source().empty()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.PowershellCore", settings->_allProfiles.GetAt(0).Source()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.Wsl", settings->_allProfiles.GetAt(1).Source()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.Wsl", settings->_allProfiles.GetAt(2).Source()); - VERIFY_ARE_EQUAL(L"Windows.Terminal.Azure", settings->_allProfiles.GetAt(3).Source()); - // settings->_allProfiles.GetAt(4) does not have a source - VERIFY_ARE_EQUAL(L"profile0FromUserSettings", settings->_allProfiles.GetAt(0).Name()); - VERIFY_ARE_EQUAL(L"profile1FromUserSettings", settings->_allProfiles.GetAt(1).Name()); - VERIFY_ARE_EQUAL(L"profile2FromUserSettings", settings->_allProfiles.GetAt(2).Name()); - VERIFY_ARE_EQUAL(L"profile3FromUserSettings", settings->_allProfiles.GetAt(3).Name()); - VERIFY_ARE_EQUAL(L"profile4FromUserSettings", settings->_allProfiles.GetAt(4).Name()); - } - - void DynamicProfileTests::UserProfilesWithInvalidSourcesAreIgnored() - { - winrt::guid guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - winrt::guid guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); - - const std::string settings0String{ R"( - { - "profiles": [ - { - "name" : "profile0FromUserSettings", // this is _allProfiles.at(0) - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "source": "Terminal.App.UnitTest.0" - }, - { - "name" : "profile2", // this shouldn't be in the profiles at all - "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", - "source": "Terminal.App.UnitTest.1" - }, - { - "name" : "profile3", // this is _allProfiles.at(3) - "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" - } - ] - })" }; - - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - gen0->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - p0.Name(L"profile0"); // this is _allProfiles.at(0) - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - gen1->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - Profile p1 = winrt::make(guid1); - p0.Name(L"profile0"); // this is _allProfiles.at(1) - p1.Name(L"profile1"); // this is _allProfiles.at(2) - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - - settings->_ParseJsonString(settings0String, false); - VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size()); - - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size()); - } - - void DynamicProfileTests::UserProfilesFromDisabledSourcesDontAppear() - { - winrt::guid guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - winrt::guid guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); - - const std::string settings0String{ R"( - { - "disabledProfileSources": ["Terminal.App.UnitTest.1"], - "profiles": [ - { - "name" : "profile0FromUserSettings", // this is _allProfiles.at(0) - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "source": "Terminal.App.UnitTest.0" - }, - { - "name" : "profile1FromUserSettings", // this shouldn't be in the profiles at all - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", - "source": "Terminal.App.UnitTest.1" - }, - { - "name" : "profile3", // this is _allProfiles.at(1) - "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" - } - ] - })" }; - - auto gen0 = std::make_unique(L"Terminal.App.UnitTest.0"); - gen0->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - p0.Name(L"profile0"); // this is _allProfiles.at(0) - profiles.push_back(p0); - return profiles; - }; - auto gen1 = std::make_unique(L"Terminal.App.UnitTest.1"); - gen1->pfnGenerate = [guid0, guid1]() { - std::vector profiles; - Profile p0 = winrt::make(guid0); - Profile p1 = winrt::make(guid1); - p0.Name(L"profile0"); // this shouldn't be in the profiles at all - p1.Name(L"profile1"); // this shouldn't be in the profiles at all - profiles.push_back(p0); - profiles.push_back(p1); - return profiles; - }; - - auto settings = winrt::make_self(false); - settings->_profileGenerators.emplace_back(std::move(gen0)); - settings->_profileGenerators.emplace_back(std::move(gen1)); - - settings->_ParseJsonString(settings0String, false); - VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size()); - - settings->_LoadDynamicProfiles(); - VERIFY_ARE_EQUAL(1u, settings->_allProfiles.Size()); - - settings->LayerJson(settings->_userSettings); - VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size()); - } - -}; diff --git a/src/cascadia/ut_app/JsonTests.cpp b/src/cascadia/ut_app/JsonTests.cpp deleted file mode 100644 index 0d673ad2d..000000000 --- a/src/cascadia/ut_app/JsonTests.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "../TerminalSettingsModel/ColorScheme.h" -#include "../TerminalSettingsModel/Profile.h" -#include "../TerminalSettingsModel/CascadiaSettings.h" -#include "../LocalTests_SettingsModel/JsonTestClass.h" -#include "../types/inc/colorTable.hpp" - -using namespace Microsoft::Console; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using namespace WEX::Common; -using namespace winrt::Microsoft::Terminal::Settings::Model; -using namespace winrt::Microsoft::Terminal::Control; - -namespace TerminalAppUnitTests -{ - class JsonTests : public JsonTestClass - { - BEGIN_TEST_CLASS(JsonTests) - TEST_CLASS_PROPERTY(L"ActivationContext", L"TerminalApp.Unit.Tests.manifest") - END_TEST_CLASS() - - TEST_METHOD(ParseInvalidJson); - TEST_METHOD(ParseSimpleColorScheme); - TEST_METHOD(ProfileGeneratesGuid); - - TEST_CLASS_SETUP(ClassSetup) - { - InitializeJsonReader(); - // Use 4 spaces to indent instead of \t - _builder.settings_["indentation"] = " "; - return true; - } - - Json::Value VerifyParseSucceeded(std::string_view content); - void VerifyParseFailed(std::string_view content); - - private: - Json::StreamWriterBuilder _builder; - }; - - Json::Value JsonTests::VerifyParseSucceeded(std::string_view content) - { - Json::Value root; - std::string errs; - const bool parseResult = _reader->parse(content.data(), content.data() + content.size(), &root, &errs); - VERIFY_IS_TRUE(parseResult, winrt::to_hstring(errs).c_str()); - return root; - } - - void JsonTests::VerifyParseFailed(std::string_view content) - { - Json::Value root; - std::string errs; - const bool parseResult = _reader->parse(content.data(), content.data() + content.size(), &root, &errs); - VERIFY_IS_FALSE(parseResult); - } - - void JsonTests::ParseInvalidJson() - { - const std::string badJson{ "{ foo : bar : baz }" }; - VerifyParseFailed(badJson); - } - - void JsonTests::ParseSimpleColorScheme() - { - const std::string campbellScheme{ "{" - "\"background\" : \"#0C0C0C\"," - "\"black\" : \"#0C0C0C\"," - "\"blue\" : \"#0037DA\"," - "\"brightBlack\" : \"#767676\"," - "\"brightBlue\" : \"#3B78FF\"," - "\"brightCyan\" : \"#61D6D6\"," - "\"brightGreen\" : \"#16C60C\"," - "\"brightPurple\" : \"#B4009E\"," - "\"brightRed\" : \"#E74856\"," - "\"brightWhite\" : \"#F2F2F2\"," - "\"brightYellow\" : \"#F9F1A5\"," - "\"cursorColor\" : \"#FFFFFF\"," - "\"cyan\" : \"#3A96DD\"," - "\"foreground\" : \"#F2F2F2\"," - "\"green\" : \"#13A10E\"," - "\"name\" : \"Campbell\"," - "\"purple\" : \"#881798\"," - "\"red\" : \"#C50F1F\"," - "\"selectionBackground\" : \"#131313\"," - "\"white\" : \"#CCCCCC\"," - "\"yellow\" : \"#C19C00\"" - "}" }; - - const auto schemeObject = VerifyParseSucceeded(campbellScheme); - auto scheme = implementation::ColorScheme::FromJson(schemeObject); - VERIFY_ARE_EQUAL(L"Campbell", scheme->Name()); - VERIFY_ARE_EQUAL(til::color(0xf2, 0xf2, 0xf2, 255), til::color{ scheme->Foreground() }); - VERIFY_ARE_EQUAL(til::color(0x0c, 0x0c, 0x0c, 255), til::color{ scheme->Background() }); - VERIFY_ARE_EQUAL(til::color(0x13, 0x13, 0x13, 255), til::color{ scheme->SelectionBackground() }); - VERIFY_ARE_EQUAL(til::color(0xFF, 0xFF, 0xFF, 255), til::color{ scheme->CursorColor() }); - - std::array expectedCampbellTable; - auto campbellSpan = gsl::span(&expectedCampbellTable[0], COLOR_TABLE_SIZE); - Utils::InitializeCampbellColorTable(campbellSpan); - Utils::SetColorTableAlpha(campbellSpan, 0); - - for (size_t i = 0; i < expectedCampbellTable.size(); i++) - { - const auto& expected = expectedCampbellTable.at(i); - const til::color actual{ scheme->Table().at(static_cast(i)) }; - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"Roundtrip Test for Color Scheme"); - Json::Value outJson{ scheme->ToJson() }; - VERIFY_ARE_EQUAL(schemeObject, outJson); - } - - void JsonTests::ProfileGeneratesGuid() - { - // Parse some profiles without guids. We should NOT generate new guids - // for them. If a profile doesn't have a GUID, we'll leave its _guid - // set to nullopt. The Profile::Guid() getter will - // ensure all profiles have a GUID that's actually set. - // The null guid _is_ a valid guid, so we won't re-generate that - // guid. null is _not_ a valid guid, so we'll leave that nullopt - - // See SettingsTests::ValidateProfilesGenerateGuids for a version of - // this test that includes synthesizing GUIDS for profiles without GUIDs - // set - - const std::string profileWithoutGuid{ R"({ - "name" : "profile0" - })" }; - const std::string secondProfileWithoutGuid{ R"({ - "name" : "profile1" - })" }; - const std::string profileWithNullForGuid{ R"({ - "name" : "profile2", - "guid" : null - })" }; - const std::string profileWithNullGuid{ R"({ - "name" : "profile3", - "guid" : "{00000000-0000-0000-0000-000000000000}" - })" }; - const std::string profileWithGuid{ R"({ - "name" : "profile4", - "guid" : "{6239a42c-1de4-49a3-80bd-e8fdd045185c}" - })" }; - - const auto profile0Json = VerifyParseSucceeded(profileWithoutGuid); - const auto profile1Json = VerifyParseSucceeded(secondProfileWithoutGuid); - const auto profile2Json = VerifyParseSucceeded(profileWithNullForGuid); - const auto profile3Json = VerifyParseSucceeded(profileWithNullGuid); - const auto profile4Json = VerifyParseSucceeded(profileWithGuid); - - const auto profile0 = implementation::Profile::FromJson(profile0Json); - const auto profile1 = implementation::Profile::FromJson(profile1Json); - const auto profile2 = implementation::Profile::FromJson(profile2Json); - const auto profile3 = implementation::Profile::FromJson(profile3Json); - const auto profile4 = implementation::Profile::FromJson(profile4Json); - const winrt::guid cmdGuid = Utils::GuidFromString(L"{6239a42c-1de4-49a3-80bd-e8fdd045185c}"); - const winrt::guid nullGuid{}; - - VERIFY_IS_FALSE(profile0->HasGuid()); - VERIFY_IS_FALSE(profile1->HasGuid()); - VERIFY_IS_FALSE(profile2->HasGuid()); - VERIFY_IS_TRUE(profile3->HasGuid()); - VERIFY_IS_TRUE(profile4->HasGuid()); - - VERIFY_ARE_EQUAL(profile3->Guid(), nullGuid); - VERIFY_ARE_EQUAL(profile4->Guid(), cmdGuid); - } -} diff --git a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj index 32bafde87..50ad0c4d1 100644 --- a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj +++ b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj @@ -29,9 +29,9 @@ - + - + Create diff --git a/src/cascadia/ut_app/TestDynamicProfileGenerator.h b/src/cascadia/ut_app/TestDynamicProfileGenerator.h deleted file mode 100644 index 7c30dd732..000000000 --- a/src/cascadia/ut_app/TestDynamicProfileGenerator.h +++ /dev/null @@ -1,44 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- TestDynamicProfileGenerator.hpp - -Abstract: -- This is a helper class for writing tests using dynamic profiles. Lets you - easily set a arbitrary namespace and generation function for the profiles. - -Author(s): -- Mike Griese - August 2019 ---*/ - -#include "../TerminalSettingsModel/IDynamicProfileGenerator.h" - -namespace TerminalAppUnitTests -{ - class TestDynamicProfileGenerator; -}; - -class TerminalAppUnitTests::TestDynamicProfileGenerator final : - public Microsoft::Terminal::Settings::Model::IDynamicProfileGenerator -{ -public: - TestDynamicProfileGenerator(std::wstring_view ns) : - _namespace{ ns } {}; - - std::wstring_view GetNamespace() override { return _namespace; }; - - std::vector GenerateProfiles() override - { - if (pfnGenerate) - { - return pfnGenerate(); - } - return std::vector{}; - } - - std::wstring _namespace; - - std::function()> pfnGenerate{ nullptr }; -}; diff --git a/src/inc/DefaultSettings.h b/src/inc/DefaultSettings.h index a3ef99f68..bfec08251 100644 --- a/src/inc/DefaultSettings.h +++ b/src/inc/DefaultSettings.h @@ -33,8 +33,8 @@ constexpr uint16_t DEFAULT_FONT_WEIGHT = 400; // normal constexpr int DEFAULT_ROWS = 30; constexpr int DEFAULT_COLS = 120; -const std::wstring DEFAULT_PADDING{ L"8, 8, 8, 8" }; -const std::wstring DEFAULT_STARTING_DIRECTORY{ L"%USERPROFILE%" }; +constexpr std::wstring_view DEFAULT_PADDING{ L"8, 8, 8, 8" }; +constexpr std::wstring_view DEFAULT_STARTING_DIRECTORY{ L"%USERPROFILE%" }; constexpr auto DEFAULT_CURSOR_COLOR = COLOR_WHITE; constexpr COLORREF DEFAULT_CURSOR_HEIGHT = 25; diff --git a/src/inc/til/color.h b/src/inc/til/color.h index 76b458025..fa5a141c3 100644 --- a/src/inc/til/color.h +++ b/src/inc/til/color.h @@ -70,7 +70,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { } - operator COLORREF() const noexcept + constexpr operator COLORREF() const noexcept { return static_cast(abgr & 0x00FFFFFFu); } @@ -147,14 +147,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { } - operator winrt::Windows::UI::Color() const + constexpr operator winrt::Windows::UI::Color() const { - winrt::Windows::UI::Color ret; - ret.R = r; - ret.G = g; - ret.B = b; - ret.A = a; - return ret; + return { a, r, g, b }; } #endif @@ -164,14 +159,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { } - operator winrt::Microsoft::Terminal::Core::Color() const noexcept + constexpr operator winrt::Microsoft::Terminal::Core::Color() const noexcept { - winrt::Microsoft::Terminal::Core::Color ret; - ret.R = r; - ret.G = g; - ret.B = b; - ret.A = a; - return ret; + return { r, g, b, a }; } #endif diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index d37b6e076..a2a2c6cac 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -40,7 +40,7 @@ namespace Microsoft::Console::Utils } std::wstring GuidToString(const GUID guid); - GUID GuidFromString(const std::wstring wstr); + GUID GuidFromString(_Null_terminated_ const wchar_t* str); GUID CreateGuid(); std::string ColorToHexString(const til::color color); diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 323263382..d8af05c2e 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -39,10 +39,10 @@ std::wstring Utils::GuidToString(const GUID guid) // Return Value: // - A GUID if the string could successfully be parsed. On failure, throws the // failing HRESULT. -GUID Utils::GuidFromString(const std::wstring wstr) +GUID Utils::GuidFromString(_Null_terminated_ const wchar_t* str) { - GUID result{}; - THROW_IF_FAILED(IIDFromString(wstr.c_str(), &result)); + GUID result; + THROW_IF_FAILED(IIDFromString(str, &result)); return result; } diff --git a/tools/GenerateHeaderForJson.ps1 b/tools/GenerateHeaderForJson.ps1 index 953ca8594..25f65b9ab 100644 --- a/tools/GenerateHeaderForJson.ps1 +++ b/tools/GenerateHeaderForJson.ps1 @@ -13,7 +13,7 @@ param ( ) $fullPath = Resolve-Path $JsonFile -$jsonData = Get-Content $JsonFile +$jsonData = Get-Content -Raw $JsonFile | ConvertFrom-Json | ConvertTo-Json -Compress -Depth 100 @( "// Copyright (c) Microsoft Corporation", @@ -21,7 +21,5 @@ $jsonData = Get-Content $JsonFile "", "// THIS IS AN AUTO-GENERATED FILE", "// Generated from $($fullPath.Path)", - "constexpr std::string_view $($VariableName){", - ($jsonData | ForEach-Object { "R`"#($_`n)#`"" }), - "};" + "constexpr std::string_view $VariableName{ R`"#($jsonData)#`" };" ) | Out-File -FilePath $OutPath -Encoding utf8