-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a snippets pane, which can be a static pane with all your snippets (`sendInput` actions) in it. (See #17329) This pane has a treeview with these actions in it, that we can filter with a textbox at the top. Play buttons next to entries make it quick to run the command you found. Bound in the default actions with ```json { "command": { "action": "splitPane", "type": "snippets" }, "id": "Terminal.OpenSnippetsPane", "name": { "key": "SnippetsPaneCommandName" } }, ``` re: #1595 ---- TODO, from 06-04 bug bash * [x] Snippets pane doesn't display some "no snippets found" text if there aren't any yet * [x] open snippets pane; find a "send input"; click the play button on it; input is sent to active pane; begin typing * [x] I can open an infinite amount of suggestions panes * ~I'm closing this as by-design for now at least. Nothing stopping anyone from opening infinite of any kind of pane.~ * ~This would require kind of a lot of refactoring in this PR to mark a kind of pane as being a singleton or singleton-per-tab~ * Okay everyone hates infinite suggestions panes, so I got rid of that * [x] Ctrl+Shift+W should still work in the snippets pane even if focus isn't in textbox * [ ] open snippets pane; click on text box; press TAB key; * [ ] If you press TAB again, I have no idea where focus went * [x] some previews don't work. Like `^c` (`"input": "\u0003"`) * [x] nested items just give you a bit of extra space for no reason and it looks a little awkward * [x] UI Suggestion: add padding on the right side * [ ] [Accessibility] Narrator says "Clear buffer; Suggestions found 132" when you open the snippets pane - Note: this is probably Narrator reading out the command palette (since that's where I opened it from) - We should probably expect something like "Snippets", then (assuming focus is thrown into text box) "Type to filter snippets" or something like that
- Loading branch information
1 parent
ec92892
commit 02a7c02
Showing
15 changed files
with
756 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.