diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 5608be20b54..8ab581594e0 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -629,7 +629,8 @@ "folder", "separator", "remainingProfiles", - "matchProfiles" + "matchProfiles", + "action" ] }, "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": { "oneOf": [ { @@ -2054,6 +2077,9 @@ }, { "$ref": "#/$defs/RemainingProfilesEntry" + }, + { + "$ref": "#/$defs/ActionEntry" } ] } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 039b99390dd..7980f0cda4c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1004,6 +1004,18 @@ namespace winrt::TerminalApp::implementation items.push_back(profileItem); break; } + case NewTabMenuEntryType::Action: + { + const auto actionEntry = entry.as(); + 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; } + // 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: // - Helper method to create an IconElement that can be passed to MenuFlyoutItems and // MenuFlyoutSubItems diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 2c2ff206579..7095dbaf290 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -300,6 +300,7 @@ namespace winrt::TerminalApp::implementation std::vector _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector entries); 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 _CreateNewTabFlyoutAction(const winrt::hstring& actionId); void _OpenNewTabDropdown(); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs); diff --git a/src/cascadia/TerminalSettingsModel/ActionEntry.cpp b/src/cascadia/TerminalSettingsModel/ActionEntry.cpp new file mode 100644 index 00000000000..b48207e74e9 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ActionEntry.cpp @@ -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(NewTabMenuEntryType::Action) +{ +} + +Json::Value ActionEntry::ToJson() const +{ + auto json = NewTabMenuEntry::ToJson(); + + JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId); + + return json; +} + +winrt::com_ptr ActionEntry::FromJson(const Json::Value& json) +{ + auto entry = winrt::make_self(); + + JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId); + + return entry; +} diff --git a/src/cascadia/TerminalSettingsModel/ActionEntry.h b/src/cascadia/TerminalSettingsModel/ActionEntry.h new file mode 100644 index 00000000000..9c89077828c --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ActionEntry.h @@ -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 + { + public: + ActionEntry() noexcept; + + Json::Value ToJson() const override; + static com_ptr FromJson(const Json::Value& json); + + WINRT_PROPERTY(winrt::hstring, ActionId); + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation +{ + BASIC_FACTORY(ActionEntry); +} diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 0110839fe19..4468014c5a8 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -596,6 +596,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return _GetActionByKeyChordInternal(keys).value_or(nullptr); } + Model::Command ActionMap::GetActionByID(const winrt::hstring& cmdID) const + { + return _GetActionByID(cmdID); + } + // Method Description: // - Retrieves the assigned command ID with the given key chord. // - Can return nullopt to differentiate explicit unbinding vs lack of binding. diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 3810e55f1f0..2b815214dcb 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -60,6 +60,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // queries Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const; + Model::Command GetActionByID(const winrt::hstring& cmdID) const; bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const; Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID); diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index 105f00715f5..594872f6b70 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -11,7 +11,7 @@ namespace Microsoft.Terminal.Settings.Model Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys); Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys); - + Command GetActionByID(String cmdID); Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID); Windows.Foundation.Collections.IMapView AvailableActions { get; }; diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 31e4844eb00..c62763464b0 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -28,6 +28,9 @@ NewTabMenuEntry.idl + + NewTabMenuEntry.idl + NewTabMenuEntry.idl @@ -185,6 +188,9 @@ NewTabMenuEntry.idl + + NewTabMenuEntry.idl + NewTabMenuEntry.idl diff --git a/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.cpp b/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.cpp index e346d9711c1..c4762067ed7 100644 --- a/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.cpp +++ b/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.cpp @@ -8,6 +8,7 @@ #include "SeparatorEntry.h" #include "FolderEntry.h" #include "ProfileEntry.h" +#include "ActionEntry.h" #include "RemainingProfilesEntry.h" #include "MatchProfilesEntry.h" @@ -52,6 +53,8 @@ winrt::com_ptr NewTabMenuEntry::FromJson(const Json::Value& jso return RemainingProfilesEntry::FromJson(json); case NewTabMenuEntryType::MatchProfiles: return MatchProfilesEntry::FromJson(json); + case NewTabMenuEntryType::Action: + return ActionEntry::FromJson(json); default: return nullptr; } diff --git a/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl b/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl index 64a3b1fdd94..84bc3777695 100644 --- a/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl +++ b/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl @@ -12,7 +12,8 @@ namespace Microsoft.Terminal.Settings.Model Separator, Folder, RemainingProfiles, - MatchProfiles + MatchProfiles, + Action }; [default_interface] unsealed runtimeclass NewTabMenuEntry @@ -34,6 +35,13 @@ namespace Microsoft.Terminal.Settings.Model Int32 ProfileIndex; } + [default_interface] runtimeclass ActionEntry : NewTabMenuEntry + { + ActionEntry(); + + String ActionId; + } + enum FolderEntryInlining { Never = 0, diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index d15c65c0fab..da06d0cfba3 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -678,8 +678,9 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection) // Possible NewTabMenuEntryType values JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType) { - JSON_MAPPINGS(5) = { + JSON_MAPPINGS(6) = { pair_type{ "profile", ValueType::Profile }, + pair_type{ "action", ValueType::Action }, pair_type{ "separator", ValueType::Separator }, pair_type{ "folder", ValueType::Folder }, pair_type{ "remainingProfiles", ValueType::RemainingProfiles }, diff --git a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj index 06e26727ed0..be39d71381f 100644 --- a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj +++ b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj @@ -38,6 +38,9 @@ + + ../NewTabMenuEntry.h + ../NewTabMenuEntry.h