Repository: microsoft/terminal (language: cpp)

Issue:
In newTabMenu allow assignment/override of the icon property for the action items
### Description of the new feature

Hi.

One small cosmetic suggestion. 

Currently, I can define actions->command which I can then select as the action in newTabMenu.
If I would like to have an icon (or glyph) for that item displayed in the newTabMenu, icon needs to be defined at the level of actions->command. That will result in icon being visible in the command palette as well, which I'd like to avoid (I don't like it visually to have only one or a few items with the icon in a long list of commands).

Would it be possible to have a possibility to define "icon" for newTabMenu action items ("type": "action"). That would allow assignment of desired icons/glyphs to desired commands only in newTabMenu and not in command palette.

Other option would be to have a property on the actions->command item level that would allow for the icon not to be displayed in the command palette (e.g. iconVisibility to have one of the following values: "newtabmenu", "commandpalette", "both", "none").

One more thing that could work is to allow the whole custom command to be excluded from the command palette.

Thank you in advance!

### Proposed technical implementation details

_No response_


Discussion / hints:
It should totally let you do that! This must have just been overlooked in the initial implementation. 

Something like:

```json
        {
            "type": "action",
            "id": "User.scrollToMark.D3F0B923",
            "icon": "d:\\dev\\public\\terminal\\res\\terminal.ico"
        }
```

should totally work. Thanks for filing!

(We should probably allow that for profile entries too)

Fix patch:
diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json
index ec903dc5b80..9774c51844c 100644
--- a/doc/cascadia/profiles.schema.json
+++ b/doc/cascadia/profiles.schema.json
@@ -733,6 +733,9 @@
               "type": "string",
               "default": "",
               "description": "The name or GUID of the profile to show in this entry"
+            },
+            "icon": {
+              "$ref": "#/$defs/Icon"
             }
           }
         }
@@ -804,6 +807,9 @@
               "type": "string",
               "default": "",
               "description": "The ID of the action to show in this entry"
+            },
+            "icon": {
+              "$ref": "#/$defs/Icon"
             }
           }
         }
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index b3111e9b639..99064fef8bb 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -989,7 +989,7 @@ namespace winrt::TerminalApp::implementation
 
                 for (auto&& [profileIndex, remainingProfile] : remainingProfilesEntry.Profiles())
                 {
-                    items.push_back(_CreateNewTabFlyoutProfile(remainingProfile, profileIndex));
+                    items.push_back(_CreateNewTabFlyoutProfile(remainingProfile, profileIndex, {}));
                 }
 
                 break;
@@ -1003,7 +1003,7 @@ namespace winrt::TerminalApp::implementation
                     break;
                 }
 
-                auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex());
+                auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex(), profileEntry.Icon());
                 items.push_back(profileItem);
                 break;
             }
@@ -1013,7 +1013,7 @@ namespace winrt::TerminalApp::implementation
                 const auto actionId = actionEntry.ActionId();
                 if (_settings.ActionMap().GetActionByID(actionId))
                 {
-                    auto actionItem = _CreateNewTabFlyoutAction(actionId);
+                    auto actionItem = _CreateNewTabFlyoutAction(actionId, actionEntry.Icon());
                     items.push_back(actionItem);
                 }
 
@@ -1028,7 +1028,7 @@ namespace winrt::TerminalApp::implementation
     // Method Description:
     // - This method creates a flyout menu item for a given profile with the given index.
     //   It makes sure to set the correct icon, keybinding, and click-action.
