Skip to content

Commit 6d2fc5d

Browse files
authored
Extract ActionAndArgs::FromJson into its own class (#6351)
## Summary of the Pull Request Pulls the `ActionAndArgs` deserializing into its own class, separate from `AppKeyBindings`. Some 2.0 features are going to need to re-use these actions in their json, so we'll want one unified way of deserializing them. ## References * Done primarily as part of the work on #2046/#5400/#5674 * Also related: #1571/#5888 * Will aggressively conflict with any open PRs that introduced keybindings (looking at #6299) ## PR Checklist * [x] Closes nothing, this is code refactoring * [x] I work here * [x] Current tests passed * [n/a] Requires documentation to be updated
1 parent 5e2c4c6 commit 6d2fc5d

File tree

3 files changed

+218
-174
lines changed

3 files changed

+218
-174
lines changed
Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,212 @@
11
#include "pch.h"
2+
#include "ActionArgs.h"
23
#include "ActionAndArgs.h"
34
#include "ActionAndArgs.g.cpp"
45

5-
// We define everything necessary for the ActionAndArgs class in the header, but
6-
// we still need this file to compile the ActionAndArgs.g.cpp file, and we can't
7-
// just include that file in the header.
6+
static constexpr std::string_view ActionKey{ "action" };
7+
8+
// This key is reserved to remove a keybinding, instead of mapping it to an action.
9+
static constexpr std::string_view UnboundKey{ "unbound" };
10+
11+
static constexpr std::string_view CopyTextKey{ "copy" };
12+
static constexpr std::string_view PasteTextKey{ "paste" };
13+
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
14+
static constexpr std::string_view DuplicateTabKey{ "duplicateTab" };
15+
static constexpr std::string_view NewTabKey{ "newTab" };
16+
static constexpr std::string_view NewWindowKey{ "newWindow" };
17+
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
18+
static constexpr std::string_view CloseTabKey{ "closeTab" };
19+
static constexpr std::string_view ClosePaneKey{ "closePane" };
20+
static constexpr std::string_view SwitchtoTabKey{ "switchToTab" };
21+
static constexpr std::string_view NextTabKey{ "nextTab" };
22+
static constexpr std::string_view PrevTabKey{ "prevTab" };
23+
static constexpr std::string_view AdjustFontSizeKey{ "adjustFontSize" };
24+
static constexpr std::string_view ResetFontSizeKey{ "resetFontSize" };
25+
static constexpr std::string_view ScrollupKey{ "scrollUp" };
26+
static constexpr std::string_view ScrolldownKey{ "scrollDown" };
27+
static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" };
28+
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
29+
static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
30+
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings
31+
static constexpr std::string_view SplitPaneKey{ "splitPane" };
32+
static constexpr std::string_view ResizePaneKey{ "resizePane" };
33+
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
34+
static constexpr std::string_view FindKey{ "find" };
35+
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
836

937
namespace winrt::TerminalApp::implementation
1038
{
39+
// Specifically use a map here over an unordered_map. We want to be able to
40+
// iterate over these entries in-order when we're serializing the keybindings.
41+
// HERE BE DRAGONS:
42+
// These are string_views that are being used as keys. These string_views are
43+
// just pointers to other strings. This could be dangerous, if the map outlived
44+
// the actual strings being pointed to. However, since both these strings and
45+
// the map are all const for the lifetime of the app, we have nothing to worry
46+
// about here.
47+
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionNamesMap{
48+
{ CopyTextKey, ShortcutAction::CopyText },
49+
{ PasteTextKey, ShortcutAction::PasteText },
50+
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
51+
{ DuplicateTabKey, ShortcutAction::DuplicateTab },
52+
{ NewTabKey, ShortcutAction::NewTab },
53+
{ NewWindowKey, ShortcutAction::NewWindow },
54+
{ CloseWindowKey, ShortcutAction::CloseWindow },
55+
{ CloseTabKey, ShortcutAction::CloseTab },
56+
{ ClosePaneKey, ShortcutAction::ClosePane },
57+
{ NextTabKey, ShortcutAction::NextTab },
58+
{ PrevTabKey, ShortcutAction::PrevTab },
59+
{ AdjustFontSizeKey, ShortcutAction::AdjustFontSize },
60+
{ ResetFontSizeKey, ShortcutAction::ResetFontSize },
61+
{ ScrollupKey, ShortcutAction::ScrollUp },
62+
{ ScrolldownKey, ShortcutAction::ScrollDown },
63+
{ ScrolluppageKey, ShortcutAction::ScrollUpPage },
64+
{ ScrolldownpageKey, ShortcutAction::ScrollDownPage },
65+
{ SwitchToTabKey, ShortcutAction::SwitchToTab },
66+
{ ResizePaneKey, ShortcutAction::ResizePane },
67+
{ MoveFocusKey, ShortcutAction::MoveFocus },
68+
{ OpenSettingsKey, ShortcutAction::OpenSettings },
69+
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
70+
{ SplitPaneKey, ShortcutAction::SplitPane },
71+
{ UnboundKey, ShortcutAction::Invalid },
72+
{ FindKey, ShortcutAction::Find }
73+
};
74+
75+
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
76+
using ParseActionFunction = std::function<ParseResult(const Json::Value&)>;
77+
78+
// This is a map of ShortcutAction->function<IActionArgs(Json::Value)>. It holds
79+
// a set of deserializer functions that can be used to deserialize a IActionArgs
80+
// from json. Each type of IActionArgs that can accept arbitrary args should be
81+
// placed into this map, with the corresponding deserializer function as the
82+
// value.
83+
static const std::map<ShortcutAction, ParseActionFunction, std::less<>> argParsers{
84+
{ ShortcutAction::CopyText, winrt::TerminalApp::implementation::CopyTextArgs::FromJson },
85+
86+
{ ShortcutAction::NewTab, winrt::TerminalApp::implementation::NewTabArgs::FromJson },
87+
88+
{ ShortcutAction::SwitchToTab, winrt::TerminalApp::implementation::SwitchToTabArgs::FromJson },
89+
90+
{ ShortcutAction::ResizePane, winrt::TerminalApp::implementation::ResizePaneArgs::FromJson },
91+
92+
{ ShortcutAction::MoveFocus, winrt::TerminalApp::implementation::MoveFocusArgs::FromJson },
93+
94+
{ ShortcutAction::AdjustFontSize, winrt::TerminalApp::implementation::AdjustFontSizeArgs::FromJson },
95+
96+
{ ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson },
97+
98+
{ ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson },
99+
100+
{ ShortcutAction::Invalid, nullptr },
101+
};
102+
103+
// Function Description:
104+
// - Attempts to match a string to a ShortcutAction. If there's no match, then
105+
// returns ShortcutAction::Invalid
106+
// Arguments:
107+
// - actionString: the string to match to a ShortcutAction
108+
// Return Value:
109+
// - The ShortcutAction corresponding to the given string, if a match exists.
110+
static ShortcutAction GetActionFromString(const std::string_view actionString)
111+
{
112+
// Try matching the command to one we have. If we can't find the
113+
// action name in our list of names, let's just unbind that key.
114+
const auto found = ActionAndArgs::ActionNamesMap.find(actionString);
115+
return found != ActionAndArgs::ActionNamesMap.end() ? found->second : ShortcutAction::Invalid;
116+
}
117+
118+
// Method Description:
119+
// - Deserialize an ActionAndArgs from the provided json object or string `json`.
120+
// * If json is a string, we'll attempt to treat it as an action name,
121+
// without arguments.
122+
// * If json is an object, we'll attempt to retrieve the action name from
123+
// its "action" property, and we'll use that name to fine a deserializer
124+
// to precess the rest of the arguments in the json object.
125+
// - If the action name is null or "unbound", or we don't understand the
126+
// action name, or we failed to parse the arguments to this action, we'll
127+
// return null. This should indicate to the caller that the action should
128+
// be unbound.
129+
// - If there were any warnings while parsing arguments for the action,
130+
// they'll be appended to the warnings parameter.
131+
// Arguments:
132+
// - json: The Json::Value to attempt to parse as an ActionAndArgs
133+
// - warnings: If there were any warnings during parsing, they'll be
134+
// appended to this vector.
135+
// Return Value:
136+
// - a deserialized ActionAndArgs corresponding to the values in json, or
137+
// null if we failed to deserialize an action.
138+
winrt::com_ptr<ActionAndArgs> ActionAndArgs::FromJson(const Json::Value& json,
139+
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
140+
{
141+
// Invalid is our placeholder that the action was not parsed.
142+
ShortcutAction action = ShortcutAction::Invalid;
143+
144+
// Actions can be serialized in two styles:
145+
// "action": "switchToTab0",
146+
// "action": { "action": "switchToTab", "index": 0 },
147+
// NOTE: For keybindings, the "action" param is actually "command"
148+
149+
// 1. In the first case, the json is a string, that's the
150+
// action name. There are no provided args, so we'll pass
151+
// Json::Value::null to the parse function.
152+
// 2. In the second case, the json is an object. We'll use the
153+
// "action" in that object as the action name. We'll then pass
154+
// the json object to the arg parser, for further parsing.
155+
156+
auto argsVal = Json::Value::null;
157+
158+
// Only try to parse the action if it's actually a string value.
159+
// `null` will not pass this check.
160+
if (json.isString())
161+
{
162+
auto commandString = json.asString();
163+
action = GetActionFromString(commandString);
164+
}
165+
else if (json.isObject())
166+
{
167+
const auto actionVal = json[JsonKey(ActionKey)];
168+
if (actionVal.isString())
169+
{
170+
auto actionString = actionVal.asString();
171+
action = GetActionFromString(actionString);
172+
argsVal = json;
173+
}
174+
}
175+
176+
// Some keybindings can accept other arbitrary arguments. If it
177+
// does, we'll try to deserialize any "args" that were provided with
178+
// the binding.
179+
IActionArgs args{ nullptr };
180+
std::vector<::TerminalApp::SettingsLoadWarnings> parseWarnings;
181+
const auto deserializersIter = argParsers.find(action);
182+
if (deserializersIter != argParsers.end())
183+
{
184+
auto pfn = deserializersIter->second;
185+
if (pfn)
186+
{
187+
std::tie(args, parseWarnings) = pfn(argsVal);
188+
}
189+
warnings.insert(warnings.end(), parseWarnings.begin(), parseWarnings.end());
190+
191+
// if an arg parser was registered, but failed, bail
192+
if (pfn && args == nullptr)
193+
{
194+
return nullptr;
195+
}
196+
}
197+
198+
if (action != ShortcutAction::Invalid)
199+
{
200+
auto actionAndArgs = winrt::make_self<ActionAndArgs>();
201+
actionAndArgs->Action(action);
202+
actionAndArgs->Args(args);
203+
204+
return actionAndArgs;
205+
}
206+
else
207+
{
208+
return nullptr;
209+
}
210+
}
211+
11212
}

src/cascadia/TerminalApp/ActionAndArgs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
#pragma once
22
#include "ActionAndArgs.g.h"
3+
#include "TerminalWarnings.h"
34
#include "..\inc\cppwinrt_utils.h"
45

56
namespace winrt::TerminalApp::implementation
67
{
78
struct ActionAndArgs : public ActionAndArgsT<ActionAndArgs>
89
{
10+
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionNamesMap;
11+
static winrt::com_ptr<ActionAndArgs> FromJson(const Json::Value& json,
12+
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
13+
914
ActionAndArgs() = default;
1015
GETSET_PROPERTY(TerminalApp::ShortcutAction, Action, TerminalApp::ShortcutAction::Invalid);
1116
GETSET_PROPERTY(IActionArgs, Args, nullptr);

0 commit comments

Comments
 (0)