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

Search web for selected text #15539

Merged
merged 13 commits into from
Jun 21, 2023
Merged

Conversation

mpela81
Copy link
Contributor

@mpela81 mpela81 commented Jun 11, 2023

Added searchWeb command to search the selected text on the web.
Arguments:

  • queryUrl: URL of the web page to launch (the selected text will be appended to it)
  • wrapWithQuotes: whether the selected text should be wrapped with quotes (true/false)

To make the search text more "compact" and handle multi-line selections, I'm concatenating the selected lines and replacing consecutive whitespaces with a single space (we may change this with something more clever in case).

Validation Steps Performed

Manual testing with single, multi-line, block selections.
Enable/disable the wrapWithQuotes argument.

Closes #10175

@microsoft-github-policy-service microsoft-github-policy-service bot added the Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. label Jun 11, 2023
@mpela81 mpela81 marked this pull request as ready for review June 11, 2023 15:44
src/cascadia/TerminalApp/AppActionHandlers.cpp Outdated Show resolved Hide resolved
Comment on lines +3338 to +3345
bool TermControl::HasSelection() const
{
return _core.HasSelection();
}
Windows::Foundation::Collections::IVector<winrt::hstring> TermControl::SelectedText(bool trimTrailingWhitespace) const
{
return _core.SelectedText(trimTrailingWhitespace);
}
Copy link
Member

Choose a reason for hiding this comment

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

(This comment is mostly for other maintainers. I'm not trying to imply that you need to clean this up yourself.)
This addition introduces some code cruft, because all other parts of TerminalPage get their selection from TermControl when it raises events. We should consider cleaning this up in the future, so that there's only one single path to get the selection contents.

src/cascadia/TerminalSettingsModel/defaults.json Outdated Show resolved Hide resolved
Comment on lines 1041 to 1042
// make it compact by replacing consecutive whitespaces with a single space
searchText = std::regex_replace(searchText, std::wregex(LR"(\s+)"), L" ");
Copy link
Member

@lhecker lhecker Jun 12, 2023

Choose a reason for hiding this comment

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

I'm personally hoping to fully replace std::regex usage in this project with ICU in the future. One reason for this is because it uses ctype facets which are not optimized for wchar_t usage at all. It simply calls GetStringTypeW with CT_CTYPE1 for every single individual input character, which is of course not quite optimal. (They aren't optimized for char usage either, because it doesn't even support UTF-8, but that's beside the point.)

Considering that Unicode whitespace is relatively rare in Terminals, we could simply erase 0x20 whitespace only. This is fairly easy to achieve:

searchText.erase(std::unique(searchText.begin(), searchText.end(), [](const wchar_t a, const wchar_t b) {
    return a == L' ' && b == L' ';
}), text.end());

I personally believe that would already be sufficient for our use case. Otherwise, we can just keep the regex for now. It's not like this is a performance bottleneck anyways...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used the regex to also erase the artificial \r\n which are appended to the selected lines in case of block selection.

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

Thanks so much for writing this up! I haven't evaluated the code here, but I am blocking it so that we can have a team discussion about it 😄

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Jun 12, 2023
-Change how to wrap with qoutes
-Remove SO and GH search
@microsoft-github-policy-service microsoft-github-policy-service bot added Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. Product-Terminal The new Windows Terminal. labels Jun 12, 2023
@DHowett DHowett added the Needs-Discussion Something that requires a team discussion before we can proceed label Jun 12, 2023
@carlos-zamora carlos-zamora removed the Needs-Discussion Something that requires a team discussion before we can proceed label Jun 12, 2023
@lhecker
Copy link
Member

lhecker commented Jun 12, 2023

We've just discussed this PR in our team and unfortunately realized that it has a minor problem... Optimally we'd want the functionality to be added to our new context menu here:

