mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-17 15:36:35 +00:00
start working on nested actions
This commit is contained in:
@@ -85,6 +85,10 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(TestIterateCommands);
|
||||
TEST_METHOD(TestIterateAutogenNamedCommands);
|
||||
TEST_METHOD(TestIterateOnBadJson);
|
||||
TEST_METHOD(TestNestedCommands);
|
||||
TEST_METHOD(TestNestedInIterableCommand);
|
||||
TEST_METHOD(TestIterableInNestedCommand);
|
||||
TEST_METHOD(TestNestedCommandWithoutName);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
@@ -2927,4 +2931,50 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::TestNestedCommands()
|
||||
{
|
||||
// This test
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
}
|
||||
void SettingsTests::TestNestedInIterableCommand()
|
||||
{
|
||||
// This test checks a iterable command that includes a nested command.
|
||||
// The commands should look like:
|
||||
//
|
||||
// <Command Palette>
|
||||
// └─ New Pane...
|
||||
// ├─ Profile 1...
|
||||
// | ├─ Split Automatically
|
||||
// | ├─ Split Vertically
|
||||
// | └─ Split Horizontally
|
||||
// ├─ Profile 2...
|
||||
// | ├─ Split Automatically
|
||||
// | ├─ Split Vertically
|
||||
// | └─ Split Horizontally
|
||||
// └─ Profile 3...
|
||||
// ├─ Split Automatically
|
||||
// ├─ Split Vertically
|
||||
// └─ Split Horizontally
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
}
|
||||
void SettingsTests::TestIterableInNestedCommand()
|
||||
{
|
||||
// This test checks a nested command that includes an iterable command.
|
||||
// The commands should look like:
|
||||
//
|
||||
// <Command Palette>
|
||||
// └─ New Tab With Profile...
|
||||
// ├─ Profile 1
|
||||
// ├─ Profile 2
|
||||
// └─ Profile 3
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
}
|
||||
void SettingsTests::TestNestedCommandWithoutName()
|
||||
{
|
||||
// This test tests a nested command without a name specified. This type
|
||||
// of command should just be ignored, since we can't auto-generate names
|
||||
// for nested commands, they _must_ have names specified.
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IconPathKey{ "iconPath" };
|
||||
static constexpr std::string_view ActionKey{ "command" };
|
||||
static constexpr std::string_view ArgsKey{ "args" };
|
||||
|
||||
static constexpr std::string_view IterateOnKey{ "iterateOn" };
|
||||
static constexpr std::string_view CommandsKey{ "commands" };
|
||||
|
||||
static constexpr std::string_view IterateOnProfilesValue{ "profiles" };
|
||||
|
||||
@@ -25,6 +26,16 @@ static constexpr std::wstring_view ProfileName{ L"${profile.name}" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
Command::Command()
|
||||
{
|
||||
_nestedCommandsView = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
|
||||
}
|
||||
|
||||
Collections::IObservableVector<winrt::TerminalApp::Command> Command::NestedCommands()
|
||||
{
|
||||
return _nestedCommandsView;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - attempt to get the name of this command from the provided json object.
|
||||
// * If the "name" property is a string, return that value.
|
||||
@@ -108,51 +119,95 @@ namespace winrt::TerminalApp::implementation
|
||||
// Return Value:
|
||||
// - the newly constructed Command object.
|
||||
winrt::com_ptr<Command> Command::FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings,
|
||||
const bool postExpansion)
|
||||
{
|
||||
auto result = winrt::make_self<Command>();
|
||||
|
||||
if (const auto iterateOnJson{ json[JsonKey(IterateOnKey)] })
|
||||
bool iterable = false;
|
||||
bool nested = false;
|
||||
if (!postExpansion)
|
||||
{
|
||||
auto s = iterateOnJson.asString();
|
||||
if (s == IterateOnProfilesValue)
|
||||
if (const auto iterateOnJson{ json[JsonKey(IterateOnKey)] })
|
||||
{
|
||||
result->_IterateOn = ExpandCommandType::Profiles;
|
||||
auto s = iterateOnJson.asString();
|
||||
if (s == IterateOnProfilesValue)
|
||||
{
|
||||
result->_IterateOn = ExpandCommandType::Profiles;
|
||||
iterable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For iterable commands, we'll make another pass at parsing them once
|
||||
// the json is patched. So ignore parsing sub-commands for now. Commands
|
||||
// will only be marked iterable on the first pass.
|
||||
if (!iterable)
|
||||
{
|
||||
if (const auto nestedCommandsJson{ json[JsonKey(CommandsKey)] })
|
||||
{
|
||||
auto nestedWarnings = Command::LayerJson(result->_subcommands, nestedCommandsJson);
|
||||
// It's possible that the nested commands have some warnings
|
||||
warnings.insert(warnings.end(), nestedWarnings.begin(), nestedWarnings.end());
|
||||
|
||||
// Add all the commands we've parsed to the observable vector we
|
||||
// have, so we can access them in XAML.
|
||||
for (auto& nameAndCommand : result->_subcommands)
|
||||
{
|
||||
auto command = nameAndCommand.second;
|
||||
result->_nestedCommandsView.Append(command);
|
||||
}
|
||||
nested = true;
|
||||
}
|
||||
|
||||
// TODO: else if (hasKey(CommandKey) )
|
||||
// {
|
||||
// // { name: "foo", commands: null } will land in this case, which
|
||||
// // should also be used for unbinding.
|
||||
// return nullptr;
|
||||
// }
|
||||
}
|
||||
|
||||
// TODO GH#6644: iconPath not implemented quite yet. Can't seem to get
|
||||
// the binding quite right. Additionally, do we want it to be an image,
|
||||
// or a FontIcon? I've had difficulty binding either/or.
|
||||
|
||||
if (const auto actionJson{ json[JsonKey(ActionKey)] })
|
||||
// If we're a nested command, we can ignore the current command.
|
||||
if (!nested)
|
||||
{
|
||||
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
|
||||
|
||||
if (actionAndArgs)
|
||||
if (const auto actionJson{ json[JsonKey(ActionKey)] })
|
||||
{
|
||||
result->_setAction(*actionAndArgs);
|
||||
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
|
||||
|
||||
if (actionAndArgs)
|
||||
{
|
||||
result->_setAction(*actionAndArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something like
|
||||
// { name: "foo", action: "unbound" }
|
||||
// will _remove_ the "foo" command, by returning null here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If an iterable command doesn't have a name set, we'll still just
|
||||
// try and generate a fake name for the command give the string we
|
||||
// currently have. It'll probably generate something like "New tab,
|
||||
// profile: ${profile.name}". This string will only be temporarily
|
||||
// used internally, so there's no problem.
|
||||
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something like
|
||||
// { name: "foo", action: "unbound" }
|
||||
// will _remove_ the "foo" command, by returning null here.
|
||||
// { name: "foo", action: null } will land in this case, which
|
||||
// should also be used for unbinding.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If an iterable command doesn't have a name set, we'll still just
|
||||
// try and generate a fake name for the command give the string we
|
||||
// currently have. It'll probably generate something like "New tab,
|
||||
// profile: ${profile.name}". This string will only be temporarily
|
||||
// used internally, so there's no problem.
|
||||
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// { name: "foo", action: null } will land in this case, which
|
||||
// should also be used for unbinding.
|
||||
return nullptr;
|
||||
result->_setName(_nameFromJson(json));
|
||||
}
|
||||
|
||||
// Stash the original json value in this object. If the command is
|
||||
@@ -332,7 +387,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
// Pass the new json back though FromJson, to get the new expanded value.
|
||||
if (auto newCmd{ Command::FromJson(newJsonValue, warnings) })
|
||||
if (auto newCmd{ Command::FromJson(newJsonValue, warnings, true) })
|
||||
{
|
||||
newCommands.push_back(*newCmd);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,11 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
struct Command : CommandT<Command>
|
||||
{
|
||||
Command() = default;
|
||||
Command();
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json, std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings,
|
||||
const bool postExpansion = false);
|
||||
|
||||
static std::vector<winrt::TerminalApp::Command> ExpandCommand(winrt::com_ptr<Command> expandable,
|
||||
const std::vector<::TerminalApp::Profile>& profiles,
|
||||
@@ -44,6 +46,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json);
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> NestedCommands();
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers);
|
||||
@@ -52,6 +56,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
private:
|
||||
Json::Value _originalJson;
|
||||
std::unordered_map<winrt::hstring, winrt::TerminalApp::Command> _subcommands;
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _nestedCommandsView{ nullptr };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,5 +12,7 @@ namespace TerminalApp
|
||||
String Name;
|
||||
ActionAndArgs Action;
|
||||
String KeyChordText;
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<Command> NestedCommands { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
|
||||
_allActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
_nestedActionStack = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
|
||||
if (CommandPaletteShadow())
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _nestedActionStack{ nullptr };
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
void _filterTextChanged(Windows::Foundation::IInspectable const& sender,
|
||||
|
||||
@@ -105,6 +105,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// unset by default, so we don't need to worry about clearing it
|
||||
// if there isn't a key associated with it.
|
||||
auto keyChord{ _settings->GetKeybindings().GetKeyBindingForActionWithArgs(command.Action()) };
|
||||
|
||||
// TODO: Make this recursive, because commands might have
|
||||
// subcommands that also have keybindings
|
||||
if (keyChord)
|
||||
{
|
||||
command.KeyChordText(KeyChordSerialization::ToString(keyChord));
|
||||
|
||||
Reference in New Issue
Block a user