Repository: microsoft/terminal (language: cpp)

Issue:
Backspaces we insert into a command for a `useCommandline` suggestion show up in the suggestions UI
v1.19 

1. add an action like
   ```json
        {
            "command": 
            {
                "action": "sendInput",
                "input": "git status"
            },
        },
   ```
2. Enable shell integration
3. type `git` at the prompt 
4. Do a `showSuggestions("source": [ "tasks", "commandHistory" ], "useCommandline": true)`.  The suggested task looks like

![image](https://github.com/microsoft/terminal/assets/18356694/49a3120b-4aba-400f-b8c7-76e13bde73b4)

The backspaces we inserted? Those show up in the name. They shouldn't. We can probably manually remove them, if we know the command didn't have a specific name set. 



Discussion / hints:

Fix patch:
diff --git a/src/cascadia/TerminalApp/FilteredCommand.cpp b/src/cascadia/TerminalApp/FilteredCommand.cpp
index 98a7b53de4b..d6c6c38a284 100644
--- a/src/cascadia/TerminalApp/FilteredCommand.cpp
+++ b/src/cascadia/TerminalApp/FilteredCommand.cpp
@@ -21,11 +21,22 @@ namespace winrt::TerminalApp::implementation
 {
     // This class is a wrapper of PaletteItem, that is used as an item of a filterable list in CommandPalette.
     // It manages a highlighted text that is computed by matching search filter characters to item name
-    FilteredCommand::FilteredCommand(const winrt::TerminalApp::PaletteItem& item) :
-        _Item(item),
-        _Filter(L""),
-        _Weight(0)
+    FilteredCommand::FilteredCommand(const winrt::TerminalApp::PaletteItem& item)
     {
+        // Actually implement the ctor in _constructFilteredCommand
+        _constructFilteredCommand(item);
+    }
+
+    // We need to actually implement the ctor in a separate helper. This is
+    // because we have a FilteredTask class which derives from FilteredCommand.
+    // HOWEVER, for cppwinrt ~ r e a s o n s ~, it doesn't actually derive from
+    // FilteredCommand directly, so we can't just use the FilteredCommand ctor
+    // directly in the base class.
+    void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item)
+    {
+        _Item = item;
+        _Filter = L"";
+        _Weight = 0;
         _HighlightedName = _computeHighlightedName();
 
         // Recompute the highlighted name if the item name changes
diff --git a/src/cascadia/TerminalApp/FilteredCommand.h b/src/cascadia/TerminalApp/FilteredCommand.h
index e5df7bb018a..f304ad032a3 100644
--- a/src/cascadia/TerminalApp/FilteredCommand.h
+++ b/src/cascadia/TerminalApp/FilteredCommand.h
@@ -19,7 +19,7 @@ namespace winrt::TerminalApp::implementation
         FilteredCommand() = default;
         FilteredCommand(const winrt::TerminalApp::PaletteItem& item);
 
-        void UpdateFilter(const winrt::hstring& filter);
+        virtual void UpdateFilter(const winrt::hstring& filter);
 
         static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second);
 
@@ -29,6 +29,9 @@ namespace winrt::TerminalApp::implementation
         WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, PropertyChanged.raise);
         WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise);
 
+    protected:
+        void _constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item);
+
     private:
         winrt::TerminalApp::HighlightedText _computeHighlightedName();
         int _computeWeight();