-    WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutProfile(const Profile profile, int profileIndex)
+    WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutProfile(const Profile profile, int profileIndex, const winrt::hstring& iconPathOverride)
     {
         auto profileMenuItem = WUX::Controls::MenuFlyoutItem{};
 
@@ -1049,9 +1049,10 @@ namespace winrt::TerminalApp::implementation
         auto profileName = profile.Name();
         profileMenuItem.Text(profileName);
 
-        // If there's an icon set for this profile, set it as the icon for
-        // this flyout item
-        const auto& iconPath = profile.EvaluatedIcon();
+        // If a custom icon path has been specified, set it as the icon for
+        // this flyout item. Otherwise, if an icon is set for this profile, set that icon
+        // for this flyout item.
+        const auto& iconPath = iconPathOverride.empty() ? profile.EvaluatedIcon() : iconPathOverride;
         if (!iconPath.empty())
         {
             const auto icon = _CreateNewTabFlyoutIcon(iconPath);
@@ -1112,7 +1113,7 @@ namespace winrt::TerminalApp::implementation
     // 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)
+    WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutAction(const winrt::hstring& actionId, const winrt::hstring& iconPathOverride)
     {
         auto actionMenuItem = WUX::Controls::MenuFlyoutItem{};
         const auto action{ _settings.ActionMap().GetActionByID(actionId) };
@@ -1125,9 +1126,10 @@ namespace winrt::TerminalApp::implementation
 
         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 a custom icon path has been specified, set it as the icon for
+        // this flyout item. Otherwise, if an icon is set for this action, set that icon
+        // for this flyout item.
+        const auto& iconPath = iconPathOverride.empty() ? action.IconPath() : iconPathOverride;
         if (!iconPath.empty())
         {
             const auto icon = _CreateNewTabFlyoutIcon(iconPath);
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index 9e5d3029c7f..30627d5b7ee 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -316,8 +316,8 @@ namespace winrt::TerminalApp::implementation
         void _CreateNewTabFlyout();
         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::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
-        winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutAction(const winrt::hstring& actionId);
+        winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex, const winrt::hstring& iconPathOverride);
+        winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutAction(const winrt::hstring& actionId, const winrt::hstring& iconPathOverride);
 
         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
index b48207e74e9..6f7773e715c 100644
--- a/src/cascadia/TerminalSettingsModel/ActionEntry.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionEntry.cpp
@@ -11,6 +11,7 @@ using namespace Microsoft::Terminal::Settings::Model;
 using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
 
 static constexpr std::string_view ActionIdKey{ "id" };
+static constexpr std::string_view IconKey{ "icon" };
 
 ActionEntry::ActionEntry() noexcept :
     ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
@@ -22,6 +23,7 @@ Json::Value ActionEntry::ToJson() const
     auto json = NewTabMenuEntry::ToJson();
 
     JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
+    JsonUtils::SetValueForKey(json, IconKey, _Icon);
 
     return json;
 }
@@ -31,6 +33,7 @@ winrt::com_ptr<NewTabMenuEntry> ActionEntry::FromJson(const Json::Value& json)
     auto entry = winrt::make_self<ActionEntry>();
 
     JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
+    JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
 
     return entry;
 }
diff --git a/src/cascadia/TerminalSettingsModel/ActionEntry.h b/src/cascadia/TerminalSettingsModel/ActionEntry.h
index 9c89077828c..711cec13bfc 100644
--- a/src/cascadia/TerminalSettingsModel/ActionEntry.h
+++ b/src/cascadia/TerminalSettingsModel/ActionEntry.h
@@ -28,6 +28,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
 
         WINRT_PROPERTY(winrt::hstring, ActionId);
+        WINRT_PROPERTY(winrt::hstring, Icon);
     };
 }
 
diff --git a/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl b/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl
index 84bc3777695..acf1f33f6f4 100644
--- a/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl
+++ b/src/cascadia/TerminalSettingsModel/NewTabMenuEntry.idl
@@ -33,6 +33,7 @@ namespace Microsoft.Terminal.Settings.Model
 
         Profile Profile;
         Int32 ProfileIndex;
+        String Icon;
     }
 
     [default_interface] runtimeclass ActionEntry : NewTabMenuEntry
@@ -40,6 +41,7 @@ namespace Microsoft.Terminal.Settings.Model
         ActionEntry();
 
         String ActionId;
+        String Icon;
     }
 
     enum FolderEntryInlining
diff --git a/src/cascadia/TerminalSettingsModel/ProfileEntry.cpp b/src/cascadia/TerminalSettingsModel/ProfileEntry.cpp
index c857eea8678..5f01a293beb 100644
--- a/src/cascadia/TerminalSettingsModel/ProfileEntry.cpp
+++ b/src/cascadia/TerminalSettingsModel/ProfileEntry.cpp
@@ -11,6 +11,7 @@ using namespace Microsoft::Terminal::Settings::Model;
 using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
 
 static constexpr std::string_view ProfileKey{ "profile" };
+static constexpr std::string_view IconKey{ "icon" };
 
 ProfileEntry::ProfileEntry() noexcept :
     ProfileEntry{ winrt::hstring{} }
@@ -46,6 +47,8 @@ Json::Value ProfileEntry::ToJson() const
         JsonUtils::SetValueForKey(json, ProfileKey, _Profile.Guid());
     }
 
+    JsonUtils::SetValueForKey(json, IconKey, _Icon);
+
     return json;
 }
 
@@ -54,6 +57,7 @@ winrt::com_ptr<NewTabMenuEntry> ProfileEntry::FromJson(const Json::Value& json)
     auto entry = winrt::make_self<ProfileEntry>();
 
     JsonUtils::GetValueForKey(json, ProfileKey, entry->_ProfileName);
+    JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
 
     return entry;
 }
diff --git a/src/cascadia/TerminalSettingsModel/ProfileEntry.h b/src/cascadia/TerminalSettingsModel/ProfileEntry.h
index fe0f4573d29..9ea78497f1f 100644
--- a/src/cascadia/TerminalSettingsModel/ProfileEntry.h
+++ b/src/cascadia/TerminalSettingsModel/ProfileEntry.h
@@ -41,6 +41,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
 
         WINRT_PROPERTY(Model::Profile, Profile);
         WINRT_PROPERTY(int, ProfileIndex);
+        WINRT_PROPERTY(winrt::hstring, Icon);
 
     private:
         winrt::hstring _ProfileName;


Test patch: