Allow actions in the new tab dropdown (#17281)

Allows the user to define entries in the new tab menu that execute
actions, based on their action Id

Closes #3759
Closes #9362
This commit is contained in:
PankajBhojwani 2024-06-06 15:17:18 -07:00 committed by GitHub
parent d6b6aacb4f
commit aeed0782bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 178 additions and 4 deletions

View File

@ -629,7 +629,8 @@
"folder", "folder",
"separator", "separator",
"remainingProfiles", "remainingProfiles",
"matchProfiles" "matchProfiles",
"action"
] ]
}, },
"NewTabMenuEntry": { "NewTabMenuEntry": {
@ -781,6 +782,28 @@
} }
] ]
}, },
"ActionEntry": {
"description": "An action in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "action"
},
"id": {
"type": "string",
"default": "",
"description": "The ID of the action to show in this entry"
}
}
}
]
},
"SwitchToAdjacentTabArgs": { "SwitchToAdjacentTabArgs": {
"oneOf": [ "oneOf": [
{ {
@ -2054,6 +2077,9 @@
}, },
{ {
"$ref": "#/$defs/RemainingProfilesEntry" "$ref": "#/$defs/RemainingProfilesEntry"
},
{
"$ref": "#/$defs/ActionEntry"
} }
] ]
} }

View File

@ -1004,6 +1004,18 @@ namespace winrt::TerminalApp::implementation
items.push_back(profileItem); items.push_back(profileItem);
break; break;
} }
case NewTabMenuEntryType::Action:
{
const auto actionEntry = entry.as<ActionEntry>();
const auto actionId = actionEntry.ActionId();
if (_settings.ActionMap().GetActionByID(actionId))
{
auto actionItem = _CreateNewTabFlyoutAction(actionId);
items.push_back(actionItem);
}
break;
}
} }
} }
@ -1094,6 +1106,41 @@ namespace winrt::TerminalApp::implementation
return profileMenuItem; return profileMenuItem;
} }
// Method Description:
// - This method creates a flyout menu item for a given action
// It makes sure to set the correct icon, keybinding, and click-action.
WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutAction(const winrt::hstring& actionId)
{
auto actionMenuItem = WUX::Controls::MenuFlyoutItem{};
const auto action{ _settings.ActionMap().GetActionByID(actionId) };
const auto actionKeyChord{ _settings.ActionMap().GetKeyBindingForAction(actionId) };
if (actionKeyChord)
{
_SetAcceleratorForMenuItem(actionMenuItem, actionKeyChord);
}
actionMenuItem.Text(action.Name());
// If there's an icon set for this action, set it as the icon for
// this flyout item
const auto& iconPath = action.IconPath();
if (!iconPath.empty())
{
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
actionMenuItem.Icon(icon);
}
actionMenuItem.Click([action, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_actionDispatch->DoAction(action.ActionAndArgs());
}
});
return actionMenuItem;
}
// Method Description: // Method Description:
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and // - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
// MenuFlyoutSubItems // MenuFlyoutSubItems

View File

@ -300,6 +300,7 @@ namespace winrt::TerminalApp::implementation
std::vector<winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase> _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::NewTabMenuEntry> entries); std::vector<winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase> _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::NewTabMenuEntry> entries);
winrt::Windows::UI::Xaml::Controls::IconElement _CreateNewTabFlyoutIcon(const winrt::hstring& icon); winrt::Windows::UI::Xaml::Controls::IconElement _CreateNewTabFlyoutIcon(const winrt::hstring& icon);
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex); winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutAction(const winrt::hstring& actionId);
void _OpenNewTabDropdown(); void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs);

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ActionEntry.h"
#include "JsonUtils.h"
#include "ActionEntry.g.cpp"
using namespace Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
static constexpr std::string_view ActionIdKey{ "id" };
ActionEntry::ActionEntry() noexcept :
ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
{
}
Json::Value ActionEntry::ToJson() const
{
auto json = NewTabMenuEntry::ToJson();
JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
return json;
}
winrt::com_ptr<NewTabMenuEntry> ActionEntry::FromJson(const Json::Value& json)
{
auto entry = winrt::make_self<ActionEntry>();
JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
return entry;
}

View File

@ -0,0 +1,37 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- ActionEntry.h
Abstract:
- An action entry in the "new tab" dropdown menu
Author(s):
- Pankaj Bhojwani - May 2024
--*/
#pragma once
#include "NewTabMenuEntry.h"
#include "ActionEntry.g.h"
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
struct ActionEntry : ActionEntryT<ActionEntry, NewTabMenuEntry>
{
public:
ActionEntry() noexcept;
Json::Value ToJson() const override;
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
WINRT_PROPERTY(winrt::hstring, ActionId);
};
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
{
BASIC_FACTORY(ActionEntry);
}

View File