diff --git a/src/cascadia/TerminalApp/FilteredCommand.idl b/src/cascadia/TerminalApp/FilteredCommand.idl
index ce2b04b92c1..a63e6e81100 100644
--- a/src/cascadia/TerminalApp/FilteredCommand.idl
+++ b/src/cascadia/TerminalApp/FilteredCommand.idl
@@ -6,7 +6,7 @@ import "HighlightedTextControl.idl";
 
 namespace TerminalApp
 {
-    [default_interface] runtimeclass FilteredCommand : Windows.UI.Xaml.Data.INotifyPropertyChanged
+    [default_interface] unsealed runtimeclass FilteredCommand : Windows.UI.Xaml.Data.INotifyPropertyChanged
     {
         FilteredCommand();
         FilteredCommand(PaletteItem item);
diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp
index eb153ce1ed8..96667a60fa1 100644
--- a/src/cascadia/TerminalApp/Pane.cpp
+++ b/src/cascadia/TerminalApp/Pane.cpp
@@ -2944,7 +2944,8 @@ void Pane::FinalizeConfigurationGivenDefault()
 // - Returns true if the pane or one of its descendants is read-only
 bool Pane::ContainsReadOnly() const
 {
-    return _IsLeaf() ? _content.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly());
+    return _IsLeaf() ? (_content == nullptr ? false : _content.ReadOnly()) :
+                       (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly());
 }
 
 // Method Description:
diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index efe3e1278e2..95babb6e4a6 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -907,6 +907,28 @@
   <data name="RestartConnectionToolTip" xml:space="preserve">
     <value>Restart the active pane connection</value>
   </data>
+  <data name="SnippetPaneTitle.Text" xml:space="preserve">
+    <value>Snippets</value>
+    <comment>Header for a page that includes small "snippets" of text for the user to enter</comment>
+  </data>
+  <data name="SnippetsFilterBox.PlaceholderText" xml:space="preserve">
+    <value>Filter snippets...</value>
+    <comment>Placeholder text for a text box to filter snippets (on the same page as the 'SnippetPaneTitle')</comment>
+  </data>
+  <data name="SnippetPlayButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
+    <value>Input this command</value>
+  </data>
+  <data name="SnippetPlayButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
+    <value>Input this command</value>
+  </data>
+  <data name="SnippetsPaneNoneFoundHeader.Text" xml:space="preserve">
+    <value>No snippets found in your settings.</value>
+    <comment>Text shown to user when no snippets are found in their settings</comment>
+  </data>
+  <data name="SnippetsPaneNoneFoundDetails.Text" xml:space="preserve">
+    <value>Add some "Send input" actions in your settings to have them show up here.</value>
+    <comment>Additional information presented to the user to let them know they can add a "Send input" action in their setting to populate the snippets pane</comment>
+  </data>
   <data name="ActionSavedToast.Title" xml:space="preserve">
     <value>Action saved</value>
   </data>
diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp
new file mode 100644
index 00000000000..7e8c1695543
--- /dev/null
+++ b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp
@@ -0,0 +1,199 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "SnippetsPaneContent.h"
+#include "SnippetsPaneContent.g.cpp"
+#include "FilteredTask.g.cpp"
+#include "Utils.h"
+
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::System;
+using namespace winrt::Microsoft::Terminal::Settings;
+using namespace winrt::Microsoft::Terminal::Settings::Model;
+
+namespace winrt
+{
+    namespace WUX = Windows::UI::Xaml;
+    namespace MUX = Microsoft::UI::Xaml;
+    using IInspectable = Windows::Foundation::IInspectable;
+}
+
+namespace winrt::TerminalApp::implementation
+{
+    SnippetsPaneContent::SnippetsPaneContent()
+    {
+        InitializeComponent();
+
+        WUX::Automation::AutomationProperties::SetName(*this, RS_(L"SnippetPaneTitle/Text"));
+    }
+
+    void SnippetsPaneContent::_updateFilteredCommands()
+    {
+        const auto& queryString = _filterBox().Text();
+
+        // DON'T replace the itemSource here. If you do, it'll un-expand all the
+        // nested items the user has expanded. Instead, just update the filter.
+        // That'll also trigger a PropertyChanged for the Visibility property.
+        for (const auto& t : _allTasks)
+        {
+            auto impl = winrt::get_self<implementation::FilteredTask>(t);
+            impl->UpdateFilter(queryString);
+        }
+    }
+
+    void SnippetsPaneContent::UpdateSettings(const CascadiaSettings& settings)
+    {
+        _settings = settings;
+
+        // You'd think that `FilterToSendInput(queryString)` would work. It
+        // doesn't! That uses the queryString as the current command the user
+        // has typed, then relies on the suggestions UI to _also_ filter with that
+        // string.
+
+        const auto tasks = _settings.GlobalSettings().ActionMap().FilterToSendInput(winrt::hstring{}); // IVector<Model::Command>
+        _allTasks = winrt::single_threaded_observable_vector<TerminalApp::FilteredTask>();
+        for (const auto& t : tasks)
+        {
+            const auto& filtered{ winrt::make<FilteredTask>(t) };
+            _allTasks.Append(filtered);
+        }
+        _treeView().ItemsSource(_allTasks);
+
+        _updateFilteredCommands();
+
+        PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"HasSnippets" });
+    }
+
+    bool SnippetsPaneContent::HasSnippets() const
+    {
+        return _allTasks.Size() != 0;
+    }
+
+    void SnippetsPaneContent::_filterTextChanged(const IInspectable& /*sender*/,
+                                                 const Windows::UI::Xaml::RoutedEventArgs& /*args*/)
+    {
+        _updateFilteredCommands();
+    }
+
+    winrt::Windows::UI::Xaml::FrameworkElement SnippetsPaneContent::GetRoot()
+    {
+        return *this;
+    }
+    winrt::Windows::Foundation::Size SnippetsPaneContent::MinimumSize()
+    {
+        return { 200, 200 };
+    }
+    void SnippetsPaneContent::Focus(winrt::Windows::UI::Xaml::FocusState reason)
+    {
+        _filterBox().Focus(reason);
+    }
+    void SnippetsPaneContent::Close()
+    {
+        CloseRequested.raise(*this, nullptr);
+    }
+
+    INewContentArgs SnippetsPaneContent::GetNewTerminalArgs(BuildStartupKind /*kind*/) const
+    {
+        return BaseContentArgs(L"snippets");
+    }
+
+    winrt::hstring SnippetsPaneContent::Icon() const
+    {
+        static constexpr std::wstring_view glyph{ L"\xe70b" }; // QuickNote
+        return winrt::hstring{ glyph };
+    }
+
+    winrt::WUX::Media::Brush SnippetsPaneContent::BackgroundBrush()
+    {
+        static const auto key = winrt::box_value(L"UnfocusedBorderBrush");
+        return ThemeLookup(WUX::Application::Current().Resources(),
+                           _settings.GlobalSettings().CurrentTheme().RequestedTheme(),
+                           key)
+            .try_as<winrt::WUX::Media::Brush>();
+    }
+
+    void SnippetsPaneContent::SetLastActiveControl(const Microsoft::Terminal::Control::TermControl& control)
+    {
+        _control = control;
+    }
+
+    void SnippetsPaneContent::_runCommand(const Microsoft::Terminal::Settings::Model::Command& command)
+    {
+        if (const auto& strongControl{ _control.get() })
+        {
+            // By using the last active control as the sender here, the
+            // action dispatch will send this to the active control,
+            // thinking that it is the control that requested this event.
+            strongControl.Focus(winrt::WUX::FocusState::Programmatic);
+            DispatchCommandRequested.raise(strongControl, command);
+        }
+    }
+
+    void SnippetsPaneContent::_runCommandButtonClicked(const Windows::Foundation::IInspectable& sender,
+                                                       const Windows::UI::Xaml::RoutedEventArgs&)
+    {
+        if (const auto& taskVM{ sender.try_as<WUX::Controls::Button>().DataContext().try_as<FilteredTask>() })
+        {
+            _runCommand(taskVM->Command());
+        }
+    }
+
+    // Called when one of the items in the list is tapped, or enter/space is
+    // pressed on it while focused. Notably, this isn't the Tapped event - it
+    // isn't called when the user clicks the dropdown arrow (that does usually
+    // also trigger a Tapped).
+    //
+    // We'll use this to toggle the expanded state of nested items, since the
+    // tree view arrow is so little
+    void SnippetsPaneContent::_treeItemInvokedHandler(const IInspectable& /*sender*/,
+                                                      const MUX::Controls::TreeViewItemInvokedEventArgs& e)
+    {
+        // The InvokedItem here is the item in the data collection that was
+        // bound itself.
+        if (const auto& taskVM{ e.InvokedItem().try_as<FilteredTask>() })
+        {
+            if (taskVM->HasChildren())
+            {
+                // We then need to find the actual TreeViewItem for that
+                // FilteredTask.
+                if (const auto& item{ _treeView().ContainerFromItem(*taskVM).try_as<MUX::Controls::TreeViewItem>() })
+                {
+                    item.IsExpanded(!item.IsExpanded());
+                }
+            }
+        }
+    }
+
+    // Raised on individual TreeViewItems. We'll use this event to send the
+    // input on an Enter/Space keypress, when a leaf item is selected.
+    void SnippetsPaneContent::_treeItemKeyUpHandler(const IInspectable& sender,
+                                                    const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e)
+    {
+        const auto& item{ sender.try_as<MUX::Controls::TreeViewItem>() };
+        if (!item)
+        {
+            return;
+        }
+        const auto& taskVM{ item.DataContext().try_as<FilteredTask>() };
+        if (!taskVM || taskVM->HasChildren())
+        {
+            return;
+        }
+
+        const auto& key = e.OriginalKey();
+        if (key == VirtualKey::Enter || key == VirtualKey::Space)
+        {
+            if (const auto& button = e.OriginalSource().try_as<WUX::Controls::Button>())
+            {
+                // Let the button handle the Enter key so an eventually attached click handler will be called
+                e.Handled(false);
+                return;
+            }
+
+            _runCommand(taskVM->Command());
+            e.Handled(true);
+        }
+    }
+
+}
diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.h b/src/cascadia/TerminalApp/SnippetsPaneContent.h
new file mode 100644
index 00000000000..493121c9150
--- /dev/null
+++ b/src/cascadia/TerminalApp/SnippetsPaneContent.h
@@ -0,0 +1,154 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+#include "SnippetsPaneContent.g.h"
+#include "FilteredTask.g.h"
+#include "FilteredCommand.h"
+#include "ActionPaletteItem.h"
+#include <LibraryResources.h>
+
+namespace winrt::TerminalApp::implementation
+{
+    struct SnippetsPaneContent : SnippetsPaneContentT<SnippetsPaneContent>
+    {
+        SnippetsPaneContent();
+
+        winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
+
+        void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
+
+        winrt::Windows::Foundation::Size MinimumSize();
+        void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic);
+        void Close();
+        winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const;
+
+        winrt::hstring Title() { return RS_(L"SnippetPaneTitle/Text"); }
+        uint64_t TaskbarState() { return 0; }
+        uint64_t TaskbarProgress() { return 0; }
+        bool ReadOnly() { return false; }
+        winrt::hstring Icon() const;
+        Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() const noexcept { return nullptr; }
+        winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
+
+        void SetLastActiveControl(const Microsoft::Terminal::Control::TermControl& control);
+        bool HasSnippets() const;
+
+        til::typed_event<> ConnectionStateChanged;
+        til::typed_event<IPaneContent> CloseRequested;
+        til::typed_event<IPaneContent, winrt::TerminalApp::BellEventArgs> BellRequested;
+        til::typed_event<IPaneContent> TitleChanged;
+        til::typed_event<IPaneContent> TabColorChanged;
+        til::typed_event<IPaneContent> TaskbarProgressChanged;
+        til::typed_event<IPaneContent> ReadOnlyChanged;
+        til::typed_event<IPaneContent> FocusRequested;
+
+        til::typed_event<winrt::Windows::Foundation::IInspectable, Microsoft::Terminal::Settings::Model::Command> DispatchCommandRequested;
+
+        til::property_changed_event PropertyChanged;
+
+    private:
+        friend struct SnippetsPaneContentT<SnippetsPaneContent>; // for Xaml to bind events
+
+        winrt::weak_ref<Microsoft::Terminal::Control::TermControl> _control{ nullptr };
+        winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
+        winrt::Windows::Foundation::Collections::IObservableVector<TerminalApp::FilteredTask> _allTasks{ nullptr };
+
+        void _runCommandButtonClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs&);
+        void _filterTextChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
+
+        void _updateFilteredCommands();
+        void _treeItemInvokedHandler(const Windows::Foundation::IInspectable& sender, const Microsoft::UI::Xaml::Controls::TreeViewItemInvokedEventArgs& e);
+        void _treeItemKeyUpHandler(const IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
+        void _runCommand(const Microsoft::Terminal::Settings::Model::Command& command);
+    };
+
+    struct FilteredTask : FilteredTaskT<FilteredTask>
+    {
+        FilteredTask() = default;
+
+        FilteredTask(const winrt::Microsoft::Terminal::Settings::Model::Command& command)
+        {
+            _filteredCommand = winrt::make_self<implementation::FilteredCommand>(winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(command, winrt::hstring{}));
+            _command = command;
+
+            // The Children() method must always return a non-null vector
+            _children = winrt::single_threaded_observable_vector<TerminalApp::FilteredTask>();
+            if (_command.HasNestedCommands())
+            {
+                for (const auto& [_, child] : _command.NestedCommands())
+                {
+                    auto vm{ winrt::make<FilteredTask>(child) };
+                    _children.Append(vm);
+                }
+            }
+        }
+
+        void UpdateFilter(const winrt::hstring& filter)
+        {
+            _filteredCommand->UpdateFilter(filter);
+            for (const auto& c : _children)
+            {
+                auto impl = winrt::get_self<implementation::FilteredTask>(c);
+                impl->UpdateFilter(filter);
+            }
+
+            PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Visibility" });
+        }
+
+        winrt::hstring Input()
+        {
+            if (const auto& actionItem{ _filteredCommand->Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
+            {
+                if (const auto& command{ actionItem.Command() })
+                {
+                    if (const auto& sendInput{ command.ActionAndArgs().Args().try_as<winrt::Microsoft::Terminal::Settings::Model::SendInputArgs>() })
+                    {
+                        return winrt::hstring{ til::visualize_nonspace_control_codes(sendInput.Input().c_str()) };
+                    }
+                }
+            }
+            return winrt::hstring{};
+        };
+
+        winrt::Windows::Foundation::Collections::IObservableVector<TerminalApp::FilteredTask> Children() { return _children; }
+        bool HasChildren() { return _children.Size() > 0; }
+        winrt::Microsoft::Terminal::Settings::Model::Command Command() { return _command; }
+        winrt::TerminalApp::FilteredCommand FilteredCommand() { return *_filteredCommand; }
+
+        int32_t Row() { return HasChildren() ? 2 : 1; } // See the BODGY comment in the .XAML for explanation
+
+        // Used to control if this item is visible in the TreeView. Turns out,
+        // TreeView is in fact sane enough to remove items entirely if they're
+        // Collapsed.
+        winrt::Windows::UI::Xaml::Visibility Visibility()
+        {
+            // Is there no filter, or do we match it?
+            if (_filteredCommand->Filter().empty() || _filteredCommand->Weight() > 0)
+            {
+                return winrt::Windows::UI::Xaml::Visibility::Visible;
+            }
+            // If we don't match, maybe one of our children does
+            auto totalWeight = _filteredCommand->Weight();
+            for (const auto& c : _children)
+            {
+                auto impl = winrt::get_self<implementation::FilteredTask>(c);
+                totalWeight += impl->_filteredCommand->Weight();
+            }
+
+            return totalWeight > 0 ? winrt::Windows::UI::Xaml::Visibility::Visible : winrt::Windows::UI::Xaml::Visibility::Collapsed;
+        };
+
+        til::property_changed_event PropertyChanged;
+
+    private:
+        winrt::Microsoft::Terminal::Settings::Model::Command _command{ nullptr };
+        winrt::com_ptr<winrt::TerminalApp::implementation::FilteredCommand> _filteredCommand{ nullptr };
+        winrt::Windows::Foundation::Collections::IObservableVector<TerminalApp::FilteredTask> _children{ nullptr };
+    };
+}
+
+namespace winrt::TerminalApp::factory_implementation
+{
+    BASIC_FACTORY(SnippetsPaneContent);
+}
diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.xaml b/src/cascadia/TerminalApp/SnippetsPaneContent.xaml
new file mode 100644
index 00000000000..a887b72b757
--- /dev/null
+++ b/src/cascadia/TerminalApp/SnippetsPaneContent.xaml
@@ -0,0 +1,261 @@
+<!--
+    Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
+    the MIT License. See LICENSE in the project root for license information.
+-->
+<UserControl x:Class="TerminalApp.SnippetsPaneContent"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:SettingsModel="using:Microsoft.Terminal.Settings.Model"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="using:TerminalApp"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:model="using:Microsoft.Terminal.Settings.Model"
+             xmlns:mtu="using:Microsoft.Terminal.UI"
+             xmlns:mux="using:Microsoft.UI.Xaml.Controls"
+             AutomationProperties.LocalizedControlType="pane"
+             mc:Ignorable="d">
+
+    <UserControl.Resources>
+        <ResourceDictionary>
+
+            <Style x:Key="PlayButtonTemplate"
+                   TargetType="Button">
+                <Setter Property="Margin" Value="0" />
+                <Setter Property="Padding" Value="4" />
+                <Setter Property="BorderBrush" Value="Transparent" />
+                <Setter Property="Background" Value="Transparent" />
+
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="Button">
+                            <Border x:Name="ButtonBaseElement"
+                                    Padding="{TemplateBinding Padding}"
+                                    AutomationProperties.AccessibilityView="Raw"
+                                    Background="{TemplateBinding Background}"
+                                    BackgroundSizing="{TemplateBinding BackgroundSizing}"
+                                    BorderBrush="{TemplateBinding BorderBrush}"
+                                    BorderThickness="{TemplateBinding BorderThickness}"
+                                    CornerRadius="{TemplateBinding CornerRadius}">
+                                <Viewbox Width="14"
+                                         Height="14">
+                                    <Grid>
+                                        <FontIcon x:Name="ButtonBackgroundIcon"
+                                                  FontFamily="{ThemeResource SymbolThemeFontFamily}"
+                                                  Foreground="{ThemeResource PlayButtonHoveredColor}"
+                                                  Glyph="&#xF5b0;"
+                                                  Visibility="Collapsed" />
+                                        <FontIcon x:Name="ButtonOutlineIcon"
+                                                  FontFamily="{ThemeResource SymbolThemeFontFamily}"
+                                                  Foreground="{ThemeResource PlayButtonNormalColor}"
+                                                  Glyph="&#xE768;" />
+                                    </Grid>
+                                </Viewbox>
+
+                                <VisualStateManager.VisualStateGroups>
+                                    <VisualStateGroup x:Name="CommonStates">
+
+                                        <VisualState x:Name="Normal">
+                                            <VisualState.Setters>
+                                                <Setter Target="ButtonBackgroundIcon.Visibility" Value="Collapsed" />
+                                            </VisualState.Setters>
+                                        </VisualState>
+
+                                        <VisualState x:Name="PointerOver">
+                                            <VisualState.Setters>
+                                                <Setter Target="ButtonBackgroundIcon.Visibility" Value="Visible" />
+                                            </VisualState.Setters>
+                                        </VisualState>
+
+                                        <VisualState x:Name="Disabled" />
+                                    </VisualStateGroup>
+
+                                    <VisualStateGroup x:Name="PlayButtonStates">
+                                        <VisualState x:Name="Ready">
+                                            <VisualState.Setters>
+                                                <Setter Target="ButtonBackgroundIcon.Glyph" Value="&#xF5b0;" />
+                                                <Setter Target="ButtonOutlineIcon.Glyph" Value="&#xE768;" />
+                                                <Setter Target="StatusProgress.IsActive" Value="False" />
+                                            </VisualState.Setters>
+                                        </VisualState>
+                                    </VisualStateGroup>
+
+                                </VisualStateManager.VisualStateGroups>
+                            </Border>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+
+            </Style>
+
+            <DataTemplate x:Key="TaskItemTemplate"
+                          x:DataType="local:FilteredTask">
+                <mux:TreeViewItem x:Name="rootItem"
+                                  AutomationProperties.HelpText="{x:Bind Input, Mode=OneWay}"
+                                  AutomationProperties.Name="{x:Bind FilteredCommand.Item.Name, Mode=OneWay}"
+                                  ItemsSource="{x:Bind Children, Mode=OneWay}"
+                                  KeyUp="_treeItemKeyUpHandler"
+                                  Visibility="{x:Bind Visibility, Mode=OneWay}">
+                    <Grid AutomationProperties.AccessibilityView="Raw">
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="*" />
+                            <!--  Name  -->
+                            <RowDefinition Height="*" />
+                            <!--  Input  -->
+                            <RowDefinition Height="0" />
+                            <!--  Input gets put here when we're a nested command, to hide it  -->
+                        </Grid.RowDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+
+
+                        <Button x:Uid="SnippetPlayButton"
+                                Grid.Row="0"
+                                Grid.RowSpan="1"
+                                Grid.Column="0"
+                                Margin="-28,0,0,0"
+                                Padding="3"
+                                VerticalAlignment="Bottom"
+                                Background="Transparent"
+                                BorderBrush="Transparent"
+                                Click="_runCommandButtonClicked"
+                                Style="{StaticResource PlayButtonTemplate}"
+                                Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(HasChildren), Mode=OneWay}">
+
+                            <Button.Content>
+                                <FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
+                                          FontSize="12"
+                                          Glyph="&#xE768;" />
+                                <!--
+                                    xE768 is Play, which is just an outline.
+                                    xF5B0 is PlaySolid, also a good option. Seemed
+                                    better to have a lightweight outline though
+                                -->
+                            </Button.Content>
+
+                            <Button.Resources>
+                                <ResourceDictionary>
+                                    <ResourceDictionary.ThemeDictionaries>
+                                        <ResourceDictionary x:Key="Light">
+                                            <SolidColorBrush x:Key="ButtonForegroundPointerOver"
+                                                             Color="{StaticResource SystemAccentColor}" />
+                                            <SolidColorBrush x:Key="ButtonForegroundPressed"
+                                                             Color="{StaticResource SystemAccentColor}" />
+                                        </ResourceDictionary>
+                                        <ResourceDictionary x:Key="Dark">
+                                            <SolidColorBrush x:Key="ButtonForegroundPointerOver"
+                                                             Color="{StaticResource SystemAccentColor}" />
+                                            <SolidColorBrush x:Key="ButtonForegroundPressed"
+                                                             Color="{StaticResource SystemAccentColor}" />
+                                        </ResourceDictionary>
+                                        <ResourceDictionary x:Key="HighContrast">
+                                            <SolidColorBrush x:Key="ButtonBackground"
+                                                             Color="{ThemeResource SystemColorButtonFaceColor}" />
+                                            <SolidColorBrush x:Key="ButtonBackgroundPointerOver"
+                                                             Color="{ThemeResource SystemColorHighlightColor}" />
+                                            <SolidColorBrush x:Key="ButtonBackgroundPressed"
+                                                             Color="{ThemeResource SystemColorHighlightColor}" />
+                                            <SolidColorBrush x:Key="ButtonForeground"
+                                                             Color="{ThemeResource SystemColorButtonTextColor}" />
+                                            <SolidColorBrush x:Key="ButtonForegroundPointerOver"
+                                                             Color="{ThemeResource SystemColorHighlightTextColor}" />
+                                            <SolidColorBrush x:Key="ButtonForegroundPressed"
+                                                             Color="{ThemeResource SystemColorHighlightTextColor}" />
+                                        </ResourceDictionary>
+                                    </ResourceDictionary.ThemeDictionaries>
+                                </ResourceDictionary>
+                            </Button.Resources>
+                        </Button>
+
+                        <local:HighlightedTextControl Grid.Column="1"
+                                                      HorizontalAlignment="Left"
+                                                      IsTabStop="False"
+                                                      Text="{x:Bind FilteredCommand.HighlightedName, Mode=OneWay}" />
+
+                        <!--
+                            BODGY: I can't choose different templates if this item is nested or not.
+                            (See https://github.com/microsoft/microsoft-ui-xaml/issues/2121)
+                            But what I can do, is toss the text into a 0-height row to hide it.
+                        -->
+                        <TextBlock Grid.Row="{x:Bind Row, Mode=OneWay}"
+                                   Grid.Column="1"
+                                   Margin="12,0,12,6"
+                                   FontFamily="Cascadia Mono, Consolas"
+                                   IsTextSelectionEnabled="False"
+                                   Style="{ThemeResource BodyTextBlockStyle}"
+                                   Text="{x:Bind Input}"
+                                   Visibility="{Binding ElementName=rootItem, Path=IsSelected}" />
+                    </Grid>
+                </mux:TreeViewItem>
+            </DataTemplate>
+
+            <ResourceDictionary.ThemeDictionaries>
+                <!--  same as in MainPage, this is SolidBackgroundFillColorTertiary  -->
+                <ResourceDictionary x:Key="Dark">
+                    <Color x:Key="PageBackground">#282828</Color>
+                    <Color x:Key="PlayButtonHoveredColor">#90ef90</Color>
+                    <Color x:Key="PlayButtonNormalColor">#8888</Color>
+                </ResourceDictionary>
+                <ResourceDictionary x:Key="Light">
+                    <Color x:Key="PageBackground">#F9F9F9</Color>
+                    <Color x:Key="PlayButtonHoveredColor">#257f01</Color>
+                    <Color x:Key="PlayButtonNormalColor">#88222222</Color>
+                </ResourceDictionary>
+                <ResourceDictionary x:Key="HighContrast">
+                    <!--  Define resources for HighContrast mode here  -->
+                    <StaticResource x:Key="PageBackground"
+                                    ResourceKey="SystemColorWindowColorBrush" />
+                </ResourceDictionary>
+
+            </ResourceDictionary.ThemeDictionaries>
+
+        </ResourceDictionary>
+    </UserControl.Resources>
+
+    <!--  Don't allow the user to focus the root pane, but do allow individual controls to be focused  -->
+    <!--  TabFocusNavigation="Cycle" is load-bearing: otherwise, tab focus disappears to nowhere off the play button  -->
+    <Grid AllowFocusOnInteraction="False"
+          AutomationProperties.AccessibilityView="Raw"
+          Background="{ThemeResource PageBackground}"
+          TabFocusNavigation="Cycle">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto" />
+            <RowDefinition Height="Auto" />
+            <RowDefinition Height="*" />
+        </Grid.RowDefinitions>
+
+        <TextBlock x:Name="_title"
+                   x:Uid="SnippetPaneTitle"
+                   Grid.Row="0"
+                   Margin="4"
+                   FontSize="24" />
+
+        <TextBlock Grid.Row="1"
+                   Margin="8,16,8,8"
+                   TextWrapping="WrapWholeWords"
+                   Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(HasSnippets), Mode=OneWay}">
+            <Run x:Uid="SnippetsPaneNoneFoundHeader" />
+            <LineBreak />
+            <Run x:Uid="SnippetsPaneNoneFoundDetails" />
+        </TextBlock>
+
+        <TextBox x:Name="_filterBox"
+                 x:Uid="SnippetsFilterBox"
+                 Grid.Row="1"
+                 Margin="8,0,8,8"
+                 AllowFocusOnInteraction="True"
+                 TextChanged="_filterTextChanged"
+                 Visibility="{x:Bind HasSnippets, Mode=OneWay}" />
+
+        <mux:TreeView x:Name="_treeView"
+                      Grid.Row="2"
+                      AllowFocusOnInteraction="True"
+                      CanDragItems="False"
+                      CanReorderItems="False"
+                      ItemInvoked="_treeItemInvokedHandler"
+                      ItemTemplate="{StaticResource TaskItemTemplate}"
+                      Visibility="{x:Bind HasSnippets, Mode=OneWay}" />
+    </Grid>
+
+</UserControl>
diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj
index 238fecf9afc..8549c32cd40 100644
--- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj
+++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj
@@ -71,6 +71,9 @@
     <Page Include="SuggestionsControl.xaml">
       <SubType>Designer</SubType>
     </Page>
+    <Page Include="SnippetsPaneContent.xaml">
+      <SubType>Designer</SubType>
+    </Page>
   </ItemGroup>
   <!-- ========================= Headers ======================== -->
   <ItemGroup>
@@ -161,6 +164,9 @@
     <ClInclude Include="ScratchpadContent.h">
       <DependentUpon>TerminalPaneContent.idl</DependentUpon>
     </ClInclude>
+    <ClInclude Include="SnippetsPaneContent.h">
+      <DependentUpon>SnippetsPaneContent.xaml</DependentUpon>
+    </ClInclude>
     <ClInclude Include="SettingsPaneContent.h">
       <DependentUpon>TerminalPaneContent.idl</DependentUpon>
     </ClInclude>
@@ -274,6 +280,9 @@
     <ClCompile Include="ScratchpadContent.cpp">
       <DependentUpon>TerminalPaneContent.idl</DependentUpon>
     </ClCompile>
+    <ClCompile Include="SnippetsPaneContent.cpp">
+      <DependentUpon>SnippetsPaneContent.xaml</DependentUpon>
+    </ClCompile>
     <ClCompile Include="SettingsPaneContent.cpp">
       <DependentUpon>TerminalPaneContent.idl</DependentUpon>
     </ClCompile>
@@ -352,7 +361,10 @@
     </Midl>
     <Midl Include="FilteredCommand.idl" />
     <Midl Include="IPaneContent.idl" />
