-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for actions in fragments #16185
Changes from 5 commits
37d7def
d0fcdd9
2f39e1e
79cead1
35cde03
f80a897
d9aa152
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,6 +74,13 @@ namespace SettingsModelLocalTests | |
TEST_METHOD(TestInheritedCommand); | ||
TEST_METHOD(LoadFragmentsWithMultipleUpdates); | ||
|
||
TEST_METHOD(FragmentActionSimple); | ||
TEST_METHOD(FragmentActionNoKeys); | ||
TEST_METHOD(FragmentActionNested); | ||
TEST_METHOD(FragmentActionNestedNoName); | ||
TEST_METHOD(FragmentActionIterable); | ||
TEST_METHOD(FragmentActionRoundtrip); | ||
|
||
TEST_METHOD(MigrateReloadEnvVars); | ||
|
||
private: | ||
|
@@ -2023,6 +2030,189 @@ namespace SettingsModelLocalTests | |
VERIFY_ARE_EQUAL(L"NewName", loader.userSettings.profiles[0]->Name()); | ||
} | ||
|
||
void DeserializationTests::FragmentActionSimple() | ||
{ | ||
static constexpr std::wstring_view fragmentSource{ L"fragment" }; | ||
static constexpr std::string_view fragmentJson{ R"({ | ||
"actions": [ | ||
{ | ||
"command": { "action": "addMark" }, | ||
"name": "Test Action" | ||
}, | ||
] | ||
})" }; | ||
|
||
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; | ||
loader.MergeInboxIntoUserSettings(); | ||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); | ||
loader.FinalizeLayering(); | ||
|
||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader)); | ||
|
||
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap()); | ||
const auto actionsByName = actionMap->NameMap(); | ||
VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action")); | ||
} | ||
|
||
void DeserializationTests::FragmentActionNoKeys() | ||
{ | ||
static constexpr std::wstring_view fragmentSource{ L"fragment" }; | ||
static constexpr std::string_view fragmentJson{ R"({ | ||
"actions": [ | ||
{ | ||
"command": { "action": "addMark" }, | ||
"keys": "ctrl+f", | ||
"name": "Test Action" | ||
}, | ||
] | ||
})" }; | ||
|
||
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; | ||
loader.MergeInboxIntoUserSettings(); | ||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); | ||
loader.FinalizeLayering(); | ||
|
||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader)); | ||
|
||
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap()); | ||
const auto actionsByName = actionMap->NameMap(); | ||
VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action")); | ||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('F'), 0 })); | ||
} | ||
|
||
void DeserializationTests::FragmentActionNested() | ||
{ | ||
static constexpr std::wstring_view fragmentSource{ L"fragment" }; | ||
static constexpr std::string_view fragmentJson{ R"({ | ||
"actions": [ | ||
{ | ||
"name": "nested command", | ||
"commands": [ | ||
{ | ||
"name": "child1", | ||
"command": { "action": "newTab", "commandline": "ssh [email protected]" } | ||
}, | ||
{ | ||
"name": "child2", | ||
"command": { "action": "newTab", "commandline": "ssh [email protected]" } | ||
} | ||
] | ||
}, | ||
] | ||
})" }; | ||
|
||
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; | ||
loader.MergeInboxIntoUserSettings(); | ||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); | ||
loader.FinalizeLayering(); | ||
|
||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader)); | ||
|
||
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap()); | ||
const auto actionsByName = actionMap->NameMap(); | ||
const auto& nested{ actionsByName.TryLookup(L"nested command") }; | ||
VERIFY_IS_NOT_NULL(nested); | ||
VERIFY_IS_TRUE(nested.HasNestedCommands()); | ||
} | ||
|
||
void DeserializationTests::FragmentActionNestedNoName() | ||
{ | ||
// Basically the same as TestNestedCommandWithoutName | ||
static constexpr std::wstring_view fragmentSource{ L"fragment" }; | ||
static constexpr std::string_view fragmentJson{ R"({ | ||
"actions": [ | ||
{ | ||
"commands": [ | ||
{ | ||
"name": "child1", | ||
"command": { "action": "newTab", "commandline": "ssh [email protected]" } | ||
}, | ||
{ | ||
"name": "child2", | ||
"command": { "action": "newTab", "commandline": "ssh [email protected]" } | ||
} | ||
] | ||
}, | ||
] | ||
})" }; | ||
|
||
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; | ||
loader.MergeInboxIntoUserSettings(); | ||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); | ||
loader.FinalizeLayering(); | ||
|
||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader)); | ||
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size()); | ||
} | ||
void DeserializationTests::FragmentActionIterable() | ||
{ | ||
static constexpr std::wstring_view fragmentSource{ L"fragment" }; | ||
static constexpr std::string_view fragmentJson{ R"({ | ||
"actions": [ | ||
{ | ||
"name": "nested", | ||
"commands": [ | ||
{ | ||
"iterateOn": "schemes", | ||
"name": "${scheme.name}", | ||
"command": { "action": "setColorScheme", "colorScheme": "${scheme.name}" } | ||
} | ||
] | ||
}, | ||
] | ||
})" }; | ||
|
||
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; | ||
loader.MergeInboxIntoUserSettings(); | ||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); | ||
loader.FinalizeLayering(); | ||
|
||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader)); | ||
|
||
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap()); | ||
const auto actionsByName = actionMap->NameMap(); | ||
const auto& nested{ actionsByName.TryLookup(L"nested") }; | ||
VERIFY_IS_NOT_NULL(nested); | ||
VERIFY_IS_TRUE(nested.HasNestedCommands()); | ||
VERIFY_ARE_EQUAL(settings->GlobalSettings().ColorSchemes().Size(), nested.NestedCommands().Size()); | ||
} | ||
void DeserializationTests::FragmentActionRoundtrip() | ||
{ | ||
static constexpr std::wstring_view fragmentSource{ L"fragment" }; | ||
static constexpr std::string_view fragmentJson{ R"({ | ||
"actions": [ | ||
{ | ||
"command": { "action": "addMark" }, | ||
"name": "Test Action" | ||
}, | ||
] | ||
})" }; | ||
|
||
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; | ||
loader.MergeInboxIntoUserSettings(); | ||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); | ||
loader.FinalizeLayering(); | ||
|
||
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader)); | ||
|
||
const auto actionMap = winrt::get_self<implementation::ActionMap>(oldSettings->GlobalSettings().ActionMap()); | ||
const auto actionsByName = actionMap->NameMap(); | ||
VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action")); | ||
|
||
const auto oldResult{ oldSettings->ToJson() }; | ||
|
||
Log::Comment(L"Now, create a _new_ settings object from the re-serialization of the first"); | ||
implementation::SettingsLoader newLoader{ toString(oldResult), DefaultJson }; | ||
// NOTABLY! Don't load the fragment here. | ||
newLoader.MergeInboxIntoUserSettings(); | ||
newLoader.FinalizeLayering(); | ||
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader)); | ||
|
||
const auto& newActionMap = winrt::get_self<implementation::ActionMap>(newSettings->GlobalSettings().ActionMap()); | ||
const auto newActionsByName = newActionMap->NameMap(); | ||
VERIFY_IS_NULL(newActionsByName.TryLookup(L"Test Action")); | ||
} | ||
|
||
void DeserializationTests::MigrateReloadEnvVars() | ||
{ | ||
static constexpr std::string_view settings1Json{ R"( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,7 @@ | |
static constexpr std::string_view ProfilesListKey{ "list" }; | ||
static constexpr std::string_view SchemesKey{ "schemes" }; | ||
static constexpr std::string_view ThemesKey{ "themes" }; | ||
static constexpr std::string_view ActionsKey{ "actions" }; | ||
|
||
constexpr std::wstring_view systemThemeName{ L"system" }; | ||
constexpr std::wstring_view darkThemeName{ L"dark" }; | ||
|
@@ -629,7 +630,7 @@ | |
// schemes and profiles. Additionally this function supports profiles which specify an "updates" key. | ||
void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings) | ||
{ | ||
const auto json = _parseJson(content); | ||
auto json = _parseJson(content); | ||
|
||
settings.clear(); | ||
|
||
|
@@ -647,6 +648,18 @@ | |
} | ||
CATCH_LOG() | ||
} | ||
|
||
// Construct a temp Json::Value that contains ONLY the actions from the | ||
// fragment. This will allow fragments to add actions, but not | ||
// necessarily set other global properties. | ||
Json::Value tmp = {}; | ||
tmp[ActionsKey.data()] = json.root[ActionsKey.data()]; | ||
|
||
// Now parse that tmep json object, as if it were a global settings | ||
Check failure on line 658 in src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp GitHub Actions / Spell checking
|
||
zadjii-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// blob. Manually opt-out of keybinding parsing - fragments shouldn't be | ||
// allowed to bind actions to keys directly. We may want to revisit | ||
// circa GH#2205 | ||
settings.globals->LayerJson(tmp, false); | ||
zadjii-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
{ | ||
|
@@ -688,10 +701,10 @@ | |
} | ||
} | ||
|
||
for (const auto& kv : settings.globals->ColorSchemes()) | ||
{ | ||
userSettings.globals->AddColorScheme(kv.Value()); | ||
} | ||
// Add the parsed fragment globals as a parent of the user's settings. | ||
// Later, in FinalizeInheritance, this will result in the action map from | ||
// the fragments being applied before the user's own settings. | ||
userSettings.globals->AddLeastImportantParent(settings.globals); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite get this change and how it replaces the previous code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this one was tricky - it would manually insert schemes from fragments into the userSettings, which was weird but okay. Now, instead of doing that, I just set the whole globals object to be a parent of the user's globals. We'll still resolve the schemes in a similar way in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, this is likely to have downstream effects on me as well. thanks! |
||
} | ||
|
||
SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view& content) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i like the round-trip test verifying we didn't serialize someone else's action. clever.