Repository: microsoft/terminal (language: cpp)

Issue:
Make the Settings drop down able to switch back to the json
<!-- 
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨

I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.

All good? Then proceed!
-->

# Description of the new feature/enhancement
It appears the settings.json has become unbound and using the previous keybinding for it still opens the Settings UI. I can add a custom keybinding for the settings.json but I usually am lazy and simply click on the Settings drop down button as it's quicker and easier for me.  So I'd like the ability to switch out the default experience for the Settings drop down. 
<!-- 
A clear and concise description of what the problem is that the new feature would solve.
Describe why and how a user would use this new functionality (if applicable).
-->

# Proposed technical implementation details (optional)
Add a global setting that allows the user to switch out the default experience. This should be able to be set from both the Settings UI and the settings.json.
<!-- 
A clear and concise description of what you want to happen.
-->



Discussion / hints:
I don't hate this idea. @zadjii-msft -- i'd rather make this part of the "edit the menu to put ANY ACTION in it" arc though. WDYT?
Yea, we should unify this with that effort. The spec for #1571 originally just had "we're gonna force [Settings, Feedback, About]" into the dropdown always. But now there are multiple possible "Settings" entries that make sense, so maybe those should be customizable too. Hmm. That might require a bit more noodling on, and an addendum to [#1571 - New Tab Menu Customization.md](https://github.com/microsoft/terminal/blob/main/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization/%231571%20-%20New%20Tab%20Menu%20Customization.md)
I agree that it should be considered part of that effort. I was just hoping to have something possibly in place for 1.8 and wasn't sure if a slightly different approach could be used while waiting for the New Tab Menu Customization. Alternatively, that would make a great 1.8 feature if possible (I'm sure everyone who uses custom keybindings and is told by the Settings UI to use the json would appreciate it too due to the format changes the UI does to the json).

Fix patch:
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<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;
     }
 
+    // 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<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);
 
         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<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;
+}
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<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);
+}
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<String, ActionAndArgs> 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 @@
     <ClInclude Include="SeparatorEntry.h">
       <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
     </ClInclude>
+    <ClInclude Include="ActionEntry.h">
+      <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
+    </ClInclude>
     <ClInclude Include="FolderEntry.h">
       <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
     </ClInclude>
@@ -185,6 +188,9 @@
     <ClCompile Include="SeparatorEntry.cpp">
       <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
     </ClCompile>
+    <ClCompile Include="ActionEntry.cpp">
+      <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
+    </ClCompile>
     <ClCompile Include="FolderEntry.cpp">
       <DependentUpon>NewTabMenuEntry.idl</DependentUpon>
     </ClCompile>
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> 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 @@
     <ClInclude Include="../Profile.h" />
     <ClInclude Include="../TerminalWarnings.h" />
     <ClInclude Include="../NewTabMenuEntry.h" />
+    <ClInclude Include="../ActionEntry.h">
+      <DependentUpon>../NewTabMenuEntry.h</DependentUpon>
+    </ClInclude>
     <ClInclude Include="../SeparatorEntry.h">
       <DependentUpon>../NewTabMenuEntry.h</DependentUpon>
     </ClInclude>


Test patch: