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

Double and Triple Click Selection #1197

Merged
merged 17 commits into from
Jul 11, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 73 additions & 9 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_actualFont{ DEFAULT_FONT_FACE.c_str(), 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false },
_touchAnchor{ std::nullopt },
_leadingSurrogate{},
_cursorTimer{}
_cursorTimer{},
_lastMouseClick{},
_lastMouseClickPos{},
_doubleClickOccurred{ false }
{
_Create();
}
Expand Down Expand Up @@ -697,13 +700,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto cursorPosition = point.Position();
const auto terminalPosition = _GetTerminalPosition(cursorPosition);

// save location before rendering
_terminal->SetSelectionAnchor(terminalPosition);

// handle ALT key
_terminal->SetBoxSelection(altEnabled);

_renderer->TriggerSelection();
if (_IsTripleClick(cursorPosition, point.Timestamp()))
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
{
_terminal->TripleClickSelection(terminalPosition);
_renderer->TriggerSelection();
_doubleClickOccurred = true;
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
}
else if (_IsDoubleClick(cursorPosition, point.Timestamp()))
{
_terminal->DoubleClickSelection(terminalPosition);
_renderer->TriggerSelection();
_doubleClickOccurred = true;
}
else
{
// save location before rendering
_terminal->SetSelectionAnchor(terminalPosition);

_renderer->TriggerSelection();
_lastMouseClick = point.Timestamp();
_lastMouseClickPos = cursorPosition;
_doubleClickOccurred = false;
}
}
else if (point.Properties().IsRightButtonPressed())
{
Expand Down Expand Up @@ -1438,13 +1459,56 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return terminalPosition;
}

// clang-format off
// Method Description:
// - Checks if a double click occurred
// Arguments:
// - clickPos: the (x,y) position of a given cursor (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// - clickTime: the timestamp that the click occurred
// Return Value:
// - true if the new click was at the same location within the time delta
const bool TermControl::_IsDoubleClick(winrt::Windows::Foundation::Point clickPos, TimeStamp clickTime) const
{
if (clickPos == _lastMouseClickPos)
{
TimeStamp delta;
UInt64Sub(clickTime, _lastMouseClick, &delta);
if (delta < multiClickTimer)
{
return true;
}
}
return false;
}

// Method Description:
// - Checks if a triple click occurred
// Arguments:
// - clickPos: the (x,y) position of a given cursor (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// - clickTime: the timestamp that the click occurred
// Return Value:
// - true if the new click was at the same location within the time delta after a double click having occurred
const bool TermControl::_IsTripleClick(winrt::Windows::Foundation::Point clickPos, TimeStamp clickTime) const
{
if (_doubleClickOccurred && clickPos == _lastMouseClickPos)
{
TimeStamp delta;
UInt64Sub(clickTime, _lastMouseClick, &delta);
if (delta < multiClickTimer)
{
return true;
}
}
return false;
}

// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
DEFINE_EVENT(TermControl, ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
DEFINE_EVENT(TermControl, CopyToClipboard, _clipboardCopyHandlers, TerminalControl::CopyToClipboardEventArgs);
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
DEFINE_EVENT(TermControl, ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
DEFINE_EVENT(TermControl, CopyToClipboard, _clipboardCopyHandlers, TerminalControl::CopyToClipboardEventArgs);
DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
// clang-format on

Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

struct TermControl : TermControlT<TermControl>
{
using TimeStamp = uint64_t;

// represents 0.5 seconds (or 5000 ms)
const TimeStamp multiClickTimer = 500000;
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

TermControl();
TermControl(Settings::IControlSettings settings);

Expand Down Expand Up @@ -97,6 +102,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// viewport via touch input.
std::optional<winrt::Windows::Foundation::Point> _touchAnchor;

TimeStamp _lastMouseClick;
bool _doubleClickOccurred;
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPos;

// Event revokers -- we need to deregister ourselves before we die,
// lest we get callbacks afterwards.
winrt::Windows::UI::Xaml::Controls::Control::SizeChanged_revoker _sizeChangedRevoker;
Expand Down Expand Up @@ -140,6 +149,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Settings::KeyModifiers _GetPressedModifierKeys() const;

const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
const bool _IsDoubleClick(winrt::Windows::Foundation::Point clickPos, TimeStamp clickTime) const;
const bool _IsTripleClick(winrt::Windows::Foundation::Point clickPos, TimeStamp clickTime) const;
};
}

Expand Down
136 changes: 136 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,3 +619,139 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept
const auto& cursor = _buffer->GetCursor();
return cursor.IsBlinkingAllowed();
}

// Method Description:
// - Select the sequence between delimiters defined in Settings
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
void Terminal::DoubleClickSelection(const COORD position)
{
// if you double click a delimiter, just select that one cell
COORD positionWithOffsets = _ConvertToBufferCell(position);
auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
if (_DoubleClickDelimiterCheck(cellChar))
{
SetSelectionAnchor(position);
return;
}

// scan leftwards until delimiter is found and
// set selection anchor to one right of that spot
_ExpandDoubleClickSelectionLeft(position);

// scan rightwards until delimiter is found and
// set endSelectionPosition to one left of that spot
_ExpandDoubleClickSelectionRight(position);
}

// Method Description:
// - Select the entire row of the position clicked
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
void Terminal::TripleClickSelection(const COORD position)
{
SetSelectionAnchor({ 0, position.Y });
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
SetEndSelectionPosition({ _buffer->GetSize().RightInclusive(), position.Y });
}

// Method Description:
// - expand the double click selection to the left (stopped by delimiter)
// Arguments:
// - position: viewport coordinate for selection
// Return Value:
// - update _selectionAnchor to new expanded location
void Terminal::_ExpandDoubleClickSelectionLeft(const COORD position)
{
// don't change the value if at/outside the boundary
if (position.X <= 0 || position.X >= _buffer->GetSize().RightInclusive())
{
return;
}

COORD positionWithOffsets = _ConvertToBufferCell(position);
const auto bufferViewport = _buffer->GetSize();
auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
while (positionWithOffsets.X != 0 && !_DoubleClickDelimiterCheck(cellChar))
{
bufferViewport.DecrementInBounds(positionWithOffsets);
cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
}

if (positionWithOffsets.X != 0 || _DoubleClickDelimiterCheck(cellChar))
{
// move off of delimiter to highlight properly
bufferViewport.IncrementInBounds(positionWithOffsets);
}

THROW_IF_FAILED(ShortSub(positionWithOffsets.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &positionWithOffsets.Y));
_selectionAnchor = positionWithOffsets;
_selectionAnchor_YOffset = gsl::narrow<SHORT>(_ViewStartIndex());
_selectionActive = true;
}

// Method Description:
// - expand the double click selection to the right (stopped by delimiter)
// Arguments:
// - position: viewport coordinate for selection
// Return Value:
// - update _endSelectionPosition to new expanded location
void Terminal::_ExpandDoubleClickSelectionRight(const COORD position)
{
// don't change the value if at/outside the boundary
if (position.X <= 0 || position.X >= _buffer->GetSize().RightInclusive())
{
return;
}

COORD positionWithOffsets = _ConvertToBufferCell(position);
const auto bufferViewport = _buffer->GetSize();
auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
while (positionWithOffsets.X != _buffer->GetSize().RightInclusive() && !_DoubleClickDelimiterCheck(cellChar))
{
bufferViewport.IncrementInBounds(positionWithOffsets);
cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
}

THROW_IF_FAILED(ShortSub(positionWithOffsets.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &positionWithOffsets.Y));
_endSelectionPosition = positionWithOffsets;
_endSelectionPosition_YOffset = gsl::narrow<SHORT>(_ViewStartIndex());
}

// Method Description:
// - check if buffer cell data contains delimiter for double click selection
// Arguments:
// - cellChar: the char saved to the buffer cell under observation
// Return Value:
// - true if cell data contains the delimiter.
const bool Terminal::_DoubleClickDelimiterCheck(std::wstring_view cellChar) const
{
// TODO GitHub #988: hook up delimiters to Settings
std::wstring_view delimiters[] = {
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
L" ",
L"/",
L"\\"
};

for (auto delimiter : delimiters)
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
{
if (cellChar == delimiter)
{
return true;
}
}
return false;
}

// Method Description:
// - convert viewport position to the corresponding location on the buffer
// Arguments:
// - viewportPos: a coordinate on the viewport
// Return Value:
// - the corresponding location on the buffer
const COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const
{
COORD positionWithOffsets = viewportPos;
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
THROW_IF_FAILED(ShortSub(viewportPos.Y, gsl::narrow<SHORT>(_scrollOffset), &positionWithOffsets.Y));
THROW_IF_FAILED(ShortAdd(positionWithOffsets.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &positionWithOffsets.Y));
return positionWithOffsets;
}
6 changes: 6 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ class Microsoft::Terminal::Core::Terminal final :

#pragma region TextSelection
const bool IsSelectionActive() const noexcept;
void DoubleClickSelection(const COORD position);
void TripleClickSelection(const COORD position);
void SetSelectionAnchor(const COORD position);
void SetEndSelectionPosition(const COORD position);
void SetBoxSelection(const bool isEnabled) noexcept;
Expand Down Expand Up @@ -151,6 +153,10 @@ class Microsoft::Terminal::Core::Terminal final :
bool _selectionActive;
SHORT _selectionAnchor_YOffset;
SHORT _endSelectionPosition_YOffset;
void _ExpandDoubleClickSelectionLeft(const COORD position);
void _ExpandDoubleClickSelectionRight(const COORD position);
const bool _DoubleClickDelimiterCheck(std::wstring_view cellChar) const;
const COORD _ConvertToBufferCell(const COORD viewportPos) const;

std::shared_mutex _readWriteLock;

Expand Down