void TerminalPage::_PopulateContextMenu(const IInspectable& sender,
const bool /*withSelection*/)
{
// withSelection can be used to add actions that only appear if there's
// selected text, like "search the web". In this initial draft, it's not
// actually augmented by the TerminalPage, so it's left commented out.
const auto& menu{ sender.try_as<MUX::Controls::CommandBarFlyout>() };
if (!menu)
{
return;
}
// Helper lambda for dispatching an ActionAndArgs onto the
// ShortcutActionDispatch. Used below to wire up each menu entry to the
// respective action.
auto weak = get_weak();
auto makeCallback = [weak](const ActionAndArgs& actionAndArgs) {
return [weak, actionAndArgs](auto&&, auto&&) {
if (auto page{ weak.get() })
{
page->_actionDispatch->DoAction(actionAndArgs);
}
};
};
auto makeItem = [&menu, &makeCallback](const winrt::hstring& label,
const winrt::hstring& icon,
const auto& action) {
AppBarButton button{};
if (!icon.empty())
{
auto iconElement = IconPathConverter::IconWUX(icon);
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
button.Icon(iconElement);
}
button.Label(label);
button.Click(makeCallback(action));
menu.SecondaryCommands().Append(button);
};
// Wire up each item to the action that should be performed. By actually
// connecting these to actions, we ensure the implementation is
// consistent. This also leaves room for customizing this menu with
// actions in the future.
makeItem(RS_(L"SplitPaneText"), L"\xF246", ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate } });
makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr });
// Only wire up "Close Pane" if there's multiple panes.
if (_GetFocusedTabImpl()->GetLeafPaneCount() > 1)
{
makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr });
}
makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } });
}

But the problem is that it doesn't use "real" actions yet. It uses fake actions which don't have any parameters. This puts us in a difficult spot: To make it work the way you built it we'd have to somehow create a settings schema where any user actions can be hooked up into the context menu automatically. Unfortunately, we most likely won't be able to figure something out in time for shipping this feature, which means we need to make it work without any action parameters.

Would you be willing to add a global settings entry here that contains the default query URL?

#define MTSM_GLOBAL_SETTINGS(X) \
X(int32_t, InitialRows, "initialRows", 30) \
X(int32_t, InitialCols, "initialCols", 80) \
X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \
X(bool, CopyOnSelect, "copyOnSelect", false) \
X(bool, FocusFollowMouse, "focusFollowMouse", false) \
X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false) \
X(bool, SoftwareRendering, "experimental.rendering.software", false) \
X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \
X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \
X(bool, ForceVTInput, "experimental.input.forceVT", false) \
X(bool, TrimBlockSelection, "trimBlockSelection", true) \
X(bool, DetectURLs, "experimental.detectURLs", true) \
X(bool, AlwaysShowTabs, "alwaysShowTabs", true) \
X(Model::NewTabPosition, NewTabPosition, "newTabPosition", Model::NewTabPosition::AfterLastTab) \
X(bool, ShowTitleInTitlebar, "showTerminalTitleInTitlebar", true) \
X(bool, ConfirmCloseAllTabs, "confirmCloseAllTabs", true) \
X(Model::ThemePair, Theme, "theme") \
X(hstring, Language, "language") \
X(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, "tabWidthMode", winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal) \
X(bool, UseAcrylicInTabRow, "useAcrylicInTabRow", false) \
X(bool, ShowTabsInTitlebar, "showTabsInTitlebar", true) \
X(bool, InputServiceWarning, "inputServiceWarning", true) \
X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, "copyFormatting", 0) \
X(bool, WarnAboutLargePaste, "largePasteWarning", true) \
X(bool, WarnAboutMultiLinePaste, "multiLinePasteWarning", true) \
X(Model::LaunchPosition, InitialPosition, "initialPosition", nullptr, nullptr) \
X(bool, CenterOnLaunch, "centerOnLaunch", false) \
X(Model::FirstWindowPreference, FirstWindowPreference, "firstWindowPreference", FirstWindowPreference::DefaultProfile) \
X(Model::LaunchMode, LaunchMode, "launchMode", LaunchMode::DefaultMode) \
X(bool, SnapToGridOnResize, "snapToGridOnResize", true) \
X(bool, DebugFeaturesEnabled, "debugFeatures", debugFeaturesDefault) \
X(bool, StartOnUserLogin, "startOnUserLogin", false) \
X(bool, AlwaysOnTop, "alwaysOnTop", false) \
X(bool, AutoHideWindow, "autoHideWindow", false) \
X(Model::TabSwitcherMode, TabSwitcherMode, "tabSwitcherMode", Model::TabSwitcherMode::InOrder) \
X(bool, DisableAnimations, "disableAnimations", false) \
X(hstring, StartupActions, "startupActions", L"") \
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
X(bool, ShowAdminShield, "showAdminShield", true) \
X(bool, TrimPaste, "trimPaste", true) \
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
X(bool, AllowHeadless, "compatibility.allowHeadless", false) \
X(bool, IsolatedMode, "compatibility.isolatedMode", false)

