Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
49 changes: 49 additions & 0 deletions src/cascadia/QueryExtension/ExtensionPalette.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "LibraryResources.h"
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>

#include <winrt/Windows.ApplicationModel.DataTransfer.h>

#include "ExtensionPalette.g.cpp"
#include "ChatMessage.g.cpp"
#include "GroupedChatMessages.g.cpp"
Expand All @@ -16,6 +18,7 @@ using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Controls::Primitives;
using namespace winrt::Windows::System;
namespace WWH = ::winrt::Windows::Web::Http;
namespace WSS = ::winrt::Windows::Storage::Streams;
Expand Down Expand Up @@ -340,6 +343,14 @@ namespace winrt::Microsoft::Terminal::Query::Extension::implementation
void ExtensionPalette::_lostFocusHandler(const Windows::Foundation::IInspectable& /*sender*/,
const Windows::UI::Xaml::RoutedEventArgs& /*args*/)
{
const auto focusedElement = Input::FocusManager::GetFocusedElement(this->XamlRoot());
if (focusedElement && (focusedElement.try_as<RichTextBlock>() || focusedElement.try_as<MenuFlyoutPresenter>() || focusedElement.try_as<Popup>()))
{
// The context menu for the message don't seem to be found when the VisualTreeHelper walks the visual tree. So we check here
// if one of the focused elements is a message or a context menu of one of those messages and return early to support
// copy and select all using a mouse
return;
}
const auto flyout = _queryBox().ContextFlyout();
if (flyout && flyout.IsOpen())
{
Expand Down Expand Up @@ -404,6 +415,13 @@ namespace winrt::Microsoft::Terminal::Query::Extension::implementation
}
else if (key == VirtualKey::C && ctrlDown)
{
// Get the focused element. If it is a chat message copy its selection (if any) to the clipboard.
const auto focusedElement = Input::FocusManager::GetFocusedElement(this->XamlRoot());
if (focusedElement && focusedElement.try_as<RichTextBlock>())
{
const auto textBlock = focusedElement.as<RichTextBlock>();
textBlock.CopySelectionToClipboard();
}
_queryBox().CopySelectionToClipboard();
Copy link
Member

@lhecker lhecker Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions:

  • This copies to the clipboard twice in a row. Is that intentional?
  • Do we need to handle Ctrl+C manually here? Why is it not handled as part of the _richBlock context menu?
  • Most importantly, why don't we use our custom TextMenuFlyout class here, similar to other parts of the code base? It should support RichTextBlock.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. No, Its not intentional. :|
  2. I think it's useful. I'm sure other people would find it useful too. There was other code here that did something similar. "Get in where you fit in?".
  3. Dang, it! My gut told me something was in the project for that. Didnt look hard enough. Ill swap it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So ill basically be at the previous commit only using the TextMenuFlyout class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update pushed. Thanks for your feedback!

e.Handled(true);
}
Expand Down Expand Up @@ -440,6 +458,37 @@ namespace winrt::Microsoft::Terminal::Query::Extension::implementation
_richBlock{ nullptr }
{
_richBlock = Microsoft::Terminal::UI::Markdown::Builder::Convert(_messageContent, L"");
_richBlock.IsTextSelectionEnabled(true);
_richBlock.ContextMenuOpening([this](const Windows::Foundation::IInspectable& /*sender*/, const Windows::UI::Xaml::Controls::ContextMenuEventArgs& e) {
// If the context menu is opening we want to show our custom copy option
MenuFlyout menuFlyout;
Windows::UI::Xaml::Controls::FontIcon copyIcon;
copyIcon.Glyph(L"\uE8C8"); // Copy icon

auto copyItem = MenuFlyoutItem();
copyItem.Text(RS_(L"CopyMessage"));
copyItem.Icon(copyIcon);
copyItem.Click([this](auto&, auto&) {
const auto messageContent = this->MessageContent();

if (!messageContent.empty())
{
// Create a DataPackage and set the content to the message content
Windows::ApplicationModel::DataTransfer::DataPackage package{};
package.SetText(messageContent);
Windows::ApplicationModel::DataTransfer::Clipboard::SetContent(package);
}
});
menuFlyout.Items().Append(copyItem);

menuFlyout.ShouldConstrainToRootBounds(true);
menuFlyout.Placement(Windows::UI::Xaml::Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedRight);
menuFlyout.ShowAt(_richBlock);
e.Handled(true);
});
_richBlock.AllowFocusWhenDisabled(true);
_richBlock.AllowFocusOnInteraction(true);

const auto resources = Application::Current().Resources();
const auto textBrushObj = _isQuery ? resources.Lookup(box_value(L"TextOnAccentFillColorPrimaryBrush")) : resources.Lookup(box_value(L"TextFillColorPrimaryBrush"));
if (const auto textBrush = textBrushObj.try_as<Windows::UI::Xaml::Media::SolidColorBrush>())
Expand Down
2 changes: 0 additions & 2 deletions src/cascadia/QueryExtension/ExtensionPalette.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ namespace winrt::Microsoft::Terminal::Query::Extension::implementation
void _previewKeyDownHandler(const Windows::Foundation::IInspectable& sender,
const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _setUpAIProviderInSettings(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);

void _close();
};

Expand All @@ -67,7 +66,6 @@ namespace winrt::Microsoft::Terminal::Query::Extension::implementation
bool IsQuery() const { return _isQuery; };
winrt::hstring MessageContent() const { return _messageContent; };
winrt::Windows::UI::Xaml::Controls::RichTextBlock RichBlock() const { return _richBlock; };

TYPED_EVENT(RunCommandClicked, winrt::Microsoft::Terminal::Query::Extension::ChatMessage, winrt::hstring);

private:
Expand Down
6 changes: 5 additions & 1 deletion src/cascadia/QueryExtension/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,8 @@
<value>GitHub Copilot</value>
<comment>The metadata string to display whenever a response is received from the GitHub Copilot service provider</comment>
</data>
</root>
<data name="CopyMessage" xml:space="preserve">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we're using the existing context menu, we probably don't need this resource either!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kk

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

<value>Copy Message</value>
<comment>Copy terminal chat message</comment>
</data>
</root>
Loading