Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a snippets pane #17330

Merged
merged 30 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0ec5aef
Squashed commit of the following:
zadjii-msft May 2, 2024
43d46db
hey it runs again
zadjii-msft May 2, 2024
7b8b60b
resources to localize
zadjii-msft May 3, 2024
32234ca
nits for polish
zadjii-msft May 24, 2024
2dc16e4
the cool thing with the hovering
zadjii-msft May 24, 2024
57930ad
Merge remote-tracking branch 'origin/main' into dev/migrie/f/snippets…
zadjii-msft May 28, 2024
3ac5414
last nits before review
zadjii-msft May 28, 2024
e923513
spel
zadjii-msft May 28, 2024
5fd1100
Rename this, so the code doesn't reflect the old name
zadjii-msft May 28, 2024
eed0b02
Merge remote-tracking branch 'origin/main' into dev/migrie/f/snippets…
zadjii-msft Jun 6, 2024
bef9a2e
add padding
zadjii-msft Jun 6, 2024
be590b4
focus the control
zadjii-msft Jun 7, 2024
776164a
Don't let focus get lost into the pane
zadjii-msft Jun 10, 2024
825971c
visualize escape characters
zadjii-msft Jun 10, 2024
8d8590d
add warning when empty
zadjii-msft Jun 10, 2024
def4190
Try to do the DataTemplate thing, and fail.
zadjii-msft Jun 10, 2024
2a9e02e
Revert "Try to do the DataTemplate thing, and fail."
zadjii-msft Jun 10, 2024
31747e4
oh its so bodgy
zadjii-msft Jun 10, 2024
a9293b5
fix the brush
zadjii-msft Jun 10, 2024
d50b05e
Merge remote-tracking branch 'origin/main' into dev/migrie/f/snippets…
zadjii-msft Jun 11, 2024
b109582
the most minor of nits
zadjii-msft Jun 11, 2024
6e3fd24
a11y issues
zadjii-msft Jun 11, 2024
3e9b596
oh yea nice tapped events and keypresses
zadjii-msft Jun 11, 2024
e54c1dd
spel
zadjii-msft Jun 11, 2024
df58d96
only one snippets pane
zadjii-msft Jun 11, 2024
25aff99
Merge remote-tracking branch 'origin/main' into dev/migrie/f/snippets…
zadjii-msft Jun 12, 2024
1a08ad4
last nits
zadjii-msft Jun 12, 2024
313535e
Merge remote-tracking branch 'origin/main' into dev/migrie/f/snippets…
zadjii-msft Jul 1, 2024
5ec555a
Composing's probably actually easier than... whatever that abominatio…
zadjii-msft Jul 1, 2024
a50ffbf
Merge remote-tracking branch 'origin/main' into dev/migrie/f/snippets…
zadjii-msft Jul 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1473,4 +1473,5 @@ namespace winrt::TerminalApp::implementation
_ShowAboutDialog();
args.Handled(true);
}

zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
}
19 changes: 15 additions & 4 deletions src/cascadia/TerminalApp/FilteredCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/cascadia/TerminalApp/FilteredCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/FilteredCommand.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
{
FilteredCommand();
FilteredCommand(PaletteItem item);
Expand Down
19 changes: 19 additions & 0 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -898,4 +898,23 @@
<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="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>
</root>
132 changes: 132 additions & 0 deletions src/cascadia/TerminalApp/SnippetsPaneContent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "SnippetsPaneContent.h"
#include "SnippetsPaneContent.g.cpp"
#include "FilteredTask.g.cpp"

using namespace winrt::Windows::Foundation;
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();

// auto res = Windows::UI::Xaml::Application::Current().Resources();
auto bg = Resources().Lookup(winrt::box_value(L"PageBackground"));
Background(bg.try_as<WUX::Media::Brush>());
}

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)
{
t.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(L""); // IVector<Model::Command>
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
_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 { 1, 1 };
}
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
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::Windows::UI::Xaml::Media::Brush SnippetsPaneContent::BackgroundBrush()
{
return Background();
}

void SnippetsPaneContent::SetLastActiveControl(const Microsoft::Terminal::Control::TermControl& control)
{
_control = control;
}

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>() })
{
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, taskVM->Command());
}
}
}

}
145 changes: 145 additions & 0 deletions src/cascadia/TerminalApp/SnippetsPaneContent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// 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();
};

struct FilteredTask : FilteredTaskT<FilteredTask, TerminalApp::implementation::FilteredCommand>
{
FilteredTask() = default;

FilteredTask(const winrt::Microsoft::Terminal::Settings::Model::Command& command)
{
_constructFilteredCommand(winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(command));
_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) override
{
TerminalApp::implementation::FilteredCommand::UpdateFilter(filter);
for (const auto& c : _children)
{
c.UpdateFilter(filter);
}

PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Visibility" });
}

winrt::hstring Input()
{
if (const auto& actionItem{ _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 L"";
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
};

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; }

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 (_Filter.empty() || _Weight > 0)
{
return winrt::Windows::UI::Xaml::Visibility::Visible;
}
// If we don't match, maybe one of our children does
auto totalWeight = _Weight;
for (const auto& c : _children)
{
totalWeight += c.Weight();
}

return totalWeight > 0 ? winrt::Windows::UI::Xaml::Visibility::Visible : winrt::Windows::UI::Xaml::Visibility::Collapsed;
};

private:
winrt::Microsoft::Terminal::Settings::Model::Command _command{ nullptr };
winrt::Windows::Foundation::Collections::IObservableVector<TerminalApp::FilteredTask> _children{ nullptr };
};
}

namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(SnippetsPaneContent);
}
Loading
Loading