-    <Midl Include="TerminalPaneContent.idl" />
+    <Midl Include="TerminalPaneContent.idl" >
+      <DependentUpon>TaskPaneContent.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Midl>
     <Midl Include="TerminalSettingsCache.idl" />
   </ItemGroup>
   <!-- ========================= Misc Files ======================== -->
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index aa0bf06cf23..0e3dd5256bc 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -16,6 +16,7 @@
 #include "DebugTapConnection.h"
 #include "SettingsPaneContent.h"
 #include "ScratchpadContent.h"
+#include "SnippetsPaneContent.h"
 #include "TabRowControl.h"
 
 #include "TerminalPage.g.cpp"
@@ -451,10 +452,10 @@ namespace winrt::TerminalApp::implementation
     // - command - command to dispatch
     // Return Value:
     // - <none>
-    void TerminalPage::_OnDispatchCommandRequested(const IInspectable& /*sender*/, const Microsoft::Terminal::Settings::Model::Command& command)
+    void TerminalPage::_OnDispatchCommandRequested(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& command)
     {
         const auto& actionAndArgs = command.ActionAndArgs();
-        _actionDispatch->DoAction(actionAndArgs);
+        _actionDispatch->DoAction(sender, actionAndArgs);
     }
 
     // Method Description:
@@ -620,9 +621,12 @@ namespace winrt::TerminalApp::implementation
             // GH#6586: now that we're done processing all startup commands,
             // focus the active control. This will work as expected for both
             // commandline invocations and for `wt` action invocations.
