From 4c4632701540265c500a82e2584dd5bebfcb49b1 Mon Sep 17 00:00:00 2001 From: donno2048 Date: Tue, 17 Dec 2019 07:52:37 -0800 Subject: [PATCH] Search - add search box control and implement search experience (#3590) This is the PR for feature Search: #605 This PR includes the newly introduced SearchBoxControl in TermControl dir, which is the search bar for the search experience. And the codes that enable Search in Windows Terminal. The PR that migrates the Conhost search module: https://github.com/microsoft/terminal/pull/3279 Spec (still actively updating): https://github.com/microsoft/terminal/pull/3299 ## PR Checklist * [x] Closes #605 * [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx These functionalities are included in the search experience. 1. Search in Terminal text buffer. 2. Automatic wrap-around. 3. Search up or down switch by clicking different buttons. 4. Search case sensitively/insensitively by clicking a button. S. Move the search box to the top/bottom by clicking a button. 6. Close by clicking 'X'. 7. Open search by ctrl + F. When the searchbox is open, the user could still interact with the terminal by clicking the terminal input area. While I already have the search functionalities, currently there are still some known to-do works and I will keep updating my PR: 1. Optimize the search box UI, this includes: 1) Theme adaptation. The search box background and font color should change according to the theme, 2) Add background. Currently the elements in search box are all transparent. However, we need a background. 3) Move button should be highlighted once clicked. 2. Accessibility: search process should be able to performed without mouse. Once the search box is focused, the user should be able to navigate between all interactive elements on the searchbox using keyboard. To test: 1. checkout this branch. 2. Build the project. 3. Start Windows Terminal and press Ctrl+F 4. The search box should appear on the top right corner. --- doc/cascadia/SettingsSchema.md | 3 +- src/buffer/out/search.cpp | 34 +++- src/buffer/out/search.h | 2 +- .../TerminalApp/AppActionHandlers.cpp | 8 +- .../AppKeyBindingsSerialization.cpp | 2 + .../TerminalApp/ShortcutActionDispatch.cpp | 5 + .../TerminalApp/ShortcutActionDispatch.h | 1 + .../TerminalApp/ShortcutActionDispatch.idl | 2 + src/cascadia/TerminalApp/TerminalPage.cpp | 16 +- src/cascadia/TerminalApp/TerminalPage.h | 3 + src/cascadia/TerminalApp/defaults.json | 1 + .../TerminalControl/SearchBoxControl.cpp | 186 ++++++++++++++++++ .../TerminalControl/SearchBoxControl.h | 63 ++++++ .../TerminalControl/SearchBoxControl.idl | 17 ++ .../TerminalControl/SearchBoxControl.xaml | 183 +++++++++++++++++ src/cascadia/TerminalControl/TermControl.cpp | 110 +++++++++++ src/cascadia/TerminalControl/TermControl.h | 18 +- src/cascadia/TerminalControl/TermControl.idl | 2 + .../TerminalControl/TerminalControl.vcxproj | 23 +++ .../TerminalControl.vcxproj.filters | 6 + src/cascadia/TerminalControl/pch.h | 1 + src/cascadia/TerminalCore/Terminal.cpp | 18 +- src/cascadia/TerminalCore/Terminal.hpp | 6 +- .../TerminalCore/TerminalSelection.cpp | 19 +- .../TerminalCore/terminalrenderdata.cpp | 42 +++- src/host/renderData.cpp | 13 ++ src/host/renderData.hpp | 1 + src/types/IBaseData.h | 1 + 28 files changed, 757 insertions(+), 29 deletions(-) create mode 100644 src/cascadia/TerminalControl/SearchBoxControl.cpp create mode 100644 src/cascadia/TerminalControl/SearchBoxControl.h create mode 100644 src/cascadia/TerminalControl/SearchBoxControl.idl create mode 100644 src/cascadia/TerminalControl/SearchBoxControl.xaml diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md index dee8307d..a7c8731c 100644 --- a/doc/cascadia/SettingsSchema.md +++ b/doc/cascadia/SettingsSchema.md @@ -137,7 +137,8 @@ Commands listed below are per the implementation in [`src/cascadia/TerminalApp/A - moveFocusRight - moveFocusUp - moveFocusDown -- toggleFullscreen +- toggleFullscreen +- find ## Example Keys - ctrl+1 diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 67f85540..2b662fef 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -97,11 +97,7 @@ bool Search::FindNext() // - Takes the found word and selects it in the screen buffer void Search::Select() const { - // Only select if we've found something. - if (_coordSelStart != _coordSelEnd) - { - _uiaData.SelectNewRegion(_coordSelStart, _coordSelEnd); - } + _uiaData.SelectNewRegion(_coordSelStart, _coordSelEnd); } // Routine Description: @@ -142,6 +138,7 @@ std::pair Search::GetFoundLocation() const noexcept COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction) { const auto& textBuffer = uiaData.GetTextBuffer(); + const COORD textBufferEndPosition = uiaData.GetTextBufferEndPosition(); if (uiaData.IsSelectionActive()) { auto anchor = uiaData.GetSelectionAnchor(); @@ -152,6 +149,10 @@ COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction) else { textBuffer.GetSize().DecrementInBoundsCircular(anchor); + // If the selection starts at (0, 0), we need to make sure + // it does not exceed the text buffer end position + anchor.X = std::min(textBufferEndPosition.X, anchor.X); + anchor.Y = std::min(textBufferEndPosition.Y, anchor.Y); } return anchor; } @@ -163,8 +164,7 @@ COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction) } else { - const auto bufferSize = textBuffer.GetSize().Dimensions(); - return { bufferSize.X - 1, bufferSize.Y - 1 }; + return textBufferEndPosition; } } } @@ -293,6 +293,26 @@ void Search::_UpdateNextPosition() { THROW_HR(E_NOTIMPL); } + + // To reduce wrap-around time, if the next position is larger than + // the end position of the written text + // We put the next position to: + // Forward: (0, 0) + // Backward: the position of the end of the text buffer + const COORD bufferEndPosition = _uiaData.GetTextBufferEndPosition(); + + if (_coordNext.Y > bufferEndPosition.Y || + (_coordNext.Y == bufferEndPosition.Y && _coordNext.X > bufferEndPosition.X)) + { + if (_direction == Direction::Forward) + { + _coordNext = { 0 }; + } + else + { + _coordNext = bufferEndPosition; + } + } } // Routine Description: diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index 928111c0..3822e3ff 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -59,7 +59,7 @@ class Search final private: wchar_t _ApplySensitivity(const wchar_t wch) const noexcept; - bool Search::_FindNeedleInHaystackAt(const COORD pos, COORD& start, COORD& end) const; + bool _FindNeedleInHaystackAt(const COORD pos, COORD& start, COORD& end) const; bool _CompareChars(const std::wstring_view one, const std::wstring_view two) const noexcept; void _UpdateNextPosition(); diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 68a29622..2c4b132d 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -214,6 +214,13 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_HandleFind(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _Find(); + args.Handled(true); + } + void TerminalPage::_HandleResetFontSize(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { @@ -228,5 +235,4 @@ namespace winrt::TerminalApp::implementation _ToggleFullscreen(); args.Handled(true); } - } diff --git a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp index 26dc7f27..8cec9a55 100644 --- a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp @@ -77,6 +77,7 @@ static constexpr std::string_view MoveFocusLeftKey{ "moveFocusLeft" }; // Legacy static constexpr std::string_view MoveFocusRightKey{ "moveFocusRight" }; // Legacy static constexpr std::string_view MoveFocusUpKey{ "moveFocusUp" }; // Legacy static constexpr std::string_view MoveFocusDownKey{ "moveFocusDown" }; // Legacy +static constexpr std::string_view FindKey{ "find" }; static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" }; // Specifically use a map here over an unordered_map. We want to be able to @@ -142,6 +143,7 @@ static const std::map> commandName { ToggleFullscreenKey, ShortcutAction::ToggleFullscreen }, { SplitPaneKey, ShortcutAction::SplitPane }, { UnboundKey, ShortcutAction::Invalid }, + { FindKey, ShortcutAction::Find }, }; // Function Description: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index cf845c2c..bac26872 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -180,6 +180,11 @@ namespace winrt::TerminalApp::implementation _AdjustFontSizeHandlers(*this, *eventArgs); break; } + case ShortcutAction::Find: + { + _FindHandlers(*this, *eventArgs); + break; + } case ShortcutAction::ResetFontSize: { _ResetFontSizeHandlers(*this, *eventArgs); diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.h b/src/cascadia/TerminalApp/ShortcutActionDispatch.h index bb2b72e5..f7e7ce05 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.h +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.h @@ -44,6 +44,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(ScrollDownPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); // clang-format on diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl index 01a065e6..6df2d58b 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl @@ -61,6 +61,7 @@ namespace TerminalApp MoveFocusRight, // Legacy MoveFocusUp, // Legacy MoveFocusDown, // Legacy + Find, ToggleFullscreen, OpenSettings }; @@ -97,6 +98,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler ScrollDownPage; event Windows.Foundation.TypedEventHandler OpenSettings; event Windows.Foundation.TypedEventHandler ResizePane; + event Windows.Foundation.TypedEventHandler Find; event Windows.Foundation.TypedEventHandler MoveFocus; event Windows.Foundation.TypedEventHandler ToggleFullscreen; } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index dbc15059..52fe18f3 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -630,7 +630,6 @@ namespace winrt::TerminalApp::implementation // Hook up the ShortcutActionDispatch object's events to our handlers. // They should all be hooked up here, regardless of whether or not // there's an actual keychord for them. - _actionDispatch->OpenNewTabDropdown({ this, &TerminalPage::_HandleOpenNewTabDropdown }); _actionDispatch->DuplicateTab({ this, &TerminalPage::_HandleDuplicateTab }); _actionDispatch->CloseTab({ this, &TerminalPage::_HandleCloseTab }); @@ -651,6 +650,7 @@ namespace winrt::TerminalApp::implementation _actionDispatch->MoveFocus({ this, &TerminalPage::_HandleMoveFocus }); _actionDispatch->CopyText({ this, &TerminalPage::_HandleCopyText }); _actionDispatch->AdjustFontSize({ this, &TerminalPage::_HandleAdjustFontSize }); + _actionDispatch->Find({ this, &TerminalPage::_HandleFind }); _actionDispatch->ResetFontSize({ this, &TerminalPage::_HandleResetFontSize }); _actionDispatch->ToggleFullscreen({ this, &TerminalPage::_HandleToggleFullscreen }); } @@ -1417,6 +1417,20 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Called when the user tries to do a search using keybindings. + // This will tell the current focused terminal control to create + // a search box and enable find process. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_Find() + { + const auto termControl = _GetActiveControl(); + termControl.CreateSearchBoxControl(); + } + // Method Description: // - Toggles fullscreen mode. Hides the tab row, and raises our // ToggleFullscreen event. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 11467e9f..56beac28 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -122,6 +122,8 @@ namespace winrt::TerminalApp::implementation void _OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e); void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs); + void _Find(); + void _RefreshUIForSettingsReload(); void _ToggleFullscreen(); @@ -148,6 +150,7 @@ namespace winrt::TerminalApp::implementation void _HandleCopyText(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleCloseWindow(const IInspectable&, const TerminalApp::ActionEventArgs& args); void _HandleAdjustFontSize(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleFind(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleResetFontSize(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleToggleFullscreen(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); #pragma endregion diff --git a/src/cascadia/TerminalApp/defaults.json b/src/cascadia/TerminalApp/defaults.json index c3a311c0..569bcd23 100644 --- a/src/cascadia/TerminalApp/defaults.json +++ b/src/cascadia/TerminalApp/defaults.json @@ -245,6 +245,7 @@ { "command": { "action": "switchToTab", "index": 6 }, "keys": ["ctrl+alt+7"] }, { "command": { "action": "switchToTab", "index": 7 }, "keys": ["ctrl+alt+8"] }, { "command": { "action": "switchToTab", "index": 8 }, "keys": ["ctrl+alt+9"] }, + { "command": "find", "keys": [ "ctrl+shift+f" ] }, { "command": "toggleFullscreen", "keys": [ "alt+enter" ] }, { "command": "toggleFullscreen", "keys": [ "f11" ] } ] diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp new file mode 100644 index 00000000..7f5e7a0c --- /dev/null +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT license. + +#include "pch.h" +#include "SearchBoxControl.h" +#include "SearchBoxControl.g.cpp" + +using namespace winrt; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + // Constructor + SearchBoxControl::SearchBoxControl() : + _goForward(false) + { + InitializeComponent(); + + this->CharacterReceived({ this, &SearchBoxControl::_CharacterHandler }); + + _textBox = TextBox(); + + if (_textBox) + _focusableElements.insert(_textBox); + _focusableElements.insert(CloseButton()); + _focusableElements.insert(CaseSensitivityButton()); + _focusableElements.insert(GoForwardButton()); + _focusableElements.insert(GoBackwardButton()); + } + + // Method Description: + // - Getter for _goForward + // Arguments: + // - + // Return Value: + // - bool: the value of _goForward + bool SearchBoxControl::GoForward() + { + return _goForward; + } + + // Method Description: + // - Get the current state of the case button + // Arguments: + // - + // Return Value: + // - bool: whether the case button is checked (sensitive) + // or not + bool SearchBoxControl::IsCaseSensitive() + { + return _caseButton.IsChecked().GetBoolean(); + } + + // Method Description: + // - Handler for pressing Enter on TextBox, trigger + // text search + // Arguments: + // - sender: not used + // - e: event data + // Return Value: + // - + void SearchBoxControl::TextBoxKeyDown(winrt::Windows::Foundation::IInspectable const& /*sender*/, Input::KeyRoutedEventArgs const& e) + { + if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Enter) + { + auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift); + if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down)) + { + // We do not want the direction flag to change permanately + _goForward = !_goForward; + _SearchHandlers(*this, _textBox.Text()); + _goForward = !_goForward; + } + else + { + _SearchHandlers(*this, _textBox.Text()); + } + e.Handled(true); + } + } + + // Method Description: + // - Handler for pressing Enter on TextBox, trigger + // text search + // Arguments: + // - + // Return Value: + // - + void SearchBoxControl::SetFocusOnTextbox() + { + if (_textBox) + { + Input::FocusManager::TryFocusAsync(_textBox, FocusState::Keyboard); + _textBox.SelectAll(); + } + } + + // Method Description: + // - Check if the current focus is on any element within the + // search box + // Arguments: + // - + // Return Value: + // - bool: whether the current focus is on the search box + bool SearchBoxControl::ContainsFocus() + { + auto focusedElement = Input::FocusManager::GetFocusedElement(this->XamlRoot()); + if (_focusableElements.count(focusedElement) > 0) + { + return true; + } + + return false; + } + + // Method Description: + // - Handler for clicking the GoBackward button. This change the value of _goForward, + // mark GoBackward button as checked and ensure GoForward button + // is not checked + // Arguments: + // - sender: not used + // - e: not used + // Return Value: + // - + void SearchBoxControl::GoBackwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/) + { + _goForward = false; + GoBackwardButton().IsChecked(true); + if (GoForwardButton().IsChecked()) + { + GoForwardButton().IsChecked(false); + } + + // kick off search + _SearchHandlers(*this, _textBox.Text()); + } + + // Method Description: + // - Handler for clicking the GoForward button. This change the value of _goForward, + // mark GoForward button as checked and ensure GoBackward button + // is not checked + // Arguments: + // - sender: not used + // - e: not used + // Return Value: + // - + void SearchBoxControl::GoForwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/) + { + _goForward = true; + GoForwardButton().IsChecked(true); + if (GoBackwardButton().IsChecked()) + { + GoBackwardButton().IsChecked(false); + } + + // kick off search + _SearchHandlers(*this, _textBox.Text()); + } + + // Method Description: + // - Handler for clicking the close button. This destructs the + // search box object in TermControl + // Arguments: + // - sender: not used + // - e: event data + // Return Value: + // - + void SearchBoxControl::CloseClick(winrt::Windows::Foundation::IInspectable const& /*sender*/, RoutedEventArgs const& e) + { + _ClosedHandlers(*this, e); + } + + // Method Description: + // - To avoid Characters input bubbling up to terminal, we implement this handler here, + // simply mark the key input as handled + // Arguments: + // - sender: not used + // - e: event data + // Return Value: + // - + void SearchBoxControl::_CharacterHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, Input::CharacterReceivedRoutedEventArgs const& e) + { + e.Handled(true); + } +} diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h new file mode 100644 index 00000000..cb1aeb17 --- /dev/null +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -0,0 +1,63 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- SearchBoxControl.cpp + +Abstract: +- the search dialog component used in Terminal Search + +Author(s): +- Kaiyu Wang (kawa) 11-27-2019 + +--*/ + +#pragma once +#include "winrt/Windows.UI.Xaml.h" +#include "winrt/Windows.UI.Xaml.Controls.h" +#include "../../cascadia/inc/cppwinrt_utils.h" + +#include "SearchBoxControl.g.h" + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + struct SearchBoxControl : SearchBoxControlT + { + SearchBoxControl(); + + void TextBoxKeyDown(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); + + bool GoForward(); + bool IsCaseSensitive(); + + void SetFocusOnTextbox(); + bool ContainsFocus(); + + void GoBackwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); + void GoForwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); + void CloseClick(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + TYPED_EVENT(Search, TerminalControl::SearchBoxControl, winrt::hstring); + TYPED_EVENT(Closed, TerminalControl::SearchBoxControl, Windows::UI::Xaml::RoutedEventArgs); + + private: + bool _goForward; // The direction of the search, controlled by the buttons with arrows + + winrt::Windows::UI::Xaml::Controls::Primitives::ToggleButton _goForwardButton; + winrt::Windows::UI::Xaml::Controls::Primitives::ToggleButton _goBackwardButton; + winrt::Windows::UI::Xaml::Controls::Primitives::ToggleButton _caseButton; + winrt::Windows::UI::Xaml::Controls::TextBox _textBox; + + std::unordered_set _focusableElements; + + void _CharacterHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e); + }; +} + +namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation +{ + struct SearchBoxControl : SearchBoxControlT + { + }; +} diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl new file mode 100644 index 00000000..dfe517ad --- /dev/null +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.TerminalControl +{ + [default_interface] runtimeclass SearchBoxControl : Windows.UI.Xaml.Controls.UserControl + { + SearchBoxControl(); + Boolean GoForward { get; }; + Boolean IsCaseSensitive { get; }; + void SetFocusOnTextbox(); + Boolean ContainsFocus(); + + event Windows.Foundation.TypedEventHandler Search; + event Windows.Foundation.TypedEventHandler Closed; + } +} diff --git a/src/cascadia/TerminalControl/SearchBoxControl.xaml b/src/cascadia/TerminalControl/SearchBoxControl.xaml new file mode 100644 index 00000000..1981759e --- /dev/null +++ b/src/cascadia/TerminalControl/SearchBoxControl.xaml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index a8692593..3e9d5486 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -66,6 +66,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _cursorTimer{}, _lastMouseClick{}, _lastMouseClickPos{}, + _searchBox{ nullptr }, _tsfInputControl{ nullptr } { _EnsureStaticInitialization(); @@ -163,6 +164,101 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation }); } + // Method Description: + // - Create the SearchBoxControl object, and attach it + // to the Terminal Control root + // Arguments: + // - + // Return Value: + // - + void TermControl::CreateSearchBoxControl() + { + if (!_searchBox) + { + _searchBox = winrt::make_self(); + _searchBox->HorizontalAlignment(HorizontalAlignment::Right); + _searchBox->VerticalAlignment(VerticalAlignment::Top); + // We need to make sure the searchbox does not overlap + // with the scroll bar + Thickness searchBoxPadding = { 0, 0, _scrollBar.ActualWidth(), 0 }; + _searchBox->Margin(searchBoxPadding); + + _root.Children().Append(*_searchBox); + + // Event handlers + _searchBox->Search({ get_weak(), &TermControl::_Search }); + _searchBox->Closed({ get_weak(), &TermControl::_CloseSearchBoxControl }); + } + + _searchBox->SetFocusOnTextbox(); + } + + // Method Description: + // - Search text in text buffer. This is triggered if the user click + // search button or press enter. + // Arguments: + // - IInspectable: not used + // - text: the text to search + // Return Value: + // - + void TermControl::_Search(const winrt::Windows::Foundation::IInspectable&, const winrt::hstring& text) + { + if (text.size() == 0) + { + return; + } + + const Search::Direction direction = _searchBox->GoForward() ? + Search::Direction::Forward : + Search::Direction::Backward; + + const Search::Sensitivity sensitivity = _searchBox->IsCaseSensitive() ? + Search::Sensitivity::CaseSensitive : + Search::Sensitivity::CaseInsensitive; + + Search search(*GetUiaData(), text.c_str(), direction, sensitivity); + auto lock = _terminal->LockForWriting(); + if (search.FindNext()) + { + _terminal->SetBoxSelection(false); + search.Select(); + _renderer->TriggerSelection(); + } + } + + // Method Description: + // - The handler for the close button in the search box. + // This is a wrapper method that calls _CloseSearchBoxControlHelper(). + // Arguments: + // - IInspectable: not used + // - RoutedEventArgs: not used + // Return Value: + // - + void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/) + { + _CloseSearchBoxControlHelper(); + } + + // Method Description: + // - The helper method that removes the SearchBoxControl + // object from the XAML tree, reset smart pointer and + // set focus back to Terminal + // Arguments: + // - + // Return Value: + // - + void TermControl::_CloseSearchBoxControlHelper() + { + unsigned int idx; + _root.Children().IndexOf(*_searchBox, idx); + _root.Children().RemoveAt(idx); + + _searchBox = nullptr; + + // Set focus back to terminal control + this->Focus(FocusState::Programmatic); + } + // Method Description: // - Given new settings for this profile, applies the settings to the current terminal. // Arguments: @@ -683,6 +779,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void TermControl::_KeyDownHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, Input::KeyRoutedEventArgs const& e) { + // If Esc is pressed and the search box is opened, we close the search box + if (_searchBox && e.OriginalKey() == winrt::Windows::System::VirtualKey::Escape) + { + _CloseSearchBoxControlHelper(); + return; + } + + // If the current focused element is a child element of searchbox, + // we do not send this event up to terminal + if (_searchBox && _searchBox->ContainsFocus()) + { + return; + } + // mark event as handled and do nothing if... // - closing // - key modifier is pressed diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 68c8a1ab..ae24b103 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -12,7 +12,9 @@ #include "../../renderer/dx/DxRenderer.hpp" #include "../../renderer/uia/UiaRenderer.hpp" #include "../../cascadia/TerminalCore/Terminal.hpp" +#include "../buffer/out/search.h" #include "cppwinrt_utils.h" +#include "SearchBoxControl.h" namespace winrt::Microsoft::Terminal::TerminalControl::implementation { @@ -75,6 +77,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void ResetFontSize(); void SwapChainChanged(); + + void CreateSearchBoxControl(); + ~TermControl(); Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); @@ -105,6 +110,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation Windows::UI::Xaml::Controls::Image _bgImageLayer; Windows::UI::Xaml::Controls::SwapChainPanel _swapChainPanel; Windows::UI::Xaml::Controls::Primitives::ScrollBar _scrollBar; + + winrt::com_ptr _searchBox; + TSFInputControl _tsfInputControl; event_token _connectionOutputEventToken; @@ -164,8 +172,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void _BackgroundColorChanged(const uint32_t color); bool _InitializeTerminal(); void _UpdateFont(); - void _SetFontSize(int fontSize); void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); + void _SetFontSize(int fontSize); void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e); void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); void _PointerMovedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); @@ -206,10 +214,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime); double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const; + void _Search(const winrt::Windows::Foundation::IInspectable& sender, const winrt::hstring& text); + void _CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void _CloseSearchBoxControlHelper(); + // TSFInputControl Handlers void _CompositionCompleted(winrt::hstring text); - void _CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs); - void _FontInfoHandler(const IInspectable& /*sender*/, const FontInfoEventArgs& eventArgs); + void _CurrentCursorPositionHandler(const IInspectable& sender, const CursorPositionEventArgs& eventArgs); + void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs); }; } diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index bfa3bdab..10790149 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -50,6 +50,8 @@ namespace Microsoft.Terminal.TerminalControl Int32 GetViewHeight(); event ScrollPositionChangedEventArgs ScrollPositionChanged; + void CreateSearchBoxControl(); + void AdjustFontSize(Int32 fontSizeDelta); void ResetFontSize(); } diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj b/src/cascadia/TerminalControl/TerminalControl.vcxproj index 5ec8cd42..3d1d029a 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj @@ -10,6 +10,15 @@ Console true + + 3