I'm not sure what a good name would be, but I suppose something like "searchWebQueryUrlDefault" would work. That way any action without a "queryUrl" parameter could use that as the fallback. To make it work correctly, you'd have to remove the "queryUrl" parameter from the action in src/cascadia/TerminalSettingsModel/defaults.json. If you're interested in it, you could also make the necessary modifications to TerminalPage::_PopulateContextMenu.

Let me know what you think about this! Do you feel like this is a good idea? If you're short on time, we'd be happy to accept the PR as is of course and make any remaining, necessary changes ourselves. 🙂

@mpela81
Copy link
Contributor Author

mpela81 commented Jun 13, 2023

Would you be willing to add a global settings entry here that contains the default query URL?

#define MTSM_GLOBAL_SETTINGS(X) \
X(int32_t, InitialRows, "initialRows", 30) \
X(int32_t, InitialCols, "initialCols", 80) \
X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \
X(bool, CopyOnSelect, "copyOnSelect", false) \
X(bool, FocusFollowMouse, "focusFollowMouse", false) \
X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false) \
X(bool, SoftwareRendering, "experimental.rendering.software", false) \
X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \
X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \
X(bool, ForceVTInput, "experimental.input.forceVT", false) \
X(bool, TrimBlockSelection, "trimBlockSelection", true) \
X(bool, DetectURLs, "experimental.detectURLs", true) \
X(bool, AlwaysShowTabs, "alwaysShowTabs", true) \
X(Model::NewTabPosition, NewTabPosition, "newTabPosition", Model::NewTabPosition::AfterLastTab) \
X(bool, ShowTitleInTitlebar, "showTerminalTitleInTitlebar", true) \
X(bool, ConfirmCloseAllTabs, "confirmCloseAllTabs", true) \
X(Model::ThemePair, Theme, "theme") \
X(hstring, Language, "language") \
X(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, "tabWidthMode", winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal) \
X(bool, UseAcrylicInTabRow, "useAcrylicInTabRow", false) \
X(bool, ShowTabsInTitlebar, "showTabsInTitlebar", true) \
X(bool, InputServiceWarning, "inputServiceWarning", true) \
X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, "copyFormatting", 0) \
X(bool, WarnAboutLargePaste, "largePasteWarning", true) \
X(bool, WarnAboutMultiLinePaste, "multiLinePasteWarning", true) \
X(Model::LaunchPosition, InitialPosition, "initialPosition", nullptr, nullptr) \
X(bool, CenterOnLaunch, "centerOnLaunch", false) \
X(Model::FirstWindowPreference, FirstWindowPreference, "firstWindowPreference", FirstWindowPreference::DefaultProfile) \
X(Model::LaunchMode, LaunchMode, "launchMode", LaunchMode::DefaultMode) \
X(bool, SnapToGridOnResize, "snapToGridOnResize", true) \
X(bool, DebugFeaturesEnabled, "debugFeatures", debugFeaturesDefault) \
X(bool, StartOnUserLogin, "startOnUserLogin", false) \
X(bool, AlwaysOnTop, "alwaysOnTop", false) \
X(bool, AutoHideWindow, "autoHideWindow", false) \
X(Model::TabSwitcherMode, TabSwitcherMode, "tabSwitcherMode", Model::TabSwitcherMode::InOrder) \
X(bool, DisableAnimations, "disableAnimations", false) \
X(hstring, StartupActions, "startupActions", L"") \
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
X(bool, ShowAdminShield, "showAdminShield", true) \
X(bool, TrimPaste, "trimPaste", true) \
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
X(bool, AllowHeadless, "compatibility.allowHeadless", false) \
X(bool, IsolatedMode, "compatibility.isolatedMode", false)

I'm not sure what a good name would be, but I suppose something like "searchWebQueryUrlDefault" would work. That way any action without a "queryUrl" parameter could use that as the fallback. To make it work correctly, you'd have to remove the "queryUrl" parameter from the action in src/cascadia/TerminalSettingsModel/defaults.json.

@lhecker How would this work? Let's say I add (if I get it correctly):

X(hstring, SearchWebQueryUrlDefault, "queryUrl", L"https://www.bing.com/search?q=")

