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