-            if (const auto control = _GetActiveControl())
+            if (const auto& terminalTab{ _GetFocusedTabImpl() })
             {
-                control.Focus(FocusState::Programmatic);
+                if (const auto& content{ terminalTab->GetActiveContent() })
+                {
+                    content.Focus(FocusState::Programmatic);
+                }
             }
         }
         if (initial)
@@ -2466,16 +2470,16 @@ namespace winrt::TerminalApp::implementation
         }
 
         _UnZoomIfNeeded();
-        auto [original, _] = activeTab->SplitPane(*realSplitType, splitSize, newPane);
+        auto [original, newGuy] = activeTab->SplitPane(*realSplitType, splitSize, newPane);
 
         // After GH#6586, the control will no longer focus itself
         // automatically when it's finished being laid out. Manually focus
         // the control here instead.
         if (_startupState == StartupState::Initialized)
         {
-            if (const auto control = _GetActiveControl())
+            if (const auto& content{ newGuy->GetContent() })
             {
-                control.Focus(FocusState::Programmatic);
+                content.Focus(FocusState::Programmatic);
             }
         }
     }
@@ -3270,6 +3274,8 @@ namespace winrt::TerminalApp::implementation
         return resultPane;
     }
 
+    // NOTE: callers of _MakePane should be able to accept nullptr as a return
+    // value gracefully.
     std::shared_ptr<Pane> TerminalPage::_MakePane(const INewContentArgs& contentArgs,
                                                   const winrt::TerminalApp::TabBase& sourceTab,
                                                   TerminalConnection::ITerminalConnection existingConnection)
