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

Quick copy/Quick jump #16929

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/buffer/out/UTextAdapter.cpp
Expand Up @@ -327,3 +327,18 @@

return ret;
}

UText Microsoft::Console::ICU::UTextForWrappableRow(const TextBuffer& textBuffer, til::CoordType& row) noexcept

Check failure on line 331 in src/buffer/out/UTextAdapter.cpp

View workflow job for this annotation

GitHub Actions / Spell checking

`Wrappable` is not a recognized word. (unrecognized-spelling)
{
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;
}
1 change: 1 addition & 0 deletions src/buffer/out/UTextAdapter.h
Expand Up @@ -14,4 +14,5 @@
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;

Check failure on line 17 in src/buffer/out/UTextAdapter.h

View workflow job for this annotation

GitHub Actions / Spell checking

`Wrappable` is not a recognized word. (unrecognized-spelling)
}
80 changes: 80 additions & 0 deletions src/buffer/out/search.cpp
Expand Up @@ -5,6 +5,7 @@
#include "search.h"

#include "textBuffer.hpp"
#include "UTextAdapter.h"

using namespace Microsoft::Console::Types;

Expand Down Expand Up @@ -33,6 +34,17 @@
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())
Expand Down Expand Up @@ -162,3 +174,71 @@
{
return _index;
}

std::vector<til::point_span> Search::_regexSearch(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive)
{
std::vector<til::point_span> 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);

Check failure on line 199 in src/buffer/out/search.cpp

View workflow job for this annotation

GitHub Actions / Spell checking

`Wrappable` is not a recognized word. (unrecognized-spelling)

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<int32_t>(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<int32_t>(matchStartLine) };
ps.end = til::point{ adjustedMatchEnd, static_cast<int32_t>(matchEndLine) };
results.emplace_back(ps);
}
} while (uregex_findNext(regex.get(), &status));
}
}
return results;
}
3 changes: 2 additions & 1 deletion src/buffer/out/search.h
Expand Up @@ -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;
Expand All @@ -49,4 +49,5 @@ class Search final
std::vector<til::point_span> _results;
ptrdiff_t _index = 0;
ptrdiff_t _step = 0;
static std::vector<til::point_span> _regexSearch(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive);
};
14 changes: 14 additions & 0 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Expand Up @@ -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<QuickSelectArgs>())
{
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)
{
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Expand Up @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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<QuickSelectHandler>(
_terminal,
quickSelectAlphabet);
}

void ControlCore::_setupDispatcherAndCallbacks()
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<int32_t> ControlCore::SearchResultRows()
{
const auto lock = _terminal->LockForReading();
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Expand Up @@ -25,6 +25,8 @@
#include "../buffer/out/search.h"
#include "../buffer/out/TextColor.h"

class QuickSelectHandler;

namespace ControlUnitTests
{
class ControlCoreTests;
Expand Down Expand Up @@ -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<int32_t> SearchResultRows();
Expand Down Expand Up @@ -300,6 +303,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::com_ptr<ControlSettings> _settings{ nullptr };

std::shared_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr };
std::unique_ptr<QuickSelectHandler> _quickSelectHandler;

// NOTE: _renderEngine must be ordered before _renderer.
//
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Expand Up @@ -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<Int32> SearchResultRows { get; };

Expand Down
111 changes: 111 additions & 0 deletions 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<Microsoft::Terminal::Core::Terminal>& terminal,
const std::shared_ptr<Microsoft::Console::Render::QuickSelectAlphabet>& 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();
}