Files
terminal-microsoft/src/cascadia/TerminalApp/SnippetsPaneContent.cpp
2026-06-08 13:57:42 -05:00

218 lines
7.9 KiB
C++

// 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() :
_allTasks{ winrt::single_threaded_observable_vector<TerminalApp::FilteredTask>() }
{
InitializeComponent();
WUX::Automation::AutomationProperties::SetName(*this, RS_(L"SnippetPaneTitle/Text"));
}
void SnippetsPaneContent::_updateFilteredCommands()
{
const auto& queryString = _filterBox().Text();
auto pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(queryString));
// 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(pattern);
}
}
safe_void_coroutine SnippetsPaneContent::UpdateSettings(const CascadiaSettings& settings)
{
_settings = settings;
const auto dispatcher = Dispatcher();
const auto weak = get_weak();
// 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 = co_await _settings.GlobalSettings().ActionMap().FilterToSnippets(winrt::hstring{}, winrt::hstring{}); // IVector<Model::Command>
co_await wil::resume_foreground(dispatcher);
const auto strong = weak.get();
if (!strong)
{
co_return;
}
_allTasks.Clear();
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"SettingsUiTabBrush");
return ThemeLookup(WUX::Application::Current().Resources(),
_settings.GlobalSettings().CurrentTheme(_settings.WindowSettingsDefaults()).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());
}
}
void SnippetsPaneContent::_closePaneClick(const Windows::Foundation::IInspectable& /*sender*/,
const Windows::UI::Xaml::RoutedEventArgs&)
{
Close();
}
// 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);
}
}
}