diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index 5d2e25b80..682464c5e 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -21,6 +21,7 @@ ICustom IDialog IDirect IExplorer +IInheritable IMap IObject IStorage diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index 9a1ed2fb4..9f878d230 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -81,6 +81,7 @@ namespace SettingsModelLocalTests TEST_METHOD(TestRebindNestedCommand); TEST_METHOD(TestCopy); + TEST_METHOD(TestCloneInheritanceTree); TEST_CLASS_SETUP(ClassSetup) { @@ -831,7 +832,7 @@ namespace SettingsModelLocalTests const auto serialized0Profile = profile0->GenerateStub(); const auto profile1 = implementation::Profile::FromJson(serialized0Profile); VERIFY_IS_FALSE(profile0->HasGuid()); - VERIFY_IS_FALSE(profile1->HasGuid()); + VERIFY_IS_TRUE(profile1->HasGuid()); auto settings = winrt::make_self(); settings->_profiles.Append(*profile1); @@ -1355,6 +1356,7 @@ namespace SettingsModelLocalTests 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 autogeneratedGuid{ implementation::Profile::_GenerateGuidForProfile(name3, L"") }; const std::optional badGuid{}; VerifyParseSucceeded(settings0String); @@ -1366,7 +1368,7 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(guid0, settings->_GetProfileGuidByName(name0)); VERIFY_ARE_EQUAL(guid1, settings->_GetProfileGuidByName(name1)); VERIFY_ARE_EQUAL(guid2, settings->_GetProfileGuidByName(name2)); - VERIFY_ARE_EQUAL(badGuid, settings->_GetProfileGuidByName(name3)); + VERIFY_ARE_EQUAL(autogeneratedGuid, settings->_GetProfileGuidByName(name3)); VERIFY_ARE_EQUAL(badGuid, settings->_GetProfileGuidByName(badName)); auto prof0{ settings->FindProfile(guid0) }; @@ -1521,9 +1523,9 @@ namespace SettingsModelLocalTests { auto settings = winrt::make_self(false); settings->_ParseJsonString(settings0String, false); - VERIFY_IS_TRUE(settings->_userDefaultProfileSettings == Json::Value::null); + VERIFY_IS_NULL(settings->_userDefaultProfileSettings); settings->_ApplyDefaultsFromUserSettings(); - VERIFY_IS_FALSE(settings->_userDefaultProfileSettings == Json::Value::null); + VERIFY_IS_NOT_NULL(settings->_userDefaultProfileSettings); settings->LayerJson(settings->_userSettings); VERIFY_ARE_EQUAL(guid1String, settings->_globals->UnparsedDefaultProfile()); @@ -1573,9 +1575,9 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(2u, settings->_profiles.Size()); settings->_ParseJsonString(settings0String, false); - VERIFY_IS_TRUE(settings->_userDefaultProfileSettings == Json::Value::null); + VERIFY_IS_NULL(settings->_userDefaultProfileSettings); settings->_ApplyDefaultsFromUserSettings(); - VERIFY_IS_FALSE(settings->_userDefaultProfileSettings == Json::Value::null); + VERIFY_IS_NOT_NULL(settings->_userDefaultProfileSettings); Log::Comment(NoThrowString().Format( L"Ensure that cmd and powershell don't get their GUIDs overwritten")); @@ -1692,10 +1694,6 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_profiles.GetAt(1).Source()); VERIFY_ARE_EQUAL(L"Terminal.App.UnitTest.1", settings->_profiles.GetAt(2).Source()); - VERIFY_IS_TRUE(settings->_profiles.GetAt(0).HasGuid()); - VERIFY_IS_TRUE(settings->_profiles.GetAt(1).HasGuid()); - VERIFY_IS_TRUE(settings->_profiles.GetAt(2).HasGuid()); - VERIFY_ARE_EQUAL(guid1, settings->_profiles.GetAt(0).Guid()); VERIFY_ARE_EQUAL(guid1, settings->_profiles.GetAt(1).Guid()); VERIFY_ARE_EQUAL(guid2, settings->_profiles.GetAt(2).Guid()); @@ -2256,6 +2254,7 @@ namespace SettingsModelLocalTests settings->_ParseJsonString(settings1Json, false); settings->LayerJson(settings->_userSettings); settings->_ValidateSettings(); + commands = settings->_globals->Commands(); _logCommandNames(commands); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(0u, commands.Size()); @@ -2350,6 +2349,7 @@ namespace SettingsModelLocalTests settings->_ParseJsonString(settings1Json, false); settings->LayerJson(settings->_userSettings); settings->_ValidateSettings(); + commands = settings->_globals->Commands(); _logCommandNames(commands); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(1u, commands.Size()); @@ -2460,4 +2460,126 @@ namespace SettingsModelLocalTests copyImpl->_globals->WordDelimiters(L"changed value"); VERIFY_ARE_NOT_EQUAL(settings->_globals->WordDelimiters(), copyImpl->_globals->WordDelimiters()); } + + void DeserializationTests::TestCloneInheritanceTree() + { + const std::string settingsJson{ R"( + { + "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", + "profiles": + { + "defaults": { + "name": "PROFILE DEFAULTS" + }, + "list": [ + { + "guid": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", + "name": "CMD" + }, + { + "guid": "{61c54bbd-2222-5271-96e7-009a87ff44bf}", + "name": "PowerShell" + }, + { + "guid": "{61c54bbd-3333-5271-96e7-009a87ff44bf}" + } + ] + } + })" }; + + VerifyParseSucceeded(settingsJson); + + auto settings{ winrt::make_self() }; + settings->_ParseJsonString(settingsJson, false); + settings->_ApplyDefaultsFromUserSettings(); + settings->LayerJson(settings->_userSettings); + settings->_ValidateSettings(); + + const auto copy{ settings->Copy() }; + const auto copyImpl{ winrt::get_self(copy) }; + + // test globals + VERIFY_ARE_EQUAL(settings->_globals->DefaultProfile(), copyImpl->_globals->DefaultProfile()); + + // test profiles + VERIFY_ARE_EQUAL(settings->_profiles.Size(), copyImpl->_profiles.Size()); + VERIFY_ARE_EQUAL(settings->_profiles.GetAt(0).Name(), copyImpl->_profiles.GetAt(0).Name()); + VERIFY_ARE_EQUAL(settings->_profiles.GetAt(1).Name(), copyImpl->_profiles.GetAt(1).Name()); + VERIFY_ARE_EQUAL(settings->_profiles.GetAt(2).Name(), copyImpl->_profiles.GetAt(2).Name()); + VERIFY_ARE_EQUAL(settings->_userDefaultProfileSettings->Name(), copyImpl->_userDefaultProfileSettings->Name()); + + // Modifying profile.defaults should... + VERIFY_ARE_EQUAL(settings->_userDefaultProfileSettings->HasName(), copyImpl->_userDefaultProfileSettings->HasName()); + copyImpl->_userDefaultProfileSettings->Name(L"changed value"); + + // ...keep the same name for the first two profiles + VERIFY_ARE_EQUAL(settings->_profiles.Size(), copyImpl->_profiles.Size()); + VERIFY_ARE_EQUAL(settings->_profiles.GetAt(0).Name(), copyImpl->_profiles.GetAt(0).Name()); + VERIFY_ARE_EQUAL(settings->_profiles.GetAt(1).Name(), copyImpl->_profiles.GetAt(1).Name()); + + // ...but change the name for the one that inherited it from profile.defaults + VERIFY_ARE_NOT_EQUAL(settings->_profiles.GetAt(2).Name(), copyImpl->_profiles.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()); + + Log::Comment(L"Test empty profiles.defaults"); + const std::string emptyPDJson{ R"( + { + "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", + "profiles": + { + "defaults": { + }, + "list": [ + { + "guid": "{61c54bbd-2222-5271-96e7-009a87ff44bf}", + "name": "PowerShell" + } + ] + } + })" }; + + const std::string missingPDJson{ R"( + { + "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", + "profiles": + [ + { + "guid": "{61c54bbd-2222-5271-96e7-009a87ff44bf}", + "name": "PowerShell" + } + ] + })" }; + + 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(); + + const auto copy{ settings->Copy() }; + const auto copyImpl{ winrt::get_self(copy) }; + + // test optimization: if we don't have profiles.defaults, don't add it to the tree + VERIFY_IS_NULL(settings->_userDefaultProfileSettings); + VERIFY_ARE_EQUAL(settings->_userDefaultProfileSettings, copyImpl->_userDefaultProfileSettings); + + VERIFY_ARE_EQUAL(settings->Profiles().Size(), 1u); + VERIFY_ARE_EQUAL(settings->Profiles().Size(), copyImpl->Profiles().Size()); + + // so we should only have one parent, instead of two + auto srcProfile{ winrt::get_self(settings->Profiles().GetAt(0)) }; + auto copyProfile{ winrt::get_self(copyImpl->Profiles().GetAt(0)) }; + VERIFY_ARE_EQUAL(srcProfile->Parents().size(), 0u); + VERIFY_ARE_EQUAL(srcProfile->Parents().size(), copyProfile->Parents().size()); + }; + + verifyEmptyPD(emptyPDJson); + verifyEmptyPD(missingPDJson); + } } diff --git a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp index 03d2e7c08..aa2037595 100644 --- a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp @@ -87,7 +87,7 @@ namespace SettingsModelLocalTests // 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_FALSE(profile3->ShouldBeLayered(profile3Json)); + VERIFY_IS_TRUE(profile3->ShouldBeLayered(profile3Json)); } void ProfileTests::LayerProfileProperties() @@ -132,39 +132,41 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Layering profile1 on top of profile0")); - profile0->LayerJson(profile1Json); + auto profile1{ profile0->CreateChild() }; + profile1->LayerJson(profile1Json); - VERIFY_IS_NOT_NULL(profile0->Foreground()); - VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile0->Foreground().Value() }); + VERIFY_IS_NOT_NULL(profile1->Foreground()); + VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile1->Foreground().Value() }); - VERIFY_IS_NOT_NULL(profile0->Background()); - VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() }); + VERIFY_IS_NOT_NULL(profile1->Background()); + VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile1->Background().Value() }); - VERIFY_IS_NOT_NULL(profile0->Background()); - VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() }); + VERIFY_IS_NOT_NULL(profile1->Background()); + VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile1->Background().Value() }); - VERIFY_ARE_EQUAL(L"profile1", profile0->Name()); + VERIFY_ARE_EQUAL(L"profile1", profile1->Name()); - VERIFY_IS_FALSE(profile0->StartingDirectory().empty()); - VERIFY_ARE_EQUAL(L"C:/", profile0->StartingDirectory()); + VERIFY_IS_FALSE(profile1->StartingDirectory().empty()); + VERIFY_ARE_EQUAL(L"C:/", profile1->StartingDirectory()); Log::Comment(NoThrowString().Format( L"Layering profile2 on top of (profile0+profile1)")); - profile0->LayerJson(profile2Json); + auto profile2{ profile1->CreateChild() }; + profile2->LayerJson(profile2Json); - VERIFY_IS_NOT_NULL(profile0->Foreground()); - VERIFY_ARE_EQUAL(til::color(3, 3, 3), til::color{ profile0->Foreground().Value() }); + VERIFY_IS_NOT_NULL(profile2->Foreground()); + VERIFY_ARE_EQUAL(til::color(3, 3, 3), til::color{ profile2->Foreground().Value() }); - VERIFY_IS_NOT_NULL(profile0->Background()); - VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() }); + VERIFY_IS_NOT_NULL(profile2->Background()); + VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile2->Background().Value() }); - VERIFY_IS_NOT_NULL(profile0->SelectionBackground()); - VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile0->SelectionBackground().Value() }); + VERIFY_IS_NOT_NULL(profile2->SelectionBackground()); + VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile2->SelectionBackground().Value() }); - VERIFY_ARE_EQUAL(L"profile2", profile0->Name()); + VERIFY_ARE_EQUAL(L"profile2", profile2->Name()); - VERIFY_IS_FALSE(profile0->StartingDirectory().empty()); - VERIFY_ARE_EQUAL(L"C:/", profile0->StartingDirectory()); + VERIFY_IS_FALSE(profile2->StartingDirectory().empty()); + VERIFY_ARE_EQUAL(L"C:/", profile2->StartingDirectory()); } void ProfileTests::LayerProfileIcon() diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index af9cda077..c3c225b9f 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -499,6 +499,7 @@ namespace TerminalAppLocalTests const std::string settings0String{ R"( { + "defaultProfile": "profile5", "profiles": [ { "name" : "profile0", diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 6fc3145f8..c31bbeadb 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -756,17 +756,10 @@ namespace winrt::TerminalApp::implementation TerminalConnection::ITerminalConnection connection{ nullptr }; - winrt::guid connectionType{}; + winrt::guid connectionType = profile.ConnectionType(); winrt::guid sessionGuid{}; - const auto hasConnectionType = profile.HasConnectionType(); - if (hasConnectionType) - { - connectionType = profile.ConnectionType(); - } - - if (hasConnectionType && - connectionType == TerminalConnection::AzureConnection::ConnectionType() && + if (connectionType == TerminalConnection::AzureConnection::ConnectionType() && TerminalConnection::AzureConnection::IsAzureConnectionAvailable()) { // TODO GH#4661: Replace this with directly using the AzCon when our VT is better diff --git a/src/cascadia/TerminalApp/TerminalSettings.cpp b/src/cascadia/TerminalApp/TerminalSettings.cpp index cee6b82d0..662079d7d 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.cpp +++ b/src/cascadia/TerminalApp/TerminalSettings.cpp @@ -94,10 +94,7 @@ namespace winrt::TerminalApp::implementation _Commandline = profile.Commandline(); - if (!profile.StartingDirectory().empty()) - { - _StartingDirectory = profile.EvaluatedStartingDirectory(); - } + _StartingDirectory = profile.EvaluatedStartingDirectory(); // GH#2373: Use the tabTitle as the starting title if it exists, otherwise // use the profile name diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index def525891..ccaff546d 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -71,11 +71,6 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings:: // dynamic profile generators added by default auto settings{ winrt::make_self() }; settings->_globals = _globals->Copy(); - for (const auto profile : _profiles) - { - auto profImpl{ winrt::get_self(profile) }; - settings->_profiles.Append(*profImpl->Copy()); - } for (auto warning : _warnings) { settings->_warnings.Append(warning); @@ -85,10 +80,54 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings:: settings->_userSettingsString = _userSettingsString; settings->_userSettings = _userSettings; settings->_defaultSettings = _defaultSettings; - settings->_userDefaultProfileSettings = _userDefaultProfileSettings; + + _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 : _profiles) + { + winrt::com_ptr profileImpl; + profileImpl.copy_from(winrt::get_self(profile)); + dummyRootSource->InsertParent(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->_profiles.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. diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 37a7e7056..6ee43273a 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -102,10 +102,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::string _userSettingsString; Json::Value _userSettings; Json::Value _defaultSettings; - Json::Value _userDefaultProfileSettings{ Json::Value::null }; + winrt::com_ptr _userDefaultProfileSettings{ nullptr }; 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); winrt::com_ptr _FindMatchingColorScheme(const Json::Value& schemeJson); void _ParseJsonString(std::string_view fileData, const bool isDefaultSettings); @@ -114,6 +115,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool _PrependSchemaDirective(); bool _AppendDynamicProfilesToUserSettings(); std::string _ApplyFirstRunChangesToSettingsTemplate(std::string_view settingsTemplate) const; + void _CopyProfileInheritanceTree(com_ptr& cloneSettings) const; void _ApplyDefaultsFromUserSettings(); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 7f77a9812..36e4d0fe5 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -519,20 +519,6 @@ bool CascadiaSettings::_AppendDynamicProfilesToUserSettings() for (const auto& profile : _profiles) { - if (!profile.HasGuid()) - { - // If the profile doesn't have a guid, it's a name-only profile. - // During validation, we'll generate a GUID for the profile, but - // validation occurs after this. We should ignore these types of - // profiles. - // If a dynamic profile was generated _without_ a GUID, we also - // don't want it serialized here. The first check in - // Profile::ShouldBeLayered checks that the profile has a guid. For a - // dynamic profile without a GUID, that'll _never_ be true, so it - // would be impossible to be layered. - continue; - } - // Skip profiles that are in the user settings or the default settings. if (isInJsonObj(profile, _userSettings) || isInJsonObj(profile, _defaultSettings)) { @@ -598,6 +584,8 @@ winrt::com_ptr CascadiaSettings::FromJson(const Json::Value& j // 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()] }) @@ -635,10 +623,28 @@ void CascadiaSettings::LayerJson(const Json::Value& json) void CascadiaSettings::_LayerOrCreateProfile(const Json::Value& profileJson) { // Layer the json on top of an existing profile, if we have one: - auto pProfile = _FindMatchingProfile(profileJson); - if (pProfile) + auto profileIndex{ _FindMatchingProfileIndex(profileJson) }; + if (profileIndex) { - pProfile->LayerJson(profileJson); + auto parentProj{ _profiles.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); + } + else + { + // otherwise, add a new inheritance layer + auto childImpl{ parent->CreateChild() }; + childImpl->LayerJson(profileJson); + + // replace parent in _profiles with child + _profiles.SetAt(*profileIndex, *childImpl); + } } else { @@ -647,13 +653,13 @@ void CascadiaSettings::_LayerOrCreateProfile(const Json::Value& profileJson) // `source`. Dynamic profiles _must_ be layered on an existing profile. if (!Profile::IsDynamicProfileObject(profileJson)) { - auto profile = winrt::make_self(); + auto profile{ winrt::make_self() }; - // GH#2325: If we have a set of default profile settings, apply them here. + // 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->LayerJson(_userDefaultProfileSettings); + profile->InsertParent(0, _userDefaultProfileSettings); } profile->LayerJson(profileJson); @@ -675,17 +681,40 @@ void CascadiaSettings::_LayerOrCreateProfile(const Json::Value& profileJson) // profile exists. winrt::com_ptr CascadiaSettings::_FindMatchingProfile(const Json::Value& profileJson) { - for (auto profile : _profiles) + auto index{ _FindMatchingProfileIndex(profileJson) }; + if (index) { - auto profileImpl = winrt::get_self(profile); - if (profileImpl->ShouldBeLayered(profileJson)) - { - return profileImpl->get_strong(); - } + auto profile{ _profiles.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 < _profiles.Size(); ++i) + { + const auto profile{ _profiles.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" @@ -704,7 +733,7 @@ void CascadiaSettings::_ApplyDefaultsFromUserSettings() auto defaultSettings{ Json::Value::null }; if (const auto profiles{ _userSettings[JsonKey(ProfilesKey)] }) { - if (profiles.isObject()) + if (profiles.isObject() && !profiles[JsonKey(DefaultSettingsKey)].empty()) { defaultSettings = profiles[JsonKey(DefaultSettingsKey)]; } @@ -714,16 +743,26 @@ void CascadiaSettings::_ApplyDefaultsFromUserSettings() // from user settings file if (defaultSettings) { - _userDefaultProfileSettings = defaultSettings; - // Remove the `guid` member from the default settings. That'll // hyper-explode, so just don't let them do that. - _userDefaultProfileSettings.removeMember({ "guid" }); + defaultSettings.removeMember({ "guid" }); - for (auto profile : _profiles) + _userDefaultProfileSettings = winrt::make_self(); + _userDefaultProfileSettings->LayerJson(defaultSettings); + + const auto numOfProfiles{ _profiles.Size() }; + for (uint32_t profileIndex = 0; profileIndex < numOfProfiles; ++profileIndex) { - auto profileImpl = winrt::get_self(profile); - profileImpl->LayerJson(_userDefaultProfileSettings); + // create a child, so we inherit from the defaults.json layer + auto parentProj{ _profiles.GetAt(profileIndex) }; + auto parentImpl{ winrt::get_self(parentProj) }; + auto childImpl{ parentImpl->CreateChild() }; + + // Add profile.defaults as the _first_ parent to the child + childImpl->InsertParent(0, _userDefaultProfileSettings); + + // replace parent in _profiles with child + _profiles.SetAt(profileIndex, *childImpl); } } } diff --git a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.cpp b/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.cpp index 32ba251c7..08c2c67cf 100644 --- a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.cpp +++ b/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.cpp @@ -19,6 +19,7 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CreateDefaultProfile(const { const winrt::guid profileGuid{ Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(name))) }; + auto newProfile = winrt::make(profileGuid); newProfile.Name(name); diff --git a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.h b/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.h index 28b6c53cd..5a401d120 100644 --- a/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.h +++ b/src/cascadia/TerminalSettingsModel/DefaultProfileUtils.h @@ -16,6 +16,9 @@ Author(s): #include "Profile.h" +// !!! LOAD-BEARING +// If you change or delete this GUID, all dynamic profiles +// will become disconnected from user settings. // {2bde4a90-d05f-401c-9492-e40884ead1d8} // 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 } }; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index a4db069bf..03bda082c 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -55,7 +55,7 @@ static constexpr bool debugFeaturesDefault{ false }; GlobalAppSettings::GlobalAppSettings() : _keymap{ winrt::make_self() }, _keybindingsWarnings{}, - _unparsedDefaultProfile{}, + _validDefaultProfile{ false }, _defaultProfile{}, _DebugFeaturesEnabled{ debugFeaturesDefault } { @@ -63,6 +63,25 @@ GlobalAppSettings::GlobalAppSettings() : _colorSchemes = winrt::single_threaded_map(); } +// Method Description: +// - Copies any extraneous data from the parent before completing a CreateChild call +// Arguments: +// - +// Return Value: +// - +void GlobalAppSettings::_FinalizeInheritance() +{ + // Globals only ever has 1 parent + FAIL_FAST_IF(_parents.size() > 1); + for (auto parent : _parents) + { + _keymap = std::move(parent->_keymap); + _keybindingsWarnings = std::move(parent->_keybindingsWarnings); + _colorSchemes = std::move(parent->_colorSchemes); + _commands = std::move(parent->_commands); + } +} + winrt::com_ptr GlobalAppSettings::Copy() const { auto globals{ winrt::make_self() }; @@ -91,21 +110,38 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_UseTabSwitcher = _UseTabSwitcher; globals->_DisableAnimations = _DisableAnimations; - globals->_unparsedDefaultProfile = _unparsedDefaultProfile; + globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile; + globals->_validDefaultProfile = _validDefaultProfile; globals->_defaultProfile = _defaultProfile; - globals->_keymap = _keymap->Copy(); + if (_keymap) + { + globals->_keymap = _keymap->Copy(); + } std::copy(_keybindingsWarnings.begin(), _keybindingsWarnings.end(), std::back_inserter(globals->_keybindingsWarnings)); - for (auto kv : _colorSchemes) + if (_colorSchemes) { - const auto schemeImpl{ winrt::get_self(kv.Value()) }; - globals->_colorSchemes.Insert(kv.Key(), *schemeImpl->Copy()); + for (auto kv : _colorSchemes) + { + const auto schemeImpl{ winrt::get_self(kv.Value()) }; + globals->_colorSchemes.Insert(kv.Key(), *schemeImpl->Copy()); + } } - for (auto kv : _commands) + if (_commands) { - const auto commandImpl{ winrt::get_self(kv.Value()) }; - globals->_commands.Insert(kv.Key(), *commandImpl->Copy()); + for (auto kv : _commands) + { + const auto commandImpl{ winrt::get_self(kv.Value()) }; + globals->_commands.Insert(kv.Key(), *commandImpl->Copy()); + } + } + + // Globals only ever has 1 parent + FAIL_FAST_IF(_parents.size() > 1); + for (auto parent : _parents) + { + globals->InsertParent(parent->Copy()); } return globals; } @@ -115,24 +151,71 @@ 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::KeyMapping GlobalAppSettings::KeyMap() const noexcept { return *_keymap; @@ -153,7 +236,11 @@ winrt::com_ptr GlobalAppSettings::FromJson(const Json::Value& void GlobalAppSettings::LayerJson(const Json::Value& json) { - JsonUtils::GetValueForKey(json, DefaultProfileKey, _unparsedDefaultProfile); + // _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); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 8b2dbf5fd..e93c6c81f 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -3,7 +3,7 @@ Copyright (c) Microsoft Corporation Licensed under the MIT license. Module Name: -- CascadiaSettings.hpp +- GlobalAppSettings.h Abstract: - This class encapsulates all of the settings that are global to the app, and @@ -16,6 +16,7 @@ Author(s): #pragma once #include "GlobalAppSettings.g.h" +#include "IInheritable.h" #include "KeyMapping.h" #include "Command.h" @@ -30,10 +31,11 @@ namespace SettingsModelLocalTests namespace winrt::Microsoft::Terminal::Settings::Model::implementation { - struct GlobalAppSettings : GlobalAppSettingsT + struct GlobalAppSettings : GlobalAppSettingsT, IInheritable { public: GlobalAppSettings(); + void _FinalizeInheritance() override; com_ptr Copy() const; Windows::Foundation::Collections::IMapView ColorSchemes() noexcept; @@ -52,36 +54,40 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // by higher layers in the app. void DefaultProfile(const guid& defaultProfile) noexcept; guid DefaultProfile() const; - hstring UnparsedDefaultProfile() const; + bool HasUnparsedDefaultProfile() const; + winrt::hstring UnparsedDefaultProfile() const; + void UnparsedDefaultProfile(const hstring& value); + void ClearUnparsedDefaultProfile(); - GETSET_PROPERTY(int32_t, InitialRows, DEFAULT_ROWS); - GETSET_PROPERTY(int32_t, InitialCols, DEFAULT_COLS); - GETSET_PROPERTY(bool, AlwaysShowTabs, true); - GETSET_PROPERTY(bool, ShowTitleInTitlebar, true); - GETSET_PROPERTY(bool, ConfirmCloseAllTabs, true); - GETSET_PROPERTY(winrt::Windows::UI::Xaml::ElementTheme, Theme, winrt::Windows::UI::Xaml::ElementTheme::Default); - GETSET_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal); - GETSET_PROPERTY(bool, ShowTabsInTitlebar, true); - GETSET_PROPERTY(hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS); - GETSET_PROPERTY(bool, CopyOnSelect, false); - GETSET_PROPERTY(winrt::Microsoft::Terminal::TerminalControl::CopyFormat, CopyFormatting, 0); - GETSET_PROPERTY(bool, WarnAboutLargePaste, true); - GETSET_PROPERTY(bool, WarnAboutMultiLinePaste, true); - GETSET_PROPERTY(Model::LaunchPosition, InitialPosition, nullptr, nullptr); - GETSET_PROPERTY(Model::LaunchMode, LaunchMode, LaunchMode::DefaultMode); - GETSET_PROPERTY(bool, SnapToGridOnResize, true); - GETSET_PROPERTY(bool, ForceFullRepaintRendering, false); - GETSET_PROPERTY(bool, SoftwareRendering, false); - GETSET_PROPERTY(bool, ForceVTInput, false); - GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor - GETSET_PROPERTY(bool, StartOnUserLogin, false); - GETSET_PROPERTY(bool, AlwaysOnTop, false); - GETSET_PROPERTY(bool, UseTabSwitcher, true); - GETSET_PROPERTY(bool, DisableAnimations, false); + GETSET_SETTING(int32_t, InitialRows, DEFAULT_ROWS); + GETSET_SETTING(int32_t, InitialCols, DEFAULT_COLS); + GETSET_SETTING(bool, AlwaysShowTabs, true); + GETSET_SETTING(bool, ShowTitleInTitlebar, true); + GETSET_SETTING(bool, ConfirmCloseAllTabs, true); + GETSET_SETTING(winrt::Windows::UI::Xaml::ElementTheme, Theme, winrt::Windows::UI::Xaml::ElementTheme::Default); + GETSET_SETTING(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal); + GETSET_SETTING(bool, ShowTabsInTitlebar, true); + GETSET_SETTING(hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS); + GETSET_SETTING(bool, CopyOnSelect, false); + GETSET_SETTING(winrt::Microsoft::Terminal::TerminalControl::CopyFormat, CopyFormatting, 0); + GETSET_SETTING(bool, WarnAboutLargePaste, true); + GETSET_SETTING(bool, WarnAboutMultiLinePaste, true); + GETSET_SETTING(Model::LaunchPosition, InitialPosition, nullptr, nullptr); + GETSET_SETTING(Model::LaunchMode, LaunchMode, LaunchMode::DefaultMode); + GETSET_SETTING(bool, SnapToGridOnResize, true); + GETSET_SETTING(bool, ForceFullRepaintRendering, false); + GETSET_SETTING(bool, SoftwareRendering, false); + GETSET_SETTING(bool, ForceVTInput, false); + GETSET_SETTING(bool, DebugFeaturesEnabled); // default value set in constructor + GETSET_SETTING(bool, StartOnUserLogin, false); + GETSET_SETTING(bool, AlwaysOnTop, false); + GETSET_SETTING(bool, UseTabSwitcher, true); + GETSET_SETTING(bool, DisableAnimations, false); private: - hstring _unparsedDefaultProfile; guid _defaultProfile; + std::optional _UnparsedDefaultProfile{ std::nullopt }; + bool _validDefaultProfile; com_ptr _keymap; std::vector _keybindingsWarnings; @@ -89,6 +95,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Windows::Foundation::Collections::IMap _colorSchemes; Windows::Foundation::Collections::IMap _commands; + std::optional _getUnparsedDefaultProfileImpl() const; + friend class SettingsModelLocalTests::DeserializationTests; friend class SettingsModelLocalTests::ColorSchemeTests; }; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index e2cc79b46..430e76b13 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -28,31 +28,104 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass GlobalAppSettings { Guid DefaultProfile; - String UnparsedDefaultProfile(); + Boolean HasUnparsedDefaultProfile(); + void ClearUnparsedDefaultProfile(); + String UnparsedDefaultProfile; + Boolean HasInitialRows(); + void ClearInitialRows(); Int32 InitialRows; + + Boolean HasInitialCols(); + void ClearInitialCols(); Int32 InitialCols; + + Boolean HasAlwaysShowTabs(); + void ClearAlwaysShowTabs(); Boolean AlwaysShowTabs; + + Boolean HasShowTitleInTitlebar(); + void ClearShowTitleInTitlebar(); Boolean ShowTitleInTitlebar; + + Boolean HasConfirmCloseAllTabs(); + void ClearConfirmCloseAllTabs(); Boolean ConfirmCloseAllTabs; + + Boolean HasTheme(); + void ClearTheme(); Windows.UI.Xaml.ElementTheme Theme; + + Boolean HasTabWidthMode(); + void ClearTabWidthMode(); Microsoft.UI.Xaml.Controls.TabViewWidthMode TabWidthMode; + + Boolean HasShowTabsInTitlebar(); + void ClearShowTabsInTitlebar(); Boolean ShowTabsInTitlebar; + + Boolean HasWordDelimiters(); + void ClearWordDelimiters(); String WordDelimiters; + + Boolean HasCopyOnSelect(); + void ClearCopyOnSelect(); Boolean CopyOnSelect; + + Boolean HasCopyFormatting(); + void ClearCopyFormatting(); Microsoft.Terminal.TerminalControl.CopyFormat CopyFormatting; + + Boolean HasWarnAboutLargePaste(); + void ClearWarnAboutLargePaste(); Boolean WarnAboutLargePaste; + + Boolean HasWarnAboutMultiLinePaste(); + void ClearWarnAboutMultiLinePaste(); Boolean WarnAboutMultiLinePaste; + + Boolean HasInitialPosition(); + void ClearInitialPosition(); LaunchPosition InitialPosition; + + Boolean HasLaunchMode(); + void ClearLaunchMode(); LaunchMode LaunchMode; + + Boolean HasSnapToGridOnResize(); + void ClearSnapToGridOnResize(); Boolean SnapToGridOnResize; + + Boolean HasForceFullRepaintRendering(); + void ClearForceFullRepaintRendering(); Boolean ForceFullRepaintRendering; + + Boolean HasSoftwareRendering(); + void ClearSoftwareRendering(); Boolean SoftwareRendering; + + Boolean HasForceVTInput(); + void ClearForceVTInput(); Boolean ForceVTInput; + + Boolean HasDebugFeaturesEnabled(); + void ClearDebugFeaturesEnabled(); Boolean DebugFeaturesEnabled; + + Boolean HasStartOnUserLogin(); + void ClearStartOnUserLogin(); Boolean StartOnUserLogin; + + Boolean HasAlwaysOnTop(); + void ClearAlwaysOnTop(); Boolean AlwaysOnTop; + + Boolean HasUseTabSwitcher(); + void ClearUseTabSwitcher(); Boolean UseTabSwitcher; + + Boolean HasDisableAnimations(); + void ClearDisableAnimations(); Boolean DisableAnimations; Windows.Foundation.Collections.IMapView ColorSchemes(); diff --git a/src/cascadia/TerminalSettingsModel/IInheritable.h b/src/cascadia/TerminalSettingsModel/IInheritable.h new file mode 100644 index 000000000..c8c1ee1e4 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/IInheritable.h @@ -0,0 +1,212 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- IInheritable.h + +Abstract: +- An interface allowing settings objects to inherit settings from a parent + +Author(s): +- Carlos Zamora - October 2020 + +--*/ +#pragma once + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + template + struct IInheritable + { + public: + // Method Description: + // - Create a new instance of T, but set its parent to this instance + // Arguments: + // - + // Return Value: + // - a new instance of T with this instance set as its parent + com_ptr CreateChild() const + { + auto child{ winrt::make_self() }; + + // set "this" as the parent. + // However, "this" is an IInheritable, so we need to cast it as T (the impl winrt type) + // to pass ownership over to the com_ptr. + com_ptr parent; + winrt::copy_from_abi(parent, const_cast(static_cast(this))); + child->InsertParent(parent); + + child->_FinalizeInheritance(); + return child; + } + + void InsertParent(com_ptr parent) + { + _parents.push_back(parent); + } + + void InsertParent(size_t index, com_ptr parent) + { + auto pos{ _parents.begin() + index }; + _parents.insert(pos, parent); + } + + const std::vector>& Parents() + { + return _parents; + } + + protected: + std::vector> _parents{}; + + // Method Description: + // - Actions to be performed after a child was created. Generally used to set + // any extraneous data from the parent into the child. + // Arguments: + // - + // Return Value: + // - + virtual void _FinalizeInheritance() {} + }; + + // This is like std::optional, but we can use it in inheritance to determine whether the user explicitly cleared it + template + struct NullableSetting + { + std::optional setting{ std::nullopt }; + bool set{ false }; + }; +} + +// Use this macro to quickly implement both getters and the setter for an +// inheritable setting property. This is similar to the GETSET_PROPERTY macro, except... +// - Has(): checks if the user explicitly set a value for this setting +// - Getter(): return the resolved value +// - Setter(): set the value directly +// - Clear(): clear the user set value +// - the setting is saved as an optional, where nullopt means +// that we must inherit the value from our parent +#define GETSET_SETTING(type, name, ...) \ +public: \ + /* Returns true if the user explicitly set the value, false otherwise*/ \ + bool Has##name() const \ + { \ + return _##name.has_value(); \ + }; \ + \ + /* Returns the resolved value for this setting */ \ + /* fallback: user set value --> inherited value --> system set value */ \ + type name() const \ + { \ + const auto val{ _get##name##Impl() }; \ + return val ? *val : type{ __VA_ARGS__ }; \ + }; \ + \ + /* Overwrite the user set value */ \ + void name(const type& value) \ + { \ + _##name = value; \ + }; \ + \ + /* Clear the user set value */ \ + void Clear##name() \ + { \ + _##name = std::nullopt; \ + }; \ + \ +private: \ + std::optional _##name{ std::nullopt }; \ + std::optional _get##name##Impl() const \ + { \ + /*return user set value*/ \ + if (_##name) \ + { \ + return _##name; \ + } \ + \ + /*user set value was not set*/ \ + /*iterate through parents to find a value*/ \ + for (auto parent : _parents) \ + { \ + if (auto val{ parent->_get##name##Impl() }) \ + { \ + return val; \ + } \ + } \ + \ + /*no value was found*/ \ + return std::nullopt; \ + }; + +// This macro is similar to the one above, but is reserved for optional settings +// like Profile.Foreground (where null is interpreted +// as an acceptable value, rather than "inherit") +// "type" is exposed as an IReference +#define GETSET_NULLABLE_SETTING(type, name, ...) \ +public: \ + /* Returns true if the user explicitly set the value, false otherwise*/ \ + bool Has##name() const \ + { \ + return _##name.set; \ + }; \ + \ + /* Returns the resolved value for this setting */ \ + /* fallback: user set value --> inherited value --> system set value */ \ + winrt::Windows::Foundation::IReference name() const \ + { \ + const auto val{ _get##name##Impl() }; \ + if (val.set) \ + { \ + if (val.setting) \ + { \ + return *val.setting; \ + } \ + return nullptr; \ + } \ + return winrt::Windows::Foundation::IReference{ __VA_ARGS__ }; \ + }; \ + \ + /* Overwrite the user set value */ \ + void name(const winrt::Windows::Foundation::IReference& value) \ + { \ + if (value) /*set value is different*/ \ + { \ + _##name.setting = value.Value(); \ + } \ + else \ + { \ + _##name.setting = std::nullopt; \ + } \ + _##name.set = true; \ + }; \ + \ + /* Clear the user set value */ \ + void Clear##name() \ + { \ + _##name.set = false; \ + }; \ + \ +private: \ + NullableSetting _##name{}; \ + NullableSetting _get##name##Impl() const \ + { \ + /*return user set value*/ \ + if (Has##name()) \ + { \ + return _##name; \ + } \ + \ + /*user set value was not set*/ \ + /*iterate through parents to find a value*/ \ + for (auto parent : _parents) \ + { \ + auto val{ parent->_get##name##Impl() }; \ + if (val.set) \ + { \ + return val; \ + } \ + } \ + /*no value was found*/ \ + return { std::nullopt, false }; \ + }; diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 12dd94d35..5e367a602 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -43,6 +43,7 @@ GlobalAppSettings.idl + diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index cbaad732c..d7404b98f 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -70,49 +70,100 @@ Profile::Profile(guid guid) : { } -winrt::com_ptr Profile::Copy() const +winrt::com_ptr Profile::CopySettings(winrt::com_ptr source) { auto profile{ winrt::make_self() }; - 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->_AcrylicOpacity = _AcrylicOpacity; - profile->_ScrollState = _ScrollState; - profile->_FontFace = _FontFace; - profile->_FontSize = _FontSize; - profile->_FontWeight = _FontWeight; - profile->_Padding = _Padding; - profile->_Commandline = _Commandline; - profile->_StartingDirectory = _StartingDirectory; - profile->_BackgroundImagePath = _BackgroundImagePath; - profile->_BackgroundImageOpacity = _BackgroundImageOpacity; - profile->_BackgroundImageStretchMode = _BackgroundImageStretchMode; - profile->_AntialiasingMode = _AntialiasingMode; - profile->_RetroTerminalEffect = _RetroTerminalEffect; - profile->_ForceFullRepaintRendering = _ForceFullRepaintRendering; - profile->_SoftwareRendering = _SoftwareRendering; - profile->_ColorSchemeName = _ColorSchemeName; - profile->_Foreground = _Foreground; - profile->_Background = _Background; - profile->_SelectionBackground = _SelectionBackground; - profile->_CursorColor = _CursorColor; - profile->_HistorySize = _HistorySize; - profile->_SnapOnInput = _SnapOnInput; - profile->_AltGrAliasing = _AltGrAliasing; - profile->_CursorShape = _CursorShape; - profile->_CursorHeight = _CursorHeight; - profile->_Guid = _Guid; - profile->_ConnectionType = _ConnectionType; - profile->_BackgroundImageAlignment = _BackgroundImageAlignment; + 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->_AcrylicOpacity = source->_AcrylicOpacity; + profile->_ScrollState = source->_ScrollState; + profile->_FontFace = source->_FontFace; + profile->_FontSize = source->_FontSize; + profile->_FontWeight = source->_FontWeight; + profile->_Padding = source->_Padding; + profile->_Commandline = source->_Commandline; + profile->_StartingDirectory = source->_StartingDirectory; + profile->_BackgroundImagePath = source->_BackgroundImagePath; + profile->_BackgroundImageOpacity = source->_BackgroundImageOpacity; + profile->_BackgroundImageStretchMode = source->_BackgroundImageStretchMode; + profile->_AntialiasingMode = source->_AntialiasingMode; + profile->_RetroTerminalEffect = source->_RetroTerminalEffect; + profile->_ForceFullRepaintRendering = source->_ForceFullRepaintRendering; + profile->_SoftwareRendering = source->_SoftwareRendering; + profile->_ColorSchemeName = source->_ColorSchemeName; + profile->_Foreground = source->_Foreground; + profile->_Background = source->_Background; + profile->_SelectionBackground = source->_SelectionBackground; + profile->_CursorColor = source->_CursorColor; + profile->_HistorySize = source->_HistorySize; + profile->_SnapOnInput = source->_SnapOnInput; + profile->_AltGrAliasing = source->_AltGrAliasing; + profile->_CursorShape = source->_CursorShape; + profile->_CursorHeight = source->_CursorHeight; + profile->_BellStyle = source->_BellStyle; + profile->_BackgroundImageAlignment = source->_BackgroundImageAlignment; + profile->_ConnectionType = source->_ConnectionType; + 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 + cloneGraph->InsertParent(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 + cloneGraph->InsertParent(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: // - 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. @@ -129,19 +180,17 @@ Json::Value Profile::GenerateStub() const Json::Value stub; ///// Profile-specific settings ///// - if (_Guid.has_value()) + 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(GuidKey)] = winrt::to_string(Utils::GuidToString(*_Guid)); + stub[JsonKey(SourceKey)] = winrt::to_string(source); } - stub[JsonKey(NameKey)] = winrt::to_string(_Name); - - if (!_Source.empty()) - { - stub[JsonKey(SourceKey)] = winrt::to_string(_Source); - } - - stub[JsonKey(HiddenKey)] = _Hidden; + stub[JsonKey(HiddenKey)] = Hidden(); return stub; } @@ -169,40 +218,37 @@ winrt::com_ptr>(json, GuidKey) }) + const auto otherGuid{ JsonUtils::GetValueForKey>(json, GuidKey) }; + const auto otherSource{ JsonUtils::GetValueForKey>(json, SourceKey) }; + if (otherGuid) { - if (otherGuid.value() != *_Guid) + if (otherGuid.value() != Guid()) { return false; } } else { - // If the other json object didn't have a GUID, we definitely don't want - // to layer. We technically might have the same name, and would - // auto-generate the same guid, but they should be treated as different - // profiles. - return false; + // 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; + } } - std::optional otherSource; - bool otherHadSource = JsonUtils::GetValueForKey(json, SourceKey, otherSource); - // For profiles with a `source`, also check the `source` property. bool sourceMatches = false; - if (!_Source.empty()) + const auto mySource{ Source() }; + if (!mySource.empty()) { - if (otherHadSource) + if (otherSource.has_value()) { // If we have a source and the other has a source, compare them! - sourceMatches = *otherSource == _Source; + sourceMatches = *otherSource == mySource; } else { @@ -210,9 +256,9 @@ bool Profile::ShouldBeLayered(const Json::Value& json) const // `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 (_Source == WslGeneratorNamespace || - _Source == AzureGeneratorNamespace || - _Source == PowershellCoreGeneratorNamespace) + if (mySource == WslGeneratorNamespace || + mySource == AzureGeneratorNamespace || + mySource == PowershellCoreGeneratorNamespace) { sourceMatches = true; } @@ -247,10 +293,10 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, HiddenKey, _Hidden); // Core Settings - JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground); - JsonUtils::GetValueForKey(json, BackgroundKey, _Background); - JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _SelectionBackground); - JsonUtils::GetValueForKey(json, CursorColorKey, _CursorColor); + _Foreground.set = JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground.setting); + _Background.set = JsonUtils::GetValueForKey(json, BackgroundKey, _Background.setting); + _SelectionBackground.set = JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _SelectionBackground.setting); + _CursorColor.set = JsonUtils::GetValueForKey(json, CursorColorKey, _CursorColor.setting); JsonUtils::GetValueForKey(json, ColorSchemeKey, _ColorSchemeName); // TODO:MSFT:20642297 - Use a sentinel value (-1) for "Infinite scrollback" @@ -277,7 +323,16 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, PaddingKey, _Padding, JsonUtils::PermissiveStringConverter{}); JsonUtils::GetValueForKey(json, ScrollbarStateKey, _ScrollState); - JsonUtils::GetValueForKey(json, StartingDirectoryKey, _StartingDirectory); + + // StartingDirectory is "nullable". But we represent a null starting directory as the empty string + // When null is set in the JSON, we empty initialize startDir (empty string), and set StartingDirectory to that + // Without this, we're accidentally setting StartingDirectory to nullopt instead. + hstring startDir; + if (JsonUtils::GetValueForKey(json, StartingDirectoryKey, startDir)) + { + _StartingDirectory = startDir; + } + JsonUtils::GetValueForKey(json, IconKey, _Icon); JsonUtils::GetValueForKey(json, BackgroundImageKey, _BackgroundImagePath); JsonUtils::GetValueForKey(json, BackgroundImageOpacityKey, _BackgroundImageOpacity); @@ -286,7 +341,7 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect); JsonUtils::GetValueForKey(json, AntialiasingModeKey, _AntialiasingMode); - JsonUtils::GetValueForKey(json, TabColorKey, _TabColor); + _TabColor.set = JsonUtils::GetValueForKey(json, TabColorKey, _TabColor.setting); JsonUtils::GetValueForKey(json, BellStyleKey, _BellStyle); } @@ -300,13 +355,14 @@ void Profile::LayerJson(const Json::Value& json) // - This profile's expanded background image path / desktops's wallpaper path /the empty string. winrt::hstring Profile::ExpandedBackgroundImagePath() const { - if (_BackgroundImagePath.empty()) + const auto path{ BackgroundImagePath() }; + if (path.empty()) { - return _BackgroundImagePath; + return path; } // checks if the user would like to copy their desktop wallpaper // if so, replaces the path with the desktop wallpaper's path - else if (_BackgroundImagePath == to_hstring(DesktopWallpaperEnum)) + else if (path == to_hstring(DesktopWallpaperEnum)) { WCHAR desktopWallpaper[MAX_PATH]; @@ -322,13 +378,19 @@ winrt::hstring Profile::ExpandedBackgroundImagePath() const } else { - return winrt::hstring{ wil::ExpandEnvironmentStringsW(_BackgroundImagePath.c_str()) }; + return winrt::hstring{ wil::ExpandEnvironmentStringsW(path.c_str()) }; } } winrt::hstring Profile::EvaluatedStartingDirectory() const { - return winrt::hstring{ Profile::EvaluateStartingDirectory(_StartingDirectory.c_str()) }; + auto path{ StartingDirectory() }; + if (!path.empty()) + { + return winrt::hstring{ Profile::EvaluateStartingDirectory(path.c_str()) }; + } + // treated as "inherit directory from parent process" + return path; } // Method Description: @@ -368,11 +430,11 @@ std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory) // will _not_ change the profile's GUID. void Profile::GenerateGuidIfNecessary() noexcept { - if (!_Guid.has_value()) + if (!_getGuidImpl().has_value()) { // Always use the name to generate the temporary GUID. That way, across // reloads, we'll generate the same static GUID. - _Guid = Profile::_GenerateGuidForProfile(_Name, _Source); + _Guid = Profile::_GenerateGuidForProfile(Name(), Source()); TraceLoggingWrite( g_hSettingsModelProvider, @@ -438,54 +500,50 @@ winrt::guid Profile::GetGuidOrGenerateForJson(const Json::Value& json) noexcept return Profile::_GenerateGuidForProfile(name, source); } +#pragma region BackgroundImageAlignment +bool Profile::HasBackgroundImageAlignment() const noexcept +{ + return _BackgroundImageAlignment.has_value(); +} + +void Profile::ClearBackgroundImageAlignment() noexcept +{ + _BackgroundImageAlignment = std::nullopt; +} + const HorizontalAlignment Profile::BackgroundImageHorizontalAlignment() const noexcept { - return std::get(_BackgroundImageAlignment); + const auto val{ _getBackgroundImageAlignmentImpl() }; + return val ? std::get(*val) : HorizontalAlignment::Center; } void Profile::BackgroundImageHorizontalAlignment(const HorizontalAlignment& value) noexcept { - std::get(_BackgroundImageAlignment) = value; + if (HasBackgroundImageAlignment()) + { + std::get(*_BackgroundImageAlignment) = value; + } + else + { + _BackgroundImageAlignment = { value, VerticalAlignment::Center }; + } } const VerticalAlignment Profile::BackgroundImageVerticalAlignment() const noexcept { - return std::get(_BackgroundImageAlignment); + const auto val{ _getBackgroundImageAlignmentImpl() }; + return val ? std::get(*val) : VerticalAlignment::Center; } void Profile::BackgroundImageVerticalAlignment(const VerticalAlignment& value) noexcept { - std::get(_BackgroundImageAlignment) = value; -} - -bool Profile::HasGuid() const noexcept -{ - return _Guid.has_value(); -} - -winrt::guid Profile::Guid() const -{ - // This can throw if we never had our guid set to a legitimate value. - THROW_HR_IF_MSG(E_FAIL, !_Guid.has_value(), "Profile._guid always expected to have a value"); - return *_Guid; -} - -void Profile::Guid(const winrt::guid& guid) noexcept -{ - _Guid = guid; -} - -bool Profile::HasConnectionType() const noexcept -{ - return _ConnectionType.has_value(); -} - -winrt::guid Profile::ConnectionType() const noexcept -{ - return *_ConnectionType; -} - -void Profile::ConnectionType(const winrt::guid& conType) noexcept -{ - _ConnectionType = conType; + if (HasBackgroundImageAlignment()) + { + std::get(*_BackgroundImageAlignment) = value; + } + else + { + _BackgroundImageAlignment = { HorizontalAlignment::Center, value }; + } } +#pragma endregion diff --git a/src/cascadia/TerminalSettingsModel/Profile.h b/src/cascadia/TerminalSettingsModel/Profile.h index e02a5670b..4a42c4108 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.h +++ b/src/cascadia/TerminalSettingsModel/Profile.h @@ -16,21 +16,24 @@ Author(s): #pragma once #include "Profile.g.h" +#include "IInheritable.h" #include "../inc/cppwinrt_utils.h" #include "JsonUtils.h" #include // fwdecl unittest classes -namespace TerminalAppLocalTests +namespace SettingsModelLocalTests { - class SettingsTests; + class DeserializationTests; class ProfileTests; + class ColorSchemeTests; + class KeyBindingsTests; }; namespace TerminalAppUnitTests { - class JsonTests; class DynamicProfileTests; + class JsonTests; }; // GUID used for generating GUIDs at runtime, for profiles that did not have a @@ -39,12 +42,13 @@ constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b, namespace winrt::Microsoft::Terminal::Settings::Model::implementation { - struct Profile : ProfileT + struct Profile : ProfileT, IInheritable { public: Profile(); Profile(guid guid); - com_ptr Copy() const; + static com_ptr CloneInheritanceGraph(com_ptr oldProfile, com_ptr newProfile, std::unordered_map>& visited); + static com_ptr CopySettings(com_ptr source); Json::Value GenerateStub() const; static com_ptr FromJson(const Json::Value& json); @@ -57,83 +61,97 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void GenerateGuidIfNecessary() noexcept; static guid GetGuidOrGenerateForJson(const Json::Value& json) noexcept; - bool HasGuid() const noexcept; - winrt::guid Guid() const; - void Guid(const winrt::guid& guid) noexcept; - - bool HasConnectionType() const noexcept; - winrt::guid ConnectionType() const noexcept; - void ConnectionType(const winrt::guid& conType) noexcept; - // BackgroundImageAlignment is 1 setting saved as 2 separate values + bool HasBackgroundImageAlignment() const noexcept; + void ClearBackgroundImageAlignment() noexcept; const Windows::UI::Xaml::HorizontalAlignment BackgroundImageHorizontalAlignment() const noexcept; void BackgroundImageHorizontalAlignment(const Windows::UI::Xaml::HorizontalAlignment& value) noexcept; const Windows::UI::Xaml::VerticalAlignment BackgroundImageVerticalAlignment() const noexcept; void BackgroundImageVerticalAlignment(const Windows::UI::Xaml::VerticalAlignment& value) noexcept; - GETSET_PROPERTY(hstring, Name, L"Default"); - GETSET_PROPERTY(hstring, Source); - GETSET_PROPERTY(bool, Hidden, false); + GETSET_SETTING(guid, Guid, _GenerateGuidForProfile(Name(), Source())); + GETSET_SETTING(hstring, Name, L"Default"); + GETSET_SETTING(hstring, Source); + GETSET_SETTING(bool, Hidden, false); + GETSET_SETTING(guid, ConnectionType); - GETSET_PROPERTY(hstring, Icon); + GETSET_SETTING(hstring, Icon); - GETSET_PROPERTY(CloseOnExitMode, CloseOnExit, CloseOnExitMode::Graceful); - GETSET_PROPERTY(hstring, TabTitle); - GETSET_PROPERTY(Windows::Foundation::IReference, TabColor); - GETSET_PROPERTY(bool, SuppressApplicationTitle, false); + GETSET_SETTING(CloseOnExitMode, CloseOnExit, CloseOnExitMode::Graceful); + GETSET_SETTING(hstring, TabTitle); + GETSET_NULLABLE_SETTING(Windows::UI::Color, TabColor, nullptr); + GETSET_SETTING(bool, SuppressApplicationTitle, false); - GETSET_PROPERTY(bool, UseAcrylic, false); - GETSET_PROPERTY(double, AcrylicOpacity, 0.5); - GETSET_PROPERTY(Microsoft::Terminal::TerminalControl::ScrollbarState, ScrollState, Microsoft::Terminal::TerminalControl::ScrollbarState::Visible); + GETSET_SETTING(bool, UseAcrylic, false); + GETSET_SETTING(double, AcrylicOpacity, 0.5); + GETSET_SETTING(Microsoft::Terminal::TerminalControl::ScrollbarState, ScrollState, Microsoft::Terminal::TerminalControl::ScrollbarState::Visible); - GETSET_PROPERTY(hstring, FontFace, DEFAULT_FONT_FACE); - GETSET_PROPERTY(int32_t, FontSize, DEFAULT_FONT_SIZE); - GETSET_PROPERTY(Windows::UI::Text::FontWeight, FontWeight, DEFAULT_FONT_WEIGHT); - GETSET_PROPERTY(hstring, Padding, DEFAULT_PADDING); + GETSET_SETTING(hstring, FontFace, DEFAULT_FONT_FACE); + GETSET_SETTING(int32_t, FontSize, DEFAULT_FONT_SIZE); + GETSET_SETTING(Windows::UI::Text::FontWeight, FontWeight, DEFAULT_FONT_WEIGHT); + GETSET_SETTING(hstring, Padding, DEFAULT_PADDING); - GETSET_PROPERTY(hstring, Commandline, L"cmd.exe"); - GETSET_PROPERTY(hstring, StartingDirectory); + GETSET_SETTING(hstring, Commandline, L"cmd.exe"); + GETSET_SETTING(hstring, StartingDirectory); - GETSET_PROPERTY(hstring, BackgroundImagePath); - GETSET_PROPERTY(double, BackgroundImageOpacity, 1.0); - GETSET_PROPERTY(Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch::Fill); + GETSET_SETTING(hstring, BackgroundImagePath); + GETSET_SETTING(double, BackgroundImageOpacity, 1.0); + GETSET_SETTING(Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch::Fill); - GETSET_PROPERTY(Microsoft::Terminal::TerminalControl::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::TerminalControl::TextAntialiasingMode::Grayscale); - GETSET_PROPERTY(bool, RetroTerminalEffect, false); - GETSET_PROPERTY(bool, ForceFullRepaintRendering, false); - GETSET_PROPERTY(bool, SoftwareRendering, false); + GETSET_SETTING(Microsoft::Terminal::TerminalControl::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::TerminalControl::TextAntialiasingMode::Grayscale); + GETSET_SETTING(bool, RetroTerminalEffect, false); + GETSET_SETTING(bool, ForceFullRepaintRendering, false); + GETSET_SETTING(bool, SoftwareRendering, false); - GETSET_PROPERTY(hstring, ColorSchemeName, L"Campbell"); - GETSET_PROPERTY(Windows::Foundation::IReference, Foreground); - GETSET_PROPERTY(Windows::Foundation::IReference, Background); - GETSET_PROPERTY(Windows::Foundation::IReference, SelectionBackground); - GETSET_PROPERTY(Windows::Foundation::IReference, CursorColor); + GETSET_SETTING(hstring, ColorSchemeName, L"Campbell"); - GETSET_PROPERTY(int32_t, HistorySize, DEFAULT_HISTORY_SIZE); - GETSET_PROPERTY(bool, SnapOnInput, true); - GETSET_PROPERTY(bool, AltGrAliasing, true); + GETSET_NULLABLE_SETTING(Windows::UI::Color, Foreground, nullptr); + GETSET_NULLABLE_SETTING(Windows::UI::Color, Background, nullptr); + GETSET_NULLABLE_SETTING(Windows::UI::Color, SelectionBackground, nullptr); + GETSET_NULLABLE_SETTING(Windows::UI::Color, CursorColor, nullptr); - GETSET_PROPERTY(Microsoft::Terminal::TerminalControl::CursorStyle, CursorShape, Microsoft::Terminal::TerminalControl::CursorStyle::Bar); - GETSET_PROPERTY(uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT); + GETSET_SETTING(int32_t, HistorySize, DEFAULT_HISTORY_SIZE); + GETSET_SETTING(bool, SnapOnInput, true); + GETSET_SETTING(bool, AltGrAliasing, true); - GETSET_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::BellStyle, BellStyle, winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible); + GETSET_SETTING(Microsoft::Terminal::TerminalControl::CursorStyle, CursorShape, Microsoft::Terminal::TerminalControl::CursorStyle::Bar); + GETSET_SETTING(uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT); + + GETSET_SETTING(Model::BellStyle, BellStyle, BellStyle::Audible); private: - std::optional _Guid{ std::nullopt }; - std::optional _ConnectionType{ std::nullopt }; - std::tuple _BackgroundImageAlignment{ - Windows::UI::Xaml::HorizontalAlignment::Center, - Windows::UI::Xaml::VerticalAlignment::Center + std::optional> _BackgroundImageAlignment{ std::nullopt }; + std::optional> _getBackgroundImageAlignmentImpl() const + { + /*return user set value*/ + if (_BackgroundImageAlignment) + { + return _BackgroundImageAlignment; + } + + /*user set value was not set*/ /*iterate through parents to find a value*/ + for (auto parent : _parents) + { + if (auto val{ parent->_getBackgroundImageAlignmentImpl() }) + { + return val; + } + } + + /*no value was found*/ + return std::nullopt; }; static std::wstring EvaluateStartingDirectory(const std::wstring& directory); static guid _GenerateGuidForProfile(const hstring& name, const hstring& source) noexcept; - friend class TerminalAppLocalTests::SettingsTests; - friend class TerminalAppLocalTests::ProfileTests; - friend class TerminalAppUnitTests::JsonTests; + friend class SettingsModelLocalTests::DeserializationTests; + friend class SettingsModelLocalTests::ProfileTests; + friend class SettingsModelLocalTests::ColorSchemeTests; + friend class SettingsModelLocalTests::KeyBindingsTests; friend class TerminalAppUnitTests::DynamicProfileTests; + friend class TerminalAppUnitTests::JsonTests; }; } diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 453cc94b0..ce228214e 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -20,59 +20,157 @@ namespace Microsoft.Terminal.Settings.Model Profile(); Profile(Guid guid); + Boolean HasName(); + void ClearName(); String Name; + Boolean HasGuid(); Guid Guid; + + Boolean HasSource(); + void ClearSource(); String Source; + Boolean HasConnectionType(); Guid ConnectionType; + + Boolean HasHidden(); + void ClearHidden(); Boolean Hidden; + Boolean HasIcon(); + void ClearIcon(); String Icon; + Boolean HasCloseOnExit(); + void ClearCloseOnExit(); CloseOnExitMode CloseOnExit; + + Boolean HasTabTitle(); + void ClearTabTitle(); String TabTitle; + + Boolean HasTabColor(); + void ClearTabColor(); Windows.Foundation.IReference TabColor; + + Boolean HasSuppressApplicationTitle(); + void ClearSuppressApplicationTitle(); Boolean SuppressApplicationTitle; + Boolean HasUseAcrylic(); + void ClearUseAcrylic(); Boolean UseAcrylic; + + Boolean HasAcrylicOpacity(); + void ClearAcrylicOpacity(); Double AcrylicOpacity; + + Boolean HasScrollState(); + void ClearScrollState(); Microsoft.Terminal.TerminalControl.ScrollbarState ScrollState; + Boolean HasFontFace(); + void ClearFontFace(); String FontFace; + + Boolean HasFontSize(); + void ClearFontSize(); Int32 FontSize; + + Boolean HasFontWeight(); + void ClearFontWeight(); Windows.UI.Text.FontWeight FontWeight; + + Boolean HasPadding(); + void ClearPadding(); String Padding; + Boolean HasCommandline(); + void ClearCommandline(); String Commandline; + + Boolean HasStartingDirectory(); + void ClearStartingDirectory(); String StartingDirectory; String EvaluatedStartingDirectory { get; }; + Boolean HasBackgroundImagePath(); + void ClearBackgroundImagePath(); String BackgroundImagePath; String ExpandedBackgroundImagePath { get; }; + + Boolean HasBackgroundImageOpacity(); + void ClearBackgroundImageOpacity(); Double BackgroundImageOpacity; + + Boolean HasBackgroundImageStretchMode(); + void ClearBackgroundImageStretchMode(); Windows.UI.Xaml.Media.Stretch BackgroundImageStretchMode; + + Boolean HasBackgroundImageAlignment(); + void ClearBackgroundImageAlignment(); Windows.UI.Xaml.HorizontalAlignment BackgroundImageHorizontalAlignment; Windows.UI.Xaml.VerticalAlignment BackgroundImageVerticalAlignment; + Boolean HasAntialiasingMode(); + void ClearAntialiasingMode(); Microsoft.Terminal.TerminalControl.TextAntialiasingMode AntialiasingMode; + + Boolean HasRetroTerminalEffect(); + void ClearRetroTerminalEffect(); Boolean RetroTerminalEffect; + + Boolean HasForceFullRepaintRendering(); + void ClearForceFullRepaintRendering(); Boolean ForceFullRepaintRendering; + + Boolean HasSoftwareRendering(); + void ClearSoftwareRendering(); Boolean SoftwareRendering; + Boolean HasColorSchemeName(); + void ClearColorSchemeName(); String ColorSchemeName; + + Boolean HasForeground(); + void ClearForeground(); Windows.Foundation.IReference Foreground; + + Boolean HasBackground(); + void ClearBackground(); Windows.Foundation.IReference Background; + + Boolean HasSelectionBackground(); + void ClearSelectionBackground(); Windows.Foundation.IReference SelectionBackground; + + Boolean HasCursorColor(); + void ClearCursorColor(); Windows.Foundation.IReference CursorColor; + Boolean HasHistorySize(); + void ClearHistorySize(); Int32 HistorySize; + + Boolean HasSnapOnInput(); + void ClearSnapOnInput(); Boolean SnapOnInput; + + Boolean HasAltGrAliasing(); + void ClearAltGrAliasing(); Boolean AltGrAliasing; + Boolean HasCursorShape(); + void ClearCursorShape(); Microsoft.Terminal.TerminalControl.CursorStyle CursorShape; + + Boolean HasCursorHeight(); + void ClearCursorHeight(); UInt32 CursorHeight; + Boolean HasBellStyle(); + void ClearBellStyle(); BellStyle BellStyle; } } diff --git a/src/inc/til/coalesce.h b/src/inc/til/coalesce.h index 24153869a..f14564dd3 100644 --- a/src/inc/til/coalesce.h +++ b/src/inc/til/coalesce.h @@ -57,5 +57,4 @@ namespace til { return t1.has_value() ? t1 : coalesce(std::forward(t2)...); } - }