From 168d28b036f8a8822b7400d2539514cb54a32184 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 22 Sep 2021 18:27:31 +0200 Subject: [PATCH] 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