Skip to content

Commit

Permalink
Move rect expansion to textbuffer; refactor selection code (#4560)
Browse files Browse the repository at this point in the history
- When performing chunk selection, the expansion now occurs at the time
  of the selection, not the rendering of the selection
- `GetSelectionRects()` was moved to the `TextBuffer` and is now shared
  between ConHost and Windows Terminal
- Some of the selection variables were renamed for clarity
- Selection COORDs are now in the Text Buffer coordinate space
- Fixes an issue with Shift+Click after performing a Multi-Click
  Selection

## References
This also contributes to...
- #4509: UIA Box Selection
- #2447: UIA Signaling for Selection
- #1354: UIA support for Wide Glyphs

Now that the expansion occurs at before render-time, the selection
anchors are an accurate representation of what is selected. We just need
to move `GetText` to the `TextBuffer`. Then we can have those three
issues just rely on code from the text buffer. This also means ConHost
gets some of this stuff for free 😀

### TextBuffer
- `GetTextRects` is the abstracted form of `GetSelectionRects`
- `_ExpandTextRow` is still needed to handle wide glyphs properly

### Terminal
- Rename...
    - `_boxSelection` --> `_blockSelection` for consistency with ConHost
    - `_selectionAnchor` --> `_selectionStart` for consistency with UIA
    - `_endSelectionPosition` --> `_selectionEnd` for consistency with
      UIA
- Selection anchors are in Text Buffer coordinates now
- Really rely on `SetSelectionEnd` to accomplish appropriate chunk
  selection and shift+click actions

## Validation Steps Performed
- Shift+Click
- Multi-Click --> Shift+Click
- Chunk Selection at...
    - top of buffer
    - bottom of buffer
    - random region in scrollback

Closes #4465
Closes #4547
  • Loading branch information
carlos-zamora authored Feb 28, 2020
1 parent 74cd9db commit 0e672fa
Show file tree
Hide file tree
Showing 18 changed files with 394 additions and 528 deletions.
95 changes: 95 additions & 0 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1314,6 +1314,101 @@ TextBuffer::DelimiterClass TextBuffer::_GetDelimiterClass(const std::wstring_vie
}
}

// Method Description:
// - Determines the line-by-line rectangles based on two COORDs
// - expands the rectangles to support wide glyphs
// - used for selection rects and UIA bounding rects
// Arguments:
// - start: a corner of the text region of interest (inclusive)
// - end: the other corner of the text region of interest (inclusive)
// - blockSelection: when enabled, only get the rectangular text region,
// as opposed to the text extending to the left/right
// buffer margins
// Return Value:
// - the delimiter class for the given char
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
{
std::vector<SMALL_RECT> textRects;

const auto bufferSize = GetSize();

// (0,0) is the top-left of the screen
// the physically "higher" coordinate is closer to the top-left
// the physically "lower" coordinate is closer to the bottom-right
const auto [higherCoord, lowerCoord] = bufferSize.CompareInBounds(start, end) <= 0 ?
std::make_tuple(start, end) :
std::make_tuple(end, start);

const auto textRectSize = base::ClampedNumeric<short>(1) + lowerCoord.Y - higherCoord.Y;
textRects.reserve(textRectSize);
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
{
SMALL_RECT textRow;

textRow.Top = row;
textRow.Bottom = row;

if (blockSelection || higherCoord.Y == lowerCoord.Y)
{
// set the left and right margin to the left-/right-most respectively
textRow.Left = std::min(higherCoord.X, lowerCoord.X);
textRow.Right = std::max(higherCoord.X, lowerCoord.X);
}
else
{
textRow.Left = (row == higherCoord.Y) ? higherCoord.X : bufferSize.Left();
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
}

_ExpandTextRow(textRow);
textRects.emplace_back(textRow);
}

return textRects;
}

// Method Description:
// - Expand the selection row according to include wide glyphs fully
// - this is particularly useful for box selections (ALT + selection)
// Arguments:
// - selectionRow: the selection row to be expanded
// Return Value:
// - modifies selectionRow's Left and Right values to expand properly
void TextBuffer::_ExpandTextRow(SMALL_RECT& textRow) const
{
const auto bufferSize = GetSize();

// expand left side of rect
COORD targetPoint{ textRow.Left, textRow.Top };
if (GetCellDataAt(targetPoint)->DbcsAttr().IsTrailing())
{
if (targetPoint.X == bufferSize.Left())
{
bufferSize.IncrementInBounds(targetPoint);
}
else
{
bufferSize.DecrementInBounds(targetPoint);
}
textRow.Left = targetPoint.X;
}

// expand right side of rect
targetPoint = { textRow.Right, textRow.Bottom };
if (GetCellDataAt(targetPoint)->DbcsAttr().IsLeading())
{
if (targetPoint.X == bufferSize.RightInclusive())
{
bufferSize.DecrementInBounds(targetPoint);
}
else
{
bufferSize.IncrementInBounds(targetPoint);
}
textRow.Right = targetPoint.X;
}
}

// Routine Description:
// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing).
// Arguments:
Expand Down
4 changes: 4 additions & 0 deletions src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class TextBuffer final
bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const;
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;

const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;

class TextAndColor
{
public:
Expand Down Expand Up @@ -193,6 +195,8 @@ class TextBuffer final
ROW& _GetFirstRow();
ROW& _GetPrevRowNoWrap(const ROW& row);

void _ExpandTextRow(SMALL_RECT& selectionRow) const;

enum class DelimiterClass
{
ControlChar,
Expand Down
4 changes: 2 additions & 2 deletions src/cascadia/PublicTerminalCore/HwndTerminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bo
terminalPosition.Y /= fontSize.Y;

publicTerminal->_terminal->SetSelectionAnchor(terminalPosition);
publicTerminal->_terminal->SetBoxSelection(altPressed);
publicTerminal->_terminal->SetBlockSelection(altPressed);

publicTerminal->_renderer->TriggerSelection();

Expand All @@ -354,7 +354,7 @@ HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition)
terminalPosition.X /= fontSize.X;
terminalPosition.Y /= fontSize.Y;

publicTerminal->_terminal->SetEndSelectionPosition(terminalPosition);
publicTerminal->_terminal->SetSelectionEnd(terminalPosition);
publicTerminal->_renderer->TriggerSelection();

return S_OK;
Expand Down
12 changes: 6 additions & 6 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto lock = _terminal->LockForWriting();
if (search.FindNext())
{
_terminal->SetBoxSelection(false);
_terminal->SetBlockSelection(false);
search.Select();
_renderer->TriggerSelection();
}
Expand Down Expand Up @@ -817,7 +817,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto terminalPosition = _GetTerminalPosition(cursorPosition);

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

auto clickCount = _NumberOfClicks(cursorPosition, point.Timestamp());

Expand All @@ -828,17 +828,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

if (multiClickMapper == 3)
{
_terminal->TripleClickSelection(terminalPosition);
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Line);
}
else if (multiClickMapper == 2)
{
_terminal->DoubleClickSelection(terminalPosition);
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Word);
}
else
{
if (shiftEnabled && _terminal->IsSelectionActive())
{
_terminal->SetEndSelectionPosition(terminalPosition);
_terminal->SetSelectionEnd(terminalPosition, ::Terminal::SelectionExpansionMode::Cell);
}
else
{
Expand Down Expand Up @@ -1474,7 +1474,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
terminalPosition.X = std::clamp<short>(terminalPosition.X, 0, lastVisibleCol);

// save location (for rendering) + render
_terminal->SetEndSelectionPosition(terminalPosition);
_terminal->SetSelectionEnd(terminalPosition);
_renderer->TriggerSelection();
}

Expand Down
8 changes: 3 additions & 5 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,10 @@ Terminal::Terminal() :
_pfnWriteInput{ nullptr },
_scrollOffset{ 0 },
_snapOnInput{ true },
_boxSelection{ false },
_selectionActive{ false },
_blockSelection{ false },
_selection{ std::nullopt },
_allowSingleCharSelection{ true },
_copyOnSelect{ false },
_selectionAnchor{ 0, 0 },
_endSelectionPosition{ 0, 0 }
_copyOnSelect{ false }
{
auto dispatch = std::make_unique<TerminalDispatch>(*this);
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
Expand Down
43 changes: 22 additions & 21 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class Microsoft::Terminal::Core::Terminal final :
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const noexcept override;
const COORD GetEndSelectionPosition() const noexcept override;
const COORD GetSelectionEnd() const noexcept override;
const std::wstring GetConsoleTitle() const noexcept override;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
#pragma endregion
Expand All @@ -157,12 +157,17 @@ class Microsoft::Terminal::Core::Terminal final :

#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
enum class SelectionExpansionMode
{
Cell,
Word,
Line
};
const bool IsCopyOnSelectActive() const noexcept;
void DoubleClickSelection(const COORD position);
void TripleClickSelection(const COORD position);
void MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode);
void SetSelectionAnchor(const COORD position);
void SetEndSelectionPosition(const COORD position);
void SetBoxSelection(const bool isEnabled) noexcept;
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansionMode> newExpansionMode = std::nullopt);
void SetBlockSelection(const bool isEnabled) noexcept;

const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const;
#pragma endregion
Expand All @@ -187,19 +192,20 @@ class Microsoft::Terminal::Core::Terminal final :
bool _suppressApplicationTitle;

#pragma region Text Selection
enum class SelectionExpansionMode
// a selection is represented as a range between two COORDs (start and end)
// the pivot is the COORD that remains selected when you extend a selection in any direction
// this is particularly useful when a word selection is extended over its starting point
// see TerminalSelection.cpp for more information
struct SelectionAnchors
{
Cell,
Word,
Line
COORD start;
COORD end;
COORD pivot;
};
COORD _selectionAnchor;
COORD _endSelectionPosition;
bool _boxSelection;
bool _selectionActive;
std::optional<SelectionAnchors> _selection;
bool _blockSelection;
bool _allowSingleCharSelection;
bool _copyOnSelect;
SHORT _selectionVerticalOffset;
std::wstring _wordDelimiters;
SelectionExpansionMode _multiClickSelectionMode;
#pragma endregion
Expand Down Expand Up @@ -248,15 +254,10 @@ class Microsoft::Terminal::Core::Terminal final :
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;
SHORT _ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const;
SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const;
COORD _ExpandDoubleClickSelectionLeft(const COORD position) const;
COORD _ExpandDoubleClickSelectionRight(const COORD position) const;
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos) const;
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
COORD _ConvertToBufferCell(const COORD viewportPos) const;
const bool _IsSingleCellSelection() const noexcept;
std::tuple<COORD, COORD> _PreprocessSelectionCoords() const;
SMALL_RECT _GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const;
void _ExpandSelectionRow(SMALL_RECT& selectionRow) const;
#pragma endregion

#ifdef UNIT_TESTING
Expand Down
Loading

0 comments on commit 0e672fa

Please sign in to comment.