diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index 7dec8d2dc04..6f07ce9c534 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -327,3 +327,18 @@ til::point_span Microsoft::Console::ICU::BufferRangeFromMatch(UText* ut, URegula return ret; } + +UText Microsoft::Console::ICU::UTextForWrappableRow(const TextBuffer& textBuffer, til::CoordType& row) noexcept +{ + const auto startRow = row; + auto length = 0; + while (textBuffer.GetRowByOffset(row).WasWrapForced()) + { + row++; + length += textBuffer.GetRowByOffset(row).size(); + } + length += textBuffer.GetRowByOffset(row).size(); + const auto ut = UTextFromTextBuffer(textBuffer, startRow, row + 1); + + return ut; +} diff --git a/src/buffer/out/UTextAdapter.h b/src/buffer/out/UTextAdapter.h index c8c325143ef..168967f193a 100644 --- a/src/buffer/out/UTextAdapter.h +++ b/src/buffer/out/UTextAdapter.h @@ -14,4 +14,5 @@ namespace Microsoft::Console::ICU UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd) noexcept; unique_uregex CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept; til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re); + UText UTextForWrappableRow(const TextBuffer& textBuffer, til::CoordType& row) noexcept; } diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index fd8942e0bac..a2fc14ef9bf 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -5,6 +5,7 @@ #include "search.h" #include "textBuffer.hpp" +#include "UTextAdapter.h" using namespace Microsoft::Console::Types; @@ -33,6 +34,17 @@ bool Search::ResetIfStale(Microsoft::Console::Render::IRenderData& renderData, c return true; } +void Search::QuickSelectRegex(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive) +{ + _renderData = &renderData; + _needle = needle; + _caseInsensitive = caseInsensitive; + + _results = _regexSearch(renderData, needle, caseInsensitive); + _index = 0; + _step = 1; +} + void Search::MoveToCurrentSelection() { if (_renderData->IsSelectionActive()) @@ -162,3 +174,71 @@ ptrdiff_t Search::CurrentMatch() const noexcept { return _index; } + +std::vector Search::_regexSearch(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive) +{ + std::vector results; + UErrorCode status = U_ZERO_ERROR; + uint32_t flags = 0; + WI_SetFlagIf(flags, UREGEX_CASE_INSENSITIVE, caseInsensitive); + const auto& textBuffer = renderData.GetTextBuffer(); + + const auto rowCount = textBuffer.GetLastNonSpaceCharacter().y + 1; + + const auto regex = Microsoft::Console::ICU::CreateRegex(needle, flags, &status); + if (U_FAILURE(status)) + { + return results; + } + + const auto viewPortWidth = textBuffer.GetSize().Width(); + + for (int32_t i = 0; i < rowCount; i++) + { + const auto startRow = i; + auto uText = Microsoft::Console::ICU::UTextForWrappableRow(textBuffer, i); + + uregex_setUText(regex.get(), &uText, &status); + if (U_FAILURE(status)) + { + return results; + } + + if (uregex_find(regex.get(), -1, &status)) + { + do + { + const int32_t icuStart = uregex_start(regex.get(), 0, &status); + int32_t icuEnd = uregex_end(regex.get(), 0, &status); + icuEnd--; + + //Start of line is 0,0 and should be skipped (^) + if (icuEnd >= 0) + { + const auto matchLength = utext_nativeLength(&uText); + auto adjustedMatchStart = icuStart - 1 == matchLength ? icuStart - 1 : icuStart; + auto adjustedMatchEnd = std::min(static_cast(matchLength), icuEnd); + + const size_t matchStartLine = (adjustedMatchStart / viewPortWidth) + startRow; + const size_t matchEndLine = (adjustedMatchEnd / viewPortWidth) + startRow; + + if (matchStartLine > startRow) + { + adjustedMatchStart %= (matchStartLine - startRow) * viewPortWidth; + } + + if (matchEndLine > startRow) + { + adjustedMatchEnd %= (matchEndLine - startRow) * viewPortWidth; + } + + auto ps = til::point_span{}; + ps.start = til::point{ adjustedMatchStart, static_cast(matchStartLine) }; + ps.end = til::point{ adjustedMatchEnd, static_cast(matchEndLine) }; + results.emplace_back(ps); + } + } while (uregex_findNext(regex.get(), &status)); + } + } + return results; +} diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index a338f1272c4..cee9bb00257 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -26,7 +26,7 @@ class Search final Search() = default; bool ResetIfStale(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool reverse, bool caseInsensitive); - + void QuickSelectRegex(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive); void MoveToCurrentSelection(); void MoveToPoint(til::point anchor) noexcept; void MovePastPoint(til::point anchor) noexcept; @@ -49,4 +49,5 @@ class Search final std::vector _results; ptrdiff_t _index = 0; ptrdiff_t _step = 0; + static std::vector _regexSearch(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive); }; diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 4e4f63d3676..4a9c54e4b66 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -561,6 +561,20 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } + void TerminalPage::_HandleQuickSelect(const IInspectable& sender, + const ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + if (const auto activeTab{ _senderOrFocusedTab(sender) }) + { + _SetFocusedTab(*activeTab); + _QuickSelect(*activeTab, realArgs.Input(), realArgs.ShouldCopy()); + } + args.Handled(true); + } + } + void TerminalPage::_HandleResetFontSize(const IInspectable& /*sender*/, const ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index a46544ce60c..07b569e0d23 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -3551,6 +3551,14 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_QuickSelect(const TerminalTab& tab, std::wstring_view input, bool copy) + { + if (const auto& control{ tab.GetActiveTerminalControl() }) + { + control.QuickSelect(input, copy); + } + } + // Method Description: // - Toggles borderless mode. Hides the tab row, and raises our // FocusModeChanged event. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 5e22760fc67..e252a5376bc 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -440,6 +440,7 @@ namespace winrt::TerminalApp::implementation void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::TabBase& tab); void _Find(const TerminalTab& tab); + void _QuickSelect(const TerminalTab& tab, std::wstring_view input, bool copy); winrt::Microsoft::Terminal::Control::TermControl _CreateNewControlAndContent(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 4027e914d8f..3bba806a66a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -19,6 +19,7 @@ #include "../../renderer/atlas/AtlasEngine.h" #include "ControlCore.g.cpp" +#include "QuickSelectHandler.h" #include "SelectionColor.g.cpp" using namespace ::Microsoft::Console::Types; @@ -150,6 +151,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation } UpdateSettings(settings, unfocusedAppearance); + + auto quickSelectAlphabet = std::make_shared<::Microsoft::Console::Render::QuickSelectAlphabet>(); + _terminal->SetQuickSelectAlphabet(quickSelectAlphabet); + _quickSelectHandler = std::make_unique( + _terminal, + quickSelectAlphabet); } void ControlCore::_setupDispatcherAndCallbacks() @@ -539,6 +546,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation { auto lock = _terminal->LockForWriting(); + if (_quickSelectHandler->Enabled()) + { + if (_renderer) + { + _quickSelectHandler->HandleChar(vkey, _renderer.get()); + _updateSelectionUI(); + } + return true; + } + if (_shouldTryUpdateSelection(vkey) && _terminal->SelectionMode() == ::Terminal::SelectionInteractionMode::Mark) { if (vkey == 'A' && !mods.IsAltPressed() && !mods.IsShiftPressed() && mods.IsCtrlPressed()) @@ -689,6 +706,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // This is a scroll event that wasn't initiated by the terminal // itself - it was initiated by the mouse wheel, or the scrollbar. const auto lock = _terminal->LockForWriting(); + if (_quickSelectHandler->Enabled()) + { + LOG_IF_FAILED(_renderEngine->InvalidateAll()); + } _terminal->UserScrollViewport(viewTop); } @@ -1663,6 +1684,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation FoundMatch.raise(*this, *foundResults); } + void ControlCore::EnterQuickSelectMode(const winrt::hstring& text, bool copy) + { + const auto lock = _terminal->LockForWriting(); + _quickSelectHandler->EnterQuickSelectMode(text, copy, _searcher, _renderer.get()); + } + Windows::Foundation::Collections::IVector ControlCore::SearchResultRows() { const auto lock = _terminal->LockForReading(); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index d79df07d1bc..c11c23e1cde 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -25,6 +25,8 @@ #include "../buffer/out/search.h" #include "../buffer/out/TextColor.h" +class QuickSelectHandler; + namespace ControlUnitTests { class ControlCoreTests; @@ -208,6 +210,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void SetEndSelectionPoint(const til::point position); void Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive); + void EnterQuickSelectMode(const winrt::hstring& text, bool copy); void ClearSearch(); Windows::Foundation::Collections::IVector SearchResultRows(); @@ -300,6 +303,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::com_ptr _settings{ nullptr }; std::shared_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr }; + std::unique_ptr _quickSelectHandler; // NOTE: _renderEngine must be ordered before _renderer. // diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 01223fb1a14..a8d95643de6 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -129,6 +129,7 @@ namespace Microsoft.Terminal.Control void BlinkAttributeTick(); void Search(String text, Boolean goForward, Boolean caseSensitive); + void EnterQuickSelectMode(String text, Boolean copy); void ClearSearch(); IVector SearchResultRows { get; }; diff --git a/src/cascadia/TerminalControl/QuickSelectHandler.cpp b/src/cascadia/TerminalControl/QuickSelectHandler.cpp new file mode 100644 index 00000000000..fafc9e47ce1 --- /dev/null +++ b/src/cascadia/TerminalControl/QuickSelectHandler.cpp @@ -0,0 +1,111 @@ +#include "pch.h" +#include "QuickSelectHandler.h" + +#include "ControlInteractivity.h" +#include "../../buffer/out/search.h" + +QuickSelectHandler::QuickSelectHandler( + const std::shared_ptr& terminal, + const std::shared_ptr& quickSelectAlphabet) +{ + _terminal = terminal; + _quickSelectAlphabet = quickSelectAlphabet; +} + +void QuickSelectHandler::EnterQuickSelectMode( + std::wstring_view text, + bool copyMode, + Search& searcher, + Microsoft::Console::Render::Renderer* renderer) +{ + _quickSelectAlphabet->Enabled(true); + _copyMode = copyMode; + searcher.QuickSelectRegex(*_terminal, text, true); + searcher.HighlightResults(); + renderer->TriggerSelection(); +} + +bool QuickSelectHandler::Enabled() const +{ + return _quickSelectAlphabet->Enabled(); +} + +void QuickSelectHandler::HandleChar(const uint32_t vkey, Microsoft::Console::Render::Renderer* renderer) const +{ + if (vkey == VK_ESCAPE) + { + _quickSelectAlphabet->Enabled(false); + _quickSelectAlphabet->ClearChars(); + _terminal->ClearSelection(); + renderer->TriggerSelection(); + return; + } + + if (vkey == VK_BACK) + { + _quickSelectAlphabet->RemoveChar(); + renderer->TriggerSelection(); + return; + } + + wchar_t vkeyText[2] = { 0 }; + BYTE keyboardState[256]; + if (!GetKeyboardState(keyboardState)) + { + return; + } + + keyboardState[VK_SHIFT] = 0x80; + ToUnicode(vkey, MapVirtualKey(vkey, MAPVK_VK_TO_VSC), keyboardState, vkeyText, 2, 0); + + _quickSelectAlphabet->AppendChar(vkeyText); + + if (_quickSelectAlphabet->AllCharsSet(_terminal->NumberOfVisibleSearchSelections())) + { + const auto index = _quickSelectAlphabet->GetIndexForChars(); + const auto quickSelectResult = _terminal->GetViewportSelectionAtIndex(index); + if (quickSelectResult.has_value()) + { + const auto startPoint = std::get<0>(quickSelectResult.value()); + const auto endPoint = std::get<1>(quickSelectResult.value()); + + if (!_copyMode) + { + _quickSelectAlphabet->Enabled(false); + _quickSelectAlphabet->ClearChars(); + _terminal->ClearSelection(); + _terminal->SelectNewRegion(til::point{ startPoint.x, startPoint.y }, til::point{ startPoint.x, startPoint.y}); + if (_terminal->SelectionMode() != Microsoft::Terminal::Core::Terminal::SelectionInteractionMode::Mark) + { + _terminal->ToggleMarkMode(); + } + + renderer->TriggerSelection(); + } + else + { + const auto req = TextBuffer::CopyRequest::FromConfig(_terminal->GetTextBuffer(), startPoint, endPoint, true, false, false); + const auto text = _terminal->GetTextBuffer().GetPlainText(req); + _terminal->CopyToClipboard(text); + + std::thread hideTimerThread([this, renderer]() { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + { + auto lock = _terminal->LockForWriting(); + _quickSelectAlphabet->Enabled(false); + _quickSelectAlphabet->ClearChars(); + _terminal->ClearSelection(); + //This isn't technically safe. There is a slight chance that the renderer is deleted + //I think the fix is to make the renderer a shared pointer but I am not ready to mess with change core terminal stuff + renderer->TriggerSelection(); + renderer->TriggerRedrawAll(); + renderer->NotifyPaintFrame(); + } + }); + hideTimerThread.detach(); + } + } + } + renderer->TriggerRedrawAll(); + renderer->NotifyPaintFrame(); +} diff --git a/src/cascadia/TerminalControl/QuickSelectHandler.h b/src/cascadia/TerminalControl/QuickSelectHandler.h new file mode 100644 index 00000000000..7994ea2231f --- /dev/null +++ b/src/cascadia/TerminalControl/QuickSelectHandler.h @@ -0,0 +1,31 @@ +#pragma once +#include "../../renderer/base/Renderer.hpp" +#include "../../cascadia/TerminalCore/Terminal.hpp" +#include "../../renderer/base/lib/QuickSelectAlphabet.h" +#include "search.h" + +class Search; + +namespace Microsoft::Console::Render +{ + class Renderer; +} + +class QuickSelectHandler +{ +private: + std::shared_ptr _terminal; + bool _copyMode = false; + std::shared_ptr _quickSelectAlphabet; + +public: + QuickSelectHandler( + const std::shared_ptr& terminal, + const std::shared_ptr& quickSelectAlphabet); + void EnterQuickSelectMode(std::wstring_view text, + bool copyMode, + Search& searcher, + Microsoft::Console::Render::Renderer* renderer); + bool Enabled() const; + void HandleChar(uint32_t vkey, Microsoft::Console::Render::Renderer* renderer) const; +}; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index de8ec920d3e..e56a957c356 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -456,6 +456,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation return _searchBox->TextBox().FocusState() == FocusState::Keyboard; } + void TermControl::QuickSelect(const winrt::hstring& text, bool copy) + { + _core.EnterQuickSelectMode(text, copy); + } + // Method Description: // - Search text in text buffer. This is triggered if the user clicks the // search button, presses enter, or changes the search criteria. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 2a8b28567ec..b8025fc7a33 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void SelectAll(); bool ToggleBlockSelection(); void ToggleMarkMode(); + void QuickSelect(const winrt::hstring& text, bool copy); bool SwitchSelectionEndpoint(); bool ExpandSelectionToWord(); void Close(); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index b105748ff64..29378d1a28b 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -106,6 +106,7 @@ namespace Microsoft.Terminal.Control Boolean SearchBoxEditInFocus(); void SearchMatch(Boolean goForward); + void QuickSelect(String needle, Boolean copy); void AdjustFontSize(Single fontSizeDelta); void ResetFontSize(); diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj index 59b6a01ea7d..3e4a093c3d1 100644 --- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj @@ -9,7 +9,6 @@ StaticLibrary Console true - 3 nested - - true true - - @@ -41,6 +36,7 @@ ControlInteractivity.idl + ScrollBarVisualStateManager.idl @@ -83,6 +79,7 @@ ControlInteractivity.idl + ScrollBarVisualStateManager.idl @@ -194,9 +191,7 @@ - - - + \ No newline at end of file diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 80c5e3cc905..5dca28feef9 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -225,8 +225,13 @@ class Microsoft::Terminal::Core::Terminal final : const til::point GetSelectionEnd() const noexcept override; const std::wstring_view GetConsoleTitle() const noexcept override; const bool IsUiaDataInitialized() const noexcept override; + bool InQuickSelectMode() override; + Microsoft::Console::Render::QuickSelectState GetQuickSelectState() noexcept override; + void SetQuickSelectAlphabet(std::shared_ptr val); #pragma endregion + int32_t NumberOfVisibleSearchSelections(); + std::optional> GetViewportSelectionAtIndex(int32_t index); void SetWriteInputCallback(std::function pfn) noexcept; void SetWarningBellCallback(std::function pfn) noexcept; void SetTitleChangedCallback(std::function pfn) noexcept; @@ -370,6 +375,7 @@ class Microsoft::Terminal::Core::Terminal final : size_t _hyperlinkPatternId = 0; std::wstring _workingDirectory; + std::shared_ptr _quickSelectAlphabet; // This default fake font value is only used to check if the font is a raster font. // Otherwise, the font is changed to a real value with the renderer via TriggerFontChange. diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index ae1db3507b4..eca6df7ef8f 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -716,6 +716,45 @@ void Terminal::SelectAll() _ScrollToPoint(_selection->start); } +int32_t Terminal::NumberOfVisibleSearchSelections() +{ + auto lowerIt = std::lower_bound(_searchSelections.begin(), _searchSelections.end(), _GetVisibleViewport().Top(), [](const til::inclusive_rect& rect, til::CoordType value) { + return rect.top < value; + }); + + auto upperIt = std::upper_bound(_searchSelections.begin(), _searchSelections.end(), _GetVisibleViewport().BottomExclusive(), [](til::CoordType value, const til::inclusive_rect& rect) { + return value < rect.top; + }); + + auto num = static_cast(std::distance(lowerIt, upperIt)); + return num; +} + +std::optional> Terminal::GetViewportSelectionAtIndex(int32_t index) +{ + if (_searchSelections.empty()) + { + return std::nullopt; + } + + auto lowerIt = std::lower_bound(_searchSelections.begin(), _searchSelections.end(), _GetVisibleViewport().Top(), [](const til::inclusive_rect& rect, til::CoordType value) { + return rect.top < value; + }); + + auto upperIt = std::upper_bound(_searchSelections.begin(), _searchSelections.end(), _GetVisibleViewport().BottomExclusive(), [](til::CoordType value, const til::inclusive_rect& rect) { + return value < rect.top; + }); + + auto distance = std::distance(lowerIt, upperIt); + if (index < 0 || index >= distance) + { + return std::nullopt; + } + + auto rect = (lowerIt + index)[0]; + return std::make_tuple(til::point{ rect.left, rect.top }, til::point{ rect.right, rect.bottom }); +} + void Terminal::_MoveByChar(SelectionDirection direction, til::point& pos) { switch (direction) diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 8dd9b4dae9a..e00ff058aa9 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -260,3 +260,84 @@ const bool Terminal::IsUiaDataInitialized() const noexcept _assertLocked(); return !!_mainBuffer; } + +void Terminal::SetQuickSelectAlphabet(std::shared_ptr val) +{ + _quickSelectAlphabet = val; +} + +QuickSelectState Terminal::GetQuickSelectState() noexcept +try +{ + auto result = QuickSelectState{}; + if (!InQuickSelectMode()) + { + return result; + } + + const auto lowerIt = std::lower_bound(_searchSelections.begin(), _searchSelections.end(), _GetVisibleViewport().Top(), [](const til::inclusive_rect& rect, til::CoordType value) { + return rect.top < value; + }); + + const auto upperIt = std::upper_bound(_searchSelections.begin(), _searchSelections.end(), _GetVisibleViewport().BottomExclusive(), [](til::CoordType value, const til::inclusive_rect& rect) { + return value < rect.top; + }); + + const auto num = static_cast(std::distance(lowerIt, upperIt)); + + const auto chars = _quickSelectAlphabet->GetQuickSelectChars(num); + + til::CoordType lastY = -1; + for (int i = 0; i < num; i++) + { + auto ch = chars[i]; + if (ch.isCurrentMatch) + { + const til::inclusive_rect selection = lowerIt[i]; + const auto start = til::point{ selection.left, selection.top }; + const auto end = til::point{ selection.right, selection.bottom }; + const auto adj = _activeBuffer().GetTextRects(start, end, _blockSelection, false); + + if (adj[0].right - adj[0].left + 1 >= ch.chars.size()) + { + for (auto j = 0; j < adj.size(); j++) + { + QuickSelectSelection toAdd; + if (j > 0) + { + toAdd = QuickSelectSelection{}; + } + else + { + toAdd = ch; + } + toAdd.selection = Viewport::FromInclusive(adj[j]); + toAdd.isCurrentMatch = ch.isCurrentMatch; + if (lastY == toAdd.selection.Top()) + { + result.selectionMap.at(toAdd.selection.Top()).emplace_back(toAdd); + } + else + { + auto rowSelections = std::vector{}; + rowSelections.emplace_back(toAdd); + result.selectionMap.emplace(toAdd.selection.Top(), rowSelections); + lastY = toAdd.selection.Top(); + } + } + } + } + } + + return result; +} +catch (...) +{ + LOG_CAUGHT_EXCEPTION(); + return {}; +} + +bool Terminal::InQuickSelectMode() +{ + return _quickSelectAlphabet->Enabled(); +} diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index e90a2b8dcfa..cc6a2fc4a2e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -45,6 +45,7 @@ static constexpr std::string_view AddMarkKey{ "addMark" }; static constexpr std::string_view ClearMarkKey{ "clearMark" }; static constexpr std::string_view ClearAllMarksKey{ "clearAllMarks" }; static constexpr std::string_view SendInputKey{ "sendInput" }; +static constexpr std::string_view QuickSelectKey{ "quickSelect" }; static constexpr std::string_view SetColorSchemeKey{ "setColorScheme" }; static constexpr std::string_view SetTabColorKey{ "setTabColor" }; static constexpr std::string_view SplitPaneKey{ "splitPane" }; @@ -381,6 +382,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::ClearMark, RS_(L"ClearMarkCommandKey") }, { ShortcutAction::ClearAllMarks, RS_(L"ClearAllMarksCommandKey") }, { ShortcutAction::SendInput, MustGenerate }, + { ShortcutAction::QuickSelect, RS_(L"QuickSelectCommandKey") }, { ShortcutAction::SetColorScheme, MustGenerate }, { ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") }, { ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") }, diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index a276ebeff2b..ad2c396054b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -17,6 +17,7 @@ #include "SwapPaneArgs.g.cpp" #include "AdjustFontSizeArgs.g.cpp" #include "SendInputArgs.g.cpp" +#include "QuickSelectArgs.g.cpp" #include "SplitPaneArgs.g.cpp" #include "OpenSettingsArgs.g.cpp" #include "SetFocusModeArgs.g.cpp" @@ -412,6 +413,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return winrt::hstring{ name }; } + winrt::hstring QuickSelectArgs::GenerateName() const + { + // The string will be similar to the following: + // * "Send Input: ...input..." + + auto escapedInput = til::visualize_control_codes(Input()); + auto name = fmt::format(std::wstring_view(RS_(L"QuickSelectCommandKey")), escapedInput); + return winrt::hstring{ name }; + } + winrt::hstring SplitPaneArgs::GenerateName() const { // The string will be similar to the following: diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 6d83964cbbc..a603757edd0 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -16,6 +16,7 @@ #include "SwapPaneArgs.g.h" #include "AdjustFontSizeArgs.g.h" #include "SendInputArgs.g.h" +#include "QuickSelectArgs.g.h" #include "SplitPaneArgs.g.h" #include "OpenSettingsArgs.g.h" #include "SetFocusModeArgs.g.h" @@ -135,6 +136,11 @@ private: \ #define SEND_INPUT_ARGS(X) \ X(winrt::hstring, Input, "input", args->Input().empty(), L"") +//////////////////////////////////////////////////////////////////////////////// +#define QUICK_SELECT_ARGS(X) \ + X(winrt::hstring, Input, "input", args->Input().empty(), L"") \ + X(bool, ShouldCopy, "shouldCopy", false, false) + //////////////////////////////////////////////////////////////////////////////// #define OPEN_SETTINGS_ARGS(X) \ X(SettingsTarget, Target, "target", false, SettingsTarget::SettingsFile) @@ -691,6 +697,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ACTION_ARGS_STRUCT(SendInputArgs, SEND_INPUT_ARGS); + ACTION_ARGS_STRUCT(QuickSelectArgs, QUICK_SELECT_ARGS); + ACTION_ARGS_STRUCT(OpenSettingsArgs, OPEN_SETTINGS_ARGS); ACTION_ARGS_STRUCT(SetFocusModeArgs, SET_FOCUS_MODE_ARGS); @@ -857,4 +865,5 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation BASIC_FACTORY(SuggestionsArgs); BASIC_FACTORY(SelectCommandArgs); BASIC_FACTORY(SelectOutputArgs); + BASIC_FACTORY(QuickSelectArgs); } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index 32f9005fdee..444c8aa09b5 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -218,6 +218,13 @@ namespace Microsoft.Terminal.Settings.Model String Input { get; }; }; + [default_interface] runtimeclass QuickSelectArgs : IActionArgs + { + QuickSelectArgs(); + String Input { get; }; + Boolean ShouldCopy { get; }; + }; + [default_interface] runtimeclass SplitPaneArgs : IActionArgs { SplitPaneArgs(SplitType splitMode, SplitDirection split, Double size, NewTerminalArgs terminalArgs); diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index f9d934e36e0..9525567ad90 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -35,6 +35,7 @@ ON_ALL_ACTIONS(NextTab) \ ON_ALL_ACTIONS(PrevTab) \ ON_ALL_ACTIONS(SendInput) \ + ON_ALL_ACTIONS(QuickSelect) \ ON_ALL_ACTIONS(SplitPane) \ ON_ALL_ACTIONS(ToggleSplitOrientation) \ ON_ALL_ACTIONS(TogglePaneZoom) \ @@ -142,6 +143,7 @@ ON_ALL_ACTIONS_WITH_ARGS(ScrollToMark) \ ON_ALL_ACTIONS_WITH_ARGS(AddMark) \ ON_ALL_ACTIONS_WITH_ARGS(SendInput) \ + ON_ALL_ACTIONS_WITH_ARGS(QuickSelect) \ ON_ALL_ACTIONS_WITH_ARGS(SetColorScheme) \ ON_ALL_ACTIONS_WITH_ARGS(SetTabColor) \ ON_ALL_ACTIONS_WITH_ARGS(SplitPane) \ diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 31e4844eb00..953ee5b2de3 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -236,7 +236,9 @@ - + + Designer + @@ -257,7 +259,6 @@ {ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00} - - - @@ -332,5 +330,4 @@ - - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index 89b0f24f473..d08db1b1e07 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -2,6 +2,7 @@ + @@ -99,6 +100,8 @@ + + @@ -118,9 +121,8 @@ - - - + + @@ -130,4 +132,7 @@ {81a6314f-aa5b-4533-a499-13bc3a5c4af0} - + + + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index d6d6d9565f9..6febbd9b4a5 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -411,6 +411,9 @@ Send Input: "{0}" {0} will be replaced with a string of input as defined by the user + + Quick Select + Set color scheme to {0} {0} will be replaced with the name of a color scheme as defined by the user. diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index d9b094626ba..57a5f0c1a1c 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -434,3 +434,13 @@ const til::point RenderData::GetSelectionEnd() const noexcept return { x_pos, y_pos }; } + +bool RenderData::InQuickSelectMode() +{ + return false; +} + +Microsoft::Console::Render::QuickSelectState RenderData::GetQuickSelectState() noexcept +{ + return {}; +} diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 52056d7a6f5..6af273d487b 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -59,4 +59,6 @@ class RenderData final : const til::point GetSelectionAnchor() const noexcept override; const til::point GetSelectionEnd() const noexcept override; const bool IsUiaDataInitialized() const noexcept override { return true; } + bool InQuickSelectMode() override; + Microsoft::Console::Render::QuickSelectState GetQuickSelectState() noexcept override; }; diff --git a/src/renderer/base/QuickSelectAlphabet.cpp b/src/renderer/base/QuickSelectAlphabet.cpp new file mode 100644 index 00000000000..1dbac10f855 --- /dev/null +++ b/src/renderer/base/QuickSelectAlphabet.cpp @@ -0,0 +1,150 @@ +#include "precomp.h" +#include "lib/QuickSelectAlphabet.h" + +namespace Microsoft::Console::Render +{ + QuickSelectAlphabet::QuickSelectAlphabet() + { + _quickSelectAlphabet = { L'A', L'S', L'D', L'F', L'Q', L'W', L'E', L'R', L'Z', L'X', L'C', L'V', L'J', L'K', L'L', L'M', L'I', L'U', L'O', L'P', L'G', L'H', L'T', L'Y', L'B', L'N' }; + for (int16_t i = 0; i < _quickSelectAlphabet.size(); ++i) + { + _quickSelectAlphabetMap[_quickSelectAlphabet[i]] = i; + } + } + + bool QuickSelectAlphabet::Enabled() const + { + return _enabled; + } + + void QuickSelectAlphabet::Enabled(bool val) + { + _enabled = val; + } + + void QuickSelectAlphabet::AppendChar(wchar_t* ch) + { + _chars += ch; + } + + void QuickSelectAlphabet::RemoveChar() + { + if (!_chars.empty()) + { + _chars.pop_back(); + } + } + + void QuickSelectAlphabet::ClearChars() + { + _chars.clear(); + } + + std::vector QuickSelectAlphabet::GetQuickSelectChars(int32_t number) const noexcept + try + { + auto result = std::vector(); + result.reserve(number); + + int columns = 1; + while (std::pow(_quickSelectAlphabet.size(), columns) < number) + { + columns++; + } + + std::vector indices(columns, 0); + + for (auto j = 0; j < number; j++) + { + bool allMatching = true; + std::vector chs; + for (int i = 0; i < indices.size(); i++) + { + auto idx = indices[i]; + auto ch = Microsoft::Console::Render::QuickSelectChar{}; + ch.val = _quickSelectAlphabet[idx]; + if (i < _chars.size()) + { + if (_quickSelectAlphabet[idx] != _chars[i]) + { + allMatching = false; + //We are going to throw this away anyways + break; + } + + ch.isMatch = true; + } + else + { + ch.isMatch = false; + } + chs.emplace_back(ch); + } + + auto isCurrentMatch = false; + if ((_chars.size() == 0 || chs.size() >= _chars.size()) && + allMatching) + { + isCurrentMatch = true; + } + + auto toAdd = Microsoft::Console::Render::QuickSelectSelection{}; + toAdd.isCurrentMatch = isCurrentMatch; + + for (auto ch : chs) + { + toAdd.chars.emplace_back(ch); + } + + result.emplace_back(toAdd); + + for (int k = columns - 1; k >= 0; --k) + { + indices[k]++; + if (indices[k] < _quickSelectAlphabet.size()) + { + break; // No carry over, break the loop + } + indices[k] = 0; // Carry over to the previous column + if (j == 0) + { + // If we exceed the alphabet * alphabet. Give up and return empty result. + return {}; + } + } + } + + return result; + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + return {}; + } + + bool QuickSelectAlphabet::AllCharsSet(int32_t number) const + { + int columns = 1; + while (std::pow(_quickSelectAlphabet.size(), columns) < number) + { + columns++; + } + + auto result = _chars.size() == columns; + return result; + } + + int32_t QuickSelectAlphabet::GetIndexForChars() + { + int16_t selectionIndex = 0; + int16_t power = static_cast(_chars.size() - 1); + for (int16_t i = 0; i < _chars.size(); i++) + { + auto ch = _chars[i]; + auto index = _quickSelectAlphabetMap[ch]; + selectionIndex += index * static_cast(std::pow(_quickSelectAlphabet.size(), power--)); + } + + return selectionIndex; + } +} diff --git a/src/renderer/base/lib/QuickSelectAlphabet.h b/src/renderer/base/lib/QuickSelectAlphabet.h new file mode 100644 index 00000000000..1be6c68fa26 --- /dev/null +++ b/src/renderer/base/lib/QuickSelectAlphabet.h @@ -0,0 +1,41 @@ +#pragma once +#include + +namespace Microsoft::Console::Render +{ + struct QuickSelectChar + { + bool isMatch = false; + wchar_t val; + }; + + struct QuickSelectSelection + { + bool isCurrentMatch; + std::vector chars; + Microsoft::Console::Types::Viewport selection; + }; + + struct QuickSelectState + { + std::map> selectionMap; + }; + class QuickSelectAlphabet + { + bool _enabled = false; + std::vector _quickSelectAlphabet; + std::unordered_map _quickSelectAlphabetMap; + std::wstring _chars; + + public: + QuickSelectAlphabet(); + bool Enabled() const; + void Enabled(bool val); + void AppendChar(wchar_t* ch); + void RemoveChar(); + void ClearChars(); + std::vector GetQuickSelectChars(int32_t number) const noexcept; + bool AllCharsSet(int32_t number) const; + int32_t GetIndexForChars(); + }; +} diff --git a/src/renderer/base/lib/base.vcxproj b/src/renderer/base/lib/base.vcxproj index 3c177d955e2..d0a917d7f49 100644 --- a/src/renderer/base/lib/base.vcxproj +++ b/src/renderer/base/lib/base.vcxproj @@ -16,6 +16,7 @@ + @@ -40,8 +41,9 @@ + - + \ No newline at end of file diff --git a/src/renderer/base/lib/base.vcxproj.filters b/src/renderer/base/lib/base.vcxproj.filters index 8887f1c610f..b755bd62663 100644 --- a/src/renderer/base/lib/base.vcxproj.filters +++ b/src/renderer/base/lib/base.vcxproj.filters @@ -48,6 +48,9 @@ Source Files + + Source Files + @@ -95,8 +98,12 @@ Header Files\inc + + Header Files + + - + \ No newline at end of file diff --git a/src/renderer/base/precomp.h b/src/renderer/base/precomp.h index 40f7b95a01e..8b0fe2cbc93 100644 --- a/src/renderer/base/precomp.h +++ b/src/renderer/base/precomp.h @@ -22,3 +22,4 @@ Module Name: #include #include "../../types/inc/viewport.hpp" +#include diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index c7c5c491815..b119fbbf7f5 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -700,6 +700,8 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine) LOG_IF_FAILED(pEngine->ResetLineTransform()); }); + auto quickSelectState = _pData->GetQuickSelectState(); + for (const auto& dirtyRect : dirtyAreas) { if (!dirtyRect) @@ -753,8 +755,9 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine) // Prepare the appropriate line transform for the current row and viewport offset. LOG_IF_FAILED(pEngine->PrepareLineTransform(lineRendition, screenPosition.y, view.Left())); + auto smIt = quickSelectState.selectionMap.find(row); // Ask the helper to paint through this specific line. - _PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped); + _PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped, smIt != quickSelectState.selectionMap.end() ? smIt->second : decltype(smIt->second)()); } } } @@ -768,7 +771,8 @@ static bool _IsAllSpaces(const std::wstring_view v) void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const til::point target, - const bool lineWrapped) + const bool lineWrapped, + std::vector highlights) { auto globalInvert{ _renderSettings.GetRenderMode(RenderSettings::Mode::ScreenReversed) }; @@ -826,21 +830,58 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, // Run contains wide character (>1 columns) auto containsWideCharacter = false; + std::wstring charOverrides; + // This inner loop will accumulate clusters until the color changes. // When the color changes, it will save the new color off and break. // We also accumulate clusters according to regex patterns do { + auto origAttr = it->TextAttr(); + if (_pData->InQuickSelectMode()) + { + bool isHighlight = false; + QuickSelectSelection overlay; + auto tx = screenPoint.x + cols; + for (auto highlight : highlights) + { + if (tx >= highlight.selection.Left() && tx <= highlight.selection.RightInclusive()) + { + overlay = highlight; + isHighlight = true; + break; + } + } + + //Override all formating to make it easier to see whats selectable and not + origAttr.SetDefaultForeground(); + origAttr.SetDefaultBackground(); + if (isHighlight) + { + origAttr.SetBackground(0xff3c3836); + auto overlayOffset = screenPoint.x + cols - overlay.selection.Left(); + if (overlayOffset < overlay.chars.size()) + { + auto ch = overlay.chars[overlayOffset]; + COLORREF colorref = ch.isMatch ? 0xFF0028FF : 0xFF00A5FF; + origAttr.SetForeground(colorref); + charOverrides += ch.val; + } + } + } + + auto clusterChars = charOverrides.size() > 0 ? std::wstring_view{ charOverrides.data() + cols, 1 } : it->Chars(); + til::point thisPoint{ screenPoint.x + cols, screenPoint.y }; const auto thisPointPatterns = _pData->GetPatternId(thisPoint); const auto thisUsingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar); const auto changedPatternOrFont = patternIds != thisPointPatterns || usingSoftFont != thisUsingSoftFont; - if (color != it->TextAttr() || changedPatternOrFont) + if (color != origAttr || changedPatternOrFont) { - auto newAttr{ it->TextAttr() }; + auto newAttr{ origAttr }; // foreground doesn't matter for runs of spaces (!) // if we trick it . . . we call Paint far fewer times for cmatrix - if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert) || changedPatternOrFont) + if (!_IsAllSpaces(clusterChars) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert) || changedPatternOrFont) { color = newAttr; patternIds = thisPointPatterns; @@ -871,7 +912,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, } // Advance the cluster and column counts. - _clusterBuffer.emplace_back(it->Chars(), columnCount); + _clusterBuffer.emplace_back(clusterChars, columnCount); it += std::max(it->Columns(), 1); // prevent infinite loop for no visible columns cols += columnCount; @@ -1165,7 +1206,7 @@ void Renderer::_PaintOverlay(IRenderEngine& engine, auto it = overlay.buffer.GetCellLineDataAt(source); - _PaintBufferOutputHelper(&engine, it, target, false); + _PaintBufferOutputHelper(&engine, it, target, false, {}); } } } @@ -1205,36 +1246,39 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine) { try { - std::span dirtyAreas; - LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas)); + if (!_pData->InQuickSelectMode()) + { + std::span dirtyAreas; + LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas)); - // Get selection rectangles - const auto rectangles = _GetSelectionRects(); - const auto searchRectangles = _GetSearchSelectionRects(); + // Get selection rectangles + const auto rectangles = _GetSelectionRects(); + const auto searchRectangles = _GetSearchSelectionRects(); - std::vector dirtySearchRectangles; - for (auto& dirtyRect : dirtyAreas) - { - for (const auto& sr : searchRectangles) + std::vector dirtySearchRectangles; + for (auto& dirtyRect : dirtyAreas) { - if (const auto rectCopy = sr & dirtyRect) + for (const auto& sr : searchRectangles) { - dirtySearchRectangles.emplace_back(rectCopy); + if (const auto rectCopy = sr & dirtyRect) + { + dirtySearchRectangles.emplace_back(rectCopy); + } } - } - for (const auto& rect : rectangles) - { - if (const auto rectCopy = rect & dirtyRect) + for (const auto& rect : rectangles) { - LOG_IF_FAILED(pEngine->PaintSelection(rectCopy)); + if (const auto rectCopy = rect & dirtyRect) + { + LOG_IF_FAILED(pEngine->PaintSelection(rectCopy)); + } } } - } - if (!dirtySearchRectangles.empty()) - { - LOG_IF_FAILED(pEngine->PaintSelections(std::move(dirtySearchRectangles))); + if (!dirtySearchRectangles.empty()) + { + LOG_IF_FAILED(pEngine->PaintSelections(std::move(dirtySearchRectangles))); + } } } CATCH_LOG(); diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 1cd61799a8f..2ffae630f18 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -100,7 +100,7 @@ namespace Microsoft::Console::Render bool _CheckViewportAndScroll(); [[nodiscard]] HRESULT _PaintBackground(_In_ IRenderEngine* const pEngine); void _PaintBufferOutput(_In_ IRenderEngine* const pEngine); - void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const til::point target, const bool lineWrapped); + void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const til::point target, const bool lineWrapped, std::vector highlights); void _PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngine, const TextAttribute textAttribute, const size_t cchLine, const til::point coordTarget); bool _isHoveredHyperlink(const TextAttribute& textAttribute) const noexcept; void _PaintSelection(_In_ IRenderEngine* const pEngine); diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index cfc035a7f9b..e45730049fa 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -16,6 +16,7 @@ Author(s): #include "../../host/conimeinfo.h" #include "../../buffer/out/TextAttribute.hpp" +#include "../base/lib/QuickSelectAlphabet.h" class Cursor; @@ -76,5 +77,7 @@ namespace Microsoft::Console::Render virtual const til::point GetSelectionAnchor() const noexcept = 0; virtual const til::point GetSelectionEnd() const noexcept = 0; virtual const bool IsUiaDataInitialized() const noexcept = 0; + virtual QuickSelectState GetQuickSelectState() noexcept = 0; + virtual bool InQuickSelectMode() = 0; }; }