@ -596,6 +596,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return _GetActionByKeyChordInternal(keys).value_or(nullptr); return _GetActionByKeyChordInternal(keys).value_or(nullptr);
} }
Model::Command ActionMap::GetActionByID(const winrt::hstring& cmdID) const
{
return _GetActionByID(cmdID);
}
// Method Description: // Method Description:
// - Retrieves the assigned command ID with the given key chord. // - Retrieves the assigned command ID with the given key chord.
// - Can return nullopt to differentiate explicit unbinding vs lack of binding. // - Can return nullopt to differentiate explicit unbinding vs lack of binding.

View File

@ -60,6 +60,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// queries // queries
Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const; Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const;
Model::Command GetActionByID(const winrt::hstring& cmdID) const;
bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const; bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const;
Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID); Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID);

View File

@ -11,7 +11,7 @@ namespace Microsoft.Terminal.Settings.Model
Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys); Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys);
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys); Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
Command GetActionByID(String cmdID);
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID); Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID);
Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; }; Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; };

View File

@ -28,6 +28,9 @@
<ClInclude Include="SeparatorEntry.h"> <ClInclude Include="SeparatorEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon> <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude> </ClInclude>
<ClInclude Include="ActionEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude>
<ClInclude Include="FolderEntry.h"> <ClInclude Include="FolderEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon> <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude> </ClInclude>
@ -185,6 +188,9 @@
<ClCompile Include="SeparatorEntry.cpp"> <ClCompile Include="SeparatorEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon> <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile> </ClCompile>
<ClCompile Include="ActionEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile>
<ClCompile Include="FolderEntry.cpp"> <ClCompile Include="FolderEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon> <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile> </ClCompile>

View File

@ -8,6 +8,7 @@
#include "SeparatorEntry.h" #include "SeparatorEntry.h"
#include "FolderEntry.h" #include "FolderEntry.h"
#include "ProfileEntry.h" #include "ProfileEntry.h"
#include "ActionEntry.h"
#include "RemainingProfilesEntry.h" #include "RemainingProfilesEntry.h"
#include "MatchProfilesEntry.h" #include "MatchProfilesEntry.h"
@ -52,6 +53,8 @@ winrt::com_ptr<NewTabMenuEntry> NewTabMenuEntry::FromJson(const Json::Value& jso
return RemainingProfilesEntry::FromJson(json); return RemainingProfilesEntry::FromJson(json);
case NewTabMenuEntryType::MatchProfiles: case NewTabMenuEntryType::MatchProfiles:
return MatchProfilesEntry::FromJson(json); return MatchProfilesEntry::FromJson(json);
case NewTabMenuEntryType::Action:
return ActionEntry::FromJson(json);
default: default:
return nullptr; return nullptr;
} }

View File

@ -12,7 +12,8 @@ namespace Microsoft.Terminal.Settings.Model
Separator, Separator,
Folder, Folder,
RemainingProfiles, RemainingProfiles,
MatchProfiles MatchProfiles,
Action
}; };
[default_interface] unsealed runtimeclass NewTabMenuEntry [default_interface] unsealed runtimeclass NewTabMenuEntry
@ -34,6 +35,13 @@ namespace Microsoft.Terminal.Settings.Model
Int32 ProfileIndex; Int32 ProfileIndex;
} }
[default_interface] runtimeclass ActionEntry : NewTabMenuEntry
{
ActionEntry();
String ActionId;
}
enum FolderEntryInlining enum FolderEntryInlining
{ {
Never = 0, Never = 0,

View File

@ -678,8 +678,9 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection)
// Possible NewTabMenuEntryType values // Possible NewTabMenuEntryType values
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType) JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType)
{ {
JSON_MAPPINGS(5) = { JSON_MAPPINGS(6) = {
pair_type{ "profile", ValueType::Profile }, pair_type{ "profile", ValueType::Profile },
pair_type{ "action", ValueType::Action },
pair_type{ "separator", ValueType::Separator }, pair_type{ "separator", ValueType::Separator },
pair_type{ "folder", ValueType::Folder }, pair_type{ "folder", ValueType::Folder },
pair_type{ "remainingProfiles", ValueType::RemainingProfiles }, pair_type{ "remainingProfiles", ValueType::RemainingProfiles },

View File

@ -38,6 +38,9 @@
<ClInclude Include="../Profile.h" /> <ClInclude Include="../Profile.h" />
<ClInclude Include="../TerminalWarnings.h" /> <ClInclude Include="../TerminalWarnings.h" />
<ClInclude Include="../NewTabMenuEntry.h" /> <ClInclude Include="../NewTabMenuEntry.h" />
<ClInclude Include="../ActionEntry.h">
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
</ClInclude>
<ClInclude Include="../SeparatorEntry.h"> <ClInclude Include="../SeparatorEntry.h">
<DependentUpon>../NewTabMenuEntry.h</DependentUpon> <DependentUpon>../NewTabMenuEntry.h</DependentUpon>
</ClInclude> </ClInclude>