Does it create a global setting, which then we will have to look up if the "queryUrl" parameter is empty here?

    void TerminalPage::_HandleSearchForText(const IInspectable& /*sender*/,
                                            const ActionEventArgs& args)
    {
        if (args)
        {
            if (const auto& realArgs = args.ActionArgs().try_as<SearchForTextArgs>())
            {
                const auto queryUrl = realArgs.QueryUrl();

https://github.com/microsoft/terminal/blob/dd20c927c9cc16ea8b0dc935b19f172df62e1c10/src/cascadia/TerminalApp/AppActionHandlers.cpp#L1024C1-L1031

@lhecker
Copy link
Member

lhecker commented Jun 13, 2023

Yep, mostly. You can see how it works for "ShowAdminShield" for instance. It requires an additional modification in GlobalAppSettings.idl.

The PR is already fine as is, and I don't want to burden a contributor with making any complex changes. So, if you have any trouble making this change, please feel free to just leave the PR as it is, and we'll fix it in post. After all, we only added the context menu quite recently ourselves. 🙂

@mpela81
Copy link
Contributor Author

mpela81 commented Jun 13, 2023

Ok I've tried to do that, if I've misinterpreted it I can easily revert and let you finish this off 😄

@carlos-zamora carlos-zamora assigned DHowett and unassigned lhecker Jun 13, 2023
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

I love this! Thank you so much for doing the Default thing.

I'm pretty excited about what we can build on top of this. 😄

Just a couple notes!

// make it compact by replacing consecutive whitespaces with a single space
searchText = std::regex_replace(searchText, std::wregex(LR"(\s+)"), L" ");

if (realArgs.WrapWithQuotes())
Copy link
Member

Choose a reason for hiding this comment

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

alright, so I've got an idea here. It kills two of my concerns!

When you set up a search engine shortcut in Firefox Microsoft Edge (:wink:) it gives you a special string, %s, that you can insert into the query.

image

If we use %s just like them, we get some benefits:

  1. we don't have to append, so people can put the query argument anywhere
  2. we don't need wrapWithQuotes, because people can put quotes directly in the URL!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to use %s

{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"SearchForTextCommandKey")),
QueryUrl().c_str())
Copy link
Member

Choose a reason for hiding this comment

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

note if we go with the %s idea we may want to revisit how this is phrased

Copy link
Member

Choose a reason for hiding this comment

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

We could consider automatically generating a short version by parsing the URL and turning it into like.. Search {Uri().Hostname()} => `Search google.com"

(it is not unambiguous, but it could be interesting?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it, but should we be careful if Uri could throw if queryUrl is malformed?

src/cascadia/TerminalSettingsModel/MTSMSettings.h Outdated Show resolved Hide resolved
@microsoft-github-policy-service microsoft-github-policy-service bot added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 16, 2023
Marco Pelagatti added 4 commits June 16, 2023 18:34
…minal into feat/10175-web-search-text-2

# Conflicts:
#	src/cascadia/TerminalApp/AppActionHandlers.cpp
#	src/cascadia/TerminalSettingsModel/ActionArgs.cpp
#	src/cascadia/TerminalSettingsModel/MTSMSettings.h
@microsoft-github-policy-service microsoft-github-policy-service bot removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 20, 2023
@mpela81
Copy link
Contributor Author

mpela81 commented Jun 20, 2023

trying with the context menu as well:

Search

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

Thanks so much for doing this! It looks awesome, and I'm very pleased with how it shaped up!

Mike will be happy that his WIP branch turned into something too 😄

@DHowett DHowett merged commit 40963c7 into microsoft:main Jun 21, 2023
qqkookie pushed a commit to qqkookie/terminal that referenced this pull request Jun 22, 2023
This PR adds a `searchWeb` command to search the selected text on the web.
Arguments:
- `queryUrl`: URL of the web page to launch (the selected text will be
inserted where the first `%s` is found in the query string)

To make the search text more "compact" and handle multi-line selections,
I'm concatenating the selected lines and replacing consecutive
whitespaces with a single space (we may change this with something more
clever in case).

## Validation Steps Performed
Manual testing with single, multi-line, block selections.

Closes microsoft#10175

---------

Co-authored-by: Marco Pelagatti <marco.pelagatti@iongroup.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
@mpela81 mpela81 deleted the feat/10175-web-search-text branch July 3, 2023 10:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

search web for highlighted text feature?
4 participants