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 1 commit
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
72 changes: 68 additions & 4 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -722,13 +722,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 @@ -1427,6 +1445,52 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return terminalPosition;
}

// 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)
{
const TIMESTAMP threshold = 500000;
TIMESTAMP delta;
UInt64Sub(clickTime, _lastMouseClick, &delta);
if (delta < threshold)
{
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)
{
const TIMESTAMP threshold = 500000;
TIMESTAMP delta;
UInt64Sub(clickTime, _lastMouseClick, &delta);
if (delta < threshold)
{
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.
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "../../cascadia/TerminalCore/Terminal.hpp"
#include "../../cascadia/inc/cppwinrt_utils.h"

#define TIMESTAMP uint64_t
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
struct PasteFromClipboardEventArgs :
Expand Down Expand Up @@ -94,6 +96,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;

void _Create();
void _ApplyUISettings();
void _InitializeBackgroundBrush();
Expand Down Expand Up @@ -129,6 +135,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
135 changes: 135 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,3 +618,138 @@ 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
_ExpandDoubleClickSelection_Left(position);

// scan rightwards until delimiter is found and
// set endSelectionPosition to one left of that spot
_ExpandDoubleClickSelection_Right(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::_ExpandDoubleClickSelection_Left(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);
auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
while (positionWithOffsets.X != 0 && !_DoubleClickDelimiterCheck(cellChar))
{
_mutableViewport.DecrementInBounds(positionWithOffsets);
cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
}

if (positionWithOffsets.X != 0 || _DoubleClickDelimiterCheck(cellChar))
{
// move off of delimiter to highlight properly
_mutableViewport.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::_ExpandDoubleClickSelection_Right(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);
auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
while (positionWithOffsets.X != _buffer->GetSize().RightInclusive() && !_DoubleClickDelimiterCheck(cellChar))
{
_mutableViewport.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: hook up delimiters to Settings
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
std::wstring_view delimiters[] =
{
std::wstring_view(L" "),
std::wstring_view(L"/"),
std::wstring_view(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 @@ -122,6 +122,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 @@ -154,6 +156,10 @@ class Microsoft::Terminal::Core::Terminal final :
bool _selectionActive;
SHORT _selectionAnchor_YOffset;
SHORT _endSelectionPosition_YOffset;
void _ExpandDoubleClickSelection_Left(const COORD position);
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
void _ExpandDoubleClickSelection_Right(const COORD position);
const bool _DoubleClickDelimiterCheck(std::wstring_view cellChar) const;
const COORD _ConvertToBufferCell(const COORD viewportPos) const;

std::shared_mutex _readWriteLock;

Expand Down