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 13 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
17 changes: 17 additions & 0 deletions src/cascadia/TerminalApp/Profile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ static constexpr std::string_view SnapOnInputKey{ "snapOnInput" };
static constexpr std::string_view CursorColorKey{ "cursorColor" };
static constexpr std::string_view CursorShapeKey{ "cursorShape" };
static constexpr std::string_view CursorHeightKey{ "cursorHeight" };
static constexpr std::string_view DoubleClickDelimitersKey{ "doubleClickDelimiters" };
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

static constexpr std::string_view CommandlineKey{ "commandline" };
static constexpr std::string_view FontFaceKey{ "fontFace" };
Expand Down Expand Up @@ -78,6 +79,7 @@ Profile::Profile(const winrt::guid& guid) :
_cursorColor{ DEFAULT_CURSOR_COLOR },
_cursorShape{ CursorStyle::Bar },
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
_doubleClickDelimiters{ DEFAULT_DOUBLE_CLICK_DELIMITERS },

_commandline{ L"cmd.exe" },
_startingDirectory{},
Expand Down Expand Up @@ -202,6 +204,11 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector<ColorScheme>&
terminalSettings.BackgroundImageStretchMode(_backgroundImageStretchMode.value());
}

if (!_doubleClickDelimiters.empty())
{
terminalSettings.DoubleClickDelimiters(winrt::to_hstring(_doubleClickDelimiters.c_str()));
}

return terminalSettings;
}

Expand Down Expand Up @@ -251,6 +258,7 @@ Json::Value Profile::ToJson() const
root[JsonKey(CursorHeightKey)] = _cursorHeight;
}
root[JsonKey(CursorShapeKey)] = winrt::to_string(_SerializeCursorStyle(_cursorShape));
root[JsonKey(DoubleClickDelimitersKey)] = winrt::to_string(_doubleClickDelimiters);

///// Control Settings /////
root[JsonKey(CommandlineKey)] = winrt::to_string(_commandline);
Expand Down Expand Up @@ -376,6 +384,10 @@ Profile Profile::FromJson(const Json::Value& json)
{
result._cursorShape = _ParseCursorShape(GetWstringFromJson(cursorShape));
}
if (auto doubleClickDelimiters{ json[JsonKey(DoubleClickDelimitersKey)] })
{
result._doubleClickDelimiters = GetWstringFromJson(doubleClickDelimiters);
}
if (auto tabTitle{ json[JsonKey(TabTitleKey)] })
{
result._tabTitle = GetWstringFromJson(tabTitle);
Expand Down Expand Up @@ -483,6 +495,11 @@ void Profile::SetDefaultBackground(COLORREF defaultBackground) noexcept
_defaultBackground = defaultBackground;
}

void Profile::SetDoubleClickDelimiters(std::wstring delimiters) noexcept
{
_doubleClickDelimiters = delimiters;
}

bool Profile::HasIcon() const noexcept
{
return _icon.has_value();
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/Profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class TerminalApp::Profile final
void SetUseAcrylic(bool useAcrylic) noexcept;
void SetDefaultForeground(COLORREF defaultForeground) noexcept;
void SetDefaultBackground(COLORREF defaultBackground) noexcept;
void SetDoubleClickDelimiters(std::wstring delimiters) noexcept;

bool HasIcon() const noexcept;
std::wstring_view GetIconPath() const noexcept;
Expand Down Expand Up @@ -80,6 +81,7 @@ class TerminalApp::Profile final
uint32_t _cursorColor;
uint32_t _cursorHeight;
winrt::Microsoft::Terminal::Settings::CursorStyle _cursorShape;
std::wstring _doubleClickDelimiters;

std::wstring _commandline;
std::wstring _fontFace;
Expand Down
81 changes: 72 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 @@ -669,13 +672,30 @@ 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();
}
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 @@ -1519,13 +1539,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
12 changes: 12 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

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

// represents 0.5 seconds (or 500 ms). Denoted in microseconds
// Used for PointerPoint.Timestamp Property (https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.pointerpoint.timestamp#Windows_UI_Input_PointerPoint_Timestamp)
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, TerminalConnection::ITerminalConnection connection);

Expand Down Expand Up @@ -101,6 +107,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 @@ -147,6 +157,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
DWORD _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
2 changes: 2 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting

_snapOnInput = settings.SnapOnInput();

_doubleClickDelimiters = settings.DoubleClickDelimiters();

// TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we
// have a smaller scrollback. We should do this carefully - if the new buffer
// size is smaller than where the mutable viewport currently is, we'll want
Expand Down
7 changes: 7 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ class Microsoft::Terminal::Core::Terminal final :
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
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 @@ -149,6 +151,7 @@ class Microsoft::Terminal::Core::Terminal final :
bool _selectionActive;
SHORT _selectionAnchor_YOffset;
SHORT _endSelectionPosition_YOffset;
std::wstring _doubleClickDelimiters;

std::shared_mutex _readWriteLock;

Expand Down Expand Up @@ -191,5 +194,9 @@ class Microsoft::Terminal::Core::Terminal final :
std::vector<SMALL_RECT> _GetSelectionRects() const;
const SHORT _ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const;
const SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const;
void _ExpandDoubleClickSelectionLeft(const COORD position);
void _ExpandDoubleClickSelectionRight(const COORD position);
const bool _DoubleClickDelimiterCheck(std::wstring_view cellChar) const;
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
const COORD _ConvertToBufferCell(const COORD viewportPos) const;
#pragma endregion
};
128 changes: 128 additions & 0 deletions src/cascadia/TerminalCore/TerminalSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,40 @@ const bool Terminal::IsSelectionActive() const noexcept
return _selectionActive;
}

// 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);
const auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
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 });
SetEndSelectionPosition({ _buffer->GetSize().RightInclusive(), position.Y });
}

// Method Description:
// - Record the position of the beginning of a selection
// Arguments:
Expand Down Expand Up @@ -213,3 +247,97 @@ const std::wstring Terminal::RetrieveSelectedTextFromBuffer(bool trimTrailingWhi

return result;
}

// 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();
}

if (positionWithOffsets.X != bufferViewport.RightInclusive() || _DoubleClickDelimiterCheck(cellChar))
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
{
// move off of delimiter to highlight properly
bufferViewport.DecrementInBounds(positionWithOffsets);
}

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
{
return _doubleClickDelimiters.find(cellChar) != std::wstring_view::npos;
}

// 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;
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;
}
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettings/ICoreSettings.idl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ namespace Microsoft.Terminal.Settings
UInt32 CursorColor;
CursorStyle CursorShape;
UInt32 CursorHeight;

String DoubleClickDelimiters;
};

}
Loading