@@ -3300,6 +3306,36 @@ namespace winrt::TerminalApp::implementation
         {
             content = _makeSettingsContent();
         }
+        else if (paneType == L"snippets")
+        {
+            // Prevent the user from opening a bunch of snippets panes.
+            //
+            // Look at the focused tab, and if it already has one, then just focus it.
+            const bool found = _GetFocusedTab().try_as<TerminalTab>()->GetRootPane()->WalkTree([](const auto& p) -> bool {
+                if (const auto& snippets{ p->GetContent().try_as<SnippetsPaneContent>() })
+                {
+                    snippets->Focus(FocusState::Programmatic);
+                    return true;
+                }
+                return false;
+            });
+            // Bail out if we already found one.
+            if (found)
+            {
+                return nullptr;
+            }
+
+            const auto& tasksContent{ winrt::make_self<SnippetsPaneContent>() };
+            tasksContent->UpdateSettings(_settings);
+            tasksContent->GetRoot().KeyDown({ this, &TerminalPage::_KeyDownHandler });
+            tasksContent->DispatchCommandRequested({ this, &TerminalPage::_OnDispatchCommandRequested });
+            if (const auto& termControl{ _GetActiveControl() })
+            {
+                tasksContent->SetLastActiveControl(termControl);
+            }
+
+            content = *tasksContent;
+        }
 
         assert(content);
 
diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.idl b/src/cascadia/TerminalApp/TerminalPaneContent.idl
index 0e4738fff42..255ddef547b 100644
--- a/src/cascadia/TerminalApp/TerminalPaneContent.idl
+++ b/src/cascadia/TerminalApp/TerminalPaneContent.idl
@@ -3,6 +3,7 @@
 
 import "IPaneContent.idl";
 import "TerminalSettingsCache.idl";
+import "FilteredCommand.idl";
 
 namespace TerminalApp
 {
@@ -16,4 +17,22 @@ namespace TerminalApp
 
         event Windows.Foundation.TypedEventHandler<TerminalPaneContent, Object> RestartTerminalRequested;
     }
+
+    [default_interface] runtimeclass FilteredTask : Windows.UI.Xaml.Data.INotifyPropertyChanged
+    {
+        String Input{ get; };
+        Windows.Foundation.Collections.IObservableVector<FilteredTask> Children { get; };
+        Boolean HasChildren { get; };
+        Int32 Row { get; };
+        Windows.UI.Xaml.Visibility Visibility { get; };
+        TerminalApp.FilteredCommand FilteredCommand{ get; };
+    }
+
+    [default_interface] runtimeclass SnippetsPaneContent : Windows.UI.Xaml.Controls.UserControl, IPaneContent, Windows.UI.Xaml.Data.INotifyPropertyChanged
+    {
+        SnippetsPaneContent();
+        void SetLastActiveControl(Microsoft.Terminal.Control.TermControl control);
+        Boolean HasSnippets { get; };
+        event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.Command> DispatchCommandRequested;
+    }
 }
diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp
index 72995e29bac..961df655dd8 100644
--- a/src/cascadia/TerminalApp/TerminalTab.cpp
+++ b/src/cascadia/TerminalApp/TerminalTab.cpp
@@ -525,7 +525,7 @@ namespace winrt::TerminalApp::implementation
     // - pane: The new pane to add to the tree of panes; note that this pane
     //         could itself be a parent pane/the root node of a tree of panes
     // Return Value:
-    // - <none>
+    // - a pair of (the Pane that now holds the original content, the new Pane in the tree)
     std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> TerminalTab::SplitPane(SplitDirection splitType,
                                                                                    const float splitSize,
                                                                                    std::shared_ptr<Pane> pane)
@@ -1223,6 +1223,20 @@ namespace winrt::TerminalApp::implementation
 
         // Raise our own ActivePaneChanged event.
         ActivePaneChanged.raise(*this, nullptr);
+
+        // If the new active pane is a terminal, tell other interested panes
+        // what the new active pane is.
+        const auto content{ pane->GetContent() };
+        if (const auto termContent{ content.try_as<winrt::TerminalApp::TerminalPaneContent>() })
+        {
+            const auto& termControl{ termContent.GetTermControl() };
+            _rootPane->WalkTree([termControl](const auto& p) {
+                if (const auto& taskPane{ p->GetContent().try_as<SnippetsPaneContent>() })
+                {
+                    taskPane.SetLastActiveControl(termControl);
+                }
+            });
+        }
     }
 
     // Method Description:
diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
index b65b7eabcd4..f4b39abc297 100644
--- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
@@ -126,6 +126,10 @@
   <data name="SplitPaneParentCommandName" xml:space="preserve">
     <value>Split pane</value>
   </data>
+  <data name="SnippetsPaneCommandName" xml:space="preserve">
+    <value>Open snippets pane</value>
+    <comment>This will open a pane with a list of the users's saved commands ("snippets") in it</comment>
+  </data>
   <data name="ApplicationDisplayNamePortable" xml:space="preserve">
     <value>Terminal (Portable)</value>
     <comment>This display name is used when the Terminal application is running in a "portable" mode, where settings are not stored in a shared location.</comment>
diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json
index 1a3e7c6c111..f6713e265b0 100644
--- a/src/cascadia/TerminalSettingsModel/defaults.json
+++ b/src/cascadia/TerminalSettingsModel/defaults.json
@@ -446,6 +446,7 @@
         { "command": "quit", "id": "Terminal.Quit" },
         { "command": "restoreLastClosed", "id": "Terminal.RestoreLastClosed" },
         { "command": "openAbout", "id": "Terminal.OpenAboutDialog" },
+        { "command": "experimental.openTasks", "id": "Terminal.OpenTasks" },
 
         // Tab Management
         // "command": "closeTab" is unbound by default.
@@ -528,6 +529,7 @@
         { "command": { "action": "movePane", "index": 8 }, "id": "Terminal.MovePaneToTab8" },
         { "command": { "action": "movePane", "window": "new" }, "id": "Terminal.MovePaneToNewWindow" },
         { "command": "restartConnection", "id": "Terminal.RestartConnection" },
+        { "command": { "action": "splitPane", "type": "snippets" }, "id": "Terminal.OpenSnippetsPane", "name": { "key": "SnippetsPaneCommandName" } },
 
         // Clipboard Integration
         { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopyToClipboard" },
diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h
index 17c720614b1..fa1575d32ae 100644
--- a/src/cascadia/inc/cppwinrt_utils.h
+++ b/src/cascadia/inc/cppwinrt_utils.h
@@ -133,7 +133,7 @@ public:                                   \
         _##name = value;                  \
     }                                     \
                                           \
-private:                                  \
+protected:                                \
     type _##name{ __VA_ARGS__ };
 
 // Use this macro to quickly implement both the getter and setter for an
@@ -158,7 +158,7 @@ public:
         }                                                                                 \
     };                                                                                    \
                                                                                           \
-private:                                                                                  \
+protected:                                                                                \
     type _##name{ __VA_ARGS__ };                                                          \
     void _set##name(const type& value)                                                    \
     {                                                                                     \


Test patch: