Compare commits

...

11 commits

Author SHA1 Message Date
Carlos Zamora ad2678051f Add selection marker overlays for keyboard selection 2021-09-23 12:23:41 -07:00
Carlos Zamora b7842624a0 address Leonard's comments 2021-09-23 10:54:18 -07:00
Carlos Zamora 30009eb0eb polish and fix build 2021-09-16 20:13:28 -07:00
Carlos Zamora 118d1cdc02 remove configurability 2021-09-16 19:58:42 -07:00
Carlos Zamora b13b8a7896 remove unnecessary explicit namespace 2021-09-16 18:25:42 -07:00
Carlos Zamora bf0cf5f0d2 add TODO GH behind for duplicate enum 2021-09-16 18:25:41 -07:00
Carlos Zamora 724cd2c2ca fix build 2021-09-16 18:25:41 -07:00
Carlos Zamora 8a646138f4 fix build (primarily PublicTerminalCore) 2021-09-16 18:25:41 -07:00
Carlos Zamora a0e4f2dd05 fix schema 2021-09-16 18:25:41 -07:00
Carlos Zamora bcbf01e8cb apply feedback from zadji 2021-09-16 18:25:41 -07:00
Carlos Zamora 428dec9702 Implement Keyboard Selection 2021-09-16 18:25:37 -07:00
20 changed files with 470 additions and 60 deletions

View file

@ -847,7 +847,7 @@
}
],
"required": [ "actions" ]
},
},
"CommandPaletteAction": {
"description": "Arguments for a commandPalette action",
"allOf": [

View file

@ -1430,12 +1430,13 @@ const til::point TextBuffer::GetGlyphStart(const til::point pos, std::optional<t
}
// Method Description:
// - Update pos to be the end of the current glyph/character. This is used for accessibility
// - Update pos to be the end of the current glyph/character.
// Arguments:
// - pos - a COORD on the word you are currently on
// - accessibilityMode - this is being used for accessibility; make the end exclusive.
// Return Value:
// - pos - The COORD for the last cell of the current glyph (exclusive)
const til::point TextBuffer::GetGlyphEnd(const til::point pos, std::optional<til::point> limitOptional) const
const til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode, std::optional<til::point> limitOptional) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
@ -1453,7 +1454,10 @@ const til::point TextBuffer::GetGlyphEnd(const til::point pos, std::optional<til
}
// increment one more time to become exclusive
bufferSize.IncrementInBounds(resultPos, true);
if (accessibilityMode)
{
bufferSize.IncrementInBounds(resultPos, true);
}
return resultPos;
}

View file

@ -147,7 +147,7 @@ public:
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;
const til::point GetGlyphStart(const til::point pos, std::optional<til::point> limitOptional = std::nullopt) const;
const til::point GetGlyphEnd(const til::point pos, std::optional<til::point> limitOptional = std::nullopt) const;
const til::point GetGlyphEnd(const til::point pos, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false, std::optional<til::point> limitOptional = std::nullopt) const;
bool MoveToPreviousGlyph(til::point& pos, std::optional<til::point> limitOptional = std::nullopt) const;

View file

@ -549,11 +549,11 @@ try
if (multiClickMapper == 3)
{
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Line);
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansion::Line);
}
else if (multiClickMapper == 2)
{
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Word);
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansion::Word);
}
else
{

View file

@ -359,28 +359,40 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const ControlKeyStates modifiers,
const bool keyDown)
{
// When there is a selection active, escape should clear it and NOT flow through
// to the terminal. With any other keypress, it should clear the selection AND
// flow through to the terminal.
// Update the selection, if it's present
// GH#6423 - don't dismiss selection if the key that was pressed was a
// modifier key. We'll wait for a real keystroke to dismiss the
// GH #7395 - don't dismiss selection when taking PrintScreen
// selection.
// GH#8522, GH#3758 - Only dismiss the selection on key _down_. If we
// dismiss on key up, then there's chance that we'll immediately dismiss
// GH#8522, GH#3758 - Only modify the selection on key _down_. If we
// modify on key up, then there's chance that we'll immediately dismiss
// a selection created by an action bound to a keydown.
if (HasSelection() &&
!KeyEvent::IsModifierKey(vkey) &&
vkey != VK_SNAPSHOT &&
keyDown)
{
// try to update the selection
if (const auto updateSlnParams{ ::Terminal::ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) })
{
auto lock = _terminal->LockForWriting();
_terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second);
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(false));
return true;
}
// GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination.
if (!modifiers.IsWinPressed())
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}
// When there is a selection active, escape should clear it and NOT flow through
// to the terminal. With any other keypress, it should clear the selection AND
// flow through to the terminal.
if (vkey == VK_ESCAPE)
{
return true;
@ -899,6 +911,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetSelectionAnchor(position);
}
Core::Point ControlCore::SelectionAnchor() const
{
auto lock = _terminal->LockForReading();
return til::point{ _terminal->SelectionStartForRendering() };
}
Core::Point ControlCore::SelectionEnd() const
{
auto lock = _terminal->LockForReading();
return til::point{ _terminal->SelectionEndForRendering() };
}
bool ControlCore::MovingStart() const
{
auto lock = _terminal->LockForReading();
return _terminal->MovingStart();
}
// Method Description:
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
// Arguments:
@ -925,6 +955,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// save location (for rendering) + render
_terminal->SetSelectionEnd(terminalPosition);
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}
// Called when the Terminal wants to set something to the clipboard, i.e.
@ -985,6 +1016,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}
// send data up for clipboard
@ -1276,6 +1308,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetBlockSelection(false);
search.Select();
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}
}
@ -1422,18 +1455,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// handle ALT key
_terminal->SetBlockSelection(altEnabled);
::Terminal::SelectionExpansionMode mode = ::Terminal::SelectionExpansionMode::Cell;
::Terminal::SelectionExpansion mode = ::Terminal::SelectionExpansion::Char;
if (numberOfClicks == 1)
{
mode = ::Terminal::SelectionExpansionMode::Cell;
mode = ::Terminal::SelectionExpansion::Char;
}
else if (numberOfClicks == 2)
{
mode = ::Terminal::SelectionExpansionMode::Word;
mode = ::Terminal::SelectionExpansion::Word;
}
else if (numberOfClicks == 3)
{
mode = ::Terminal::SelectionExpansionMode::Line;
mode = ::Terminal::SelectionExpansion::Line;
}
// Update the selection appropriately
@ -1458,7 +1491,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetSelectionEnd(terminalPosition, mode);
selectionNeedsToBeCopied = true;
}
else if (mode != ::Terminal::SelectionExpansionMode::Cell || shiftEnabled)
else if (mode != ::Terminal::SelectionExpansion::Char || shiftEnabled)
{
// If we are handling a double / triple-click or shift+single click
// we establish selection using the selected mode
@ -1468,6 +1501,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}
void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine)
@ -1557,5 +1591,4 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return hstring(ss.str());
}
}

View file

@ -128,6 +128,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool HasSelection() const;
bool CopyOnSelect() const;
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
Core::Point SelectionAnchor() const;
Core::Point SelectionEnd() const;
bool MovingStart() const;
void SetSelectionAnchor(til::point const& position);
void SetEndSelectionPoint(til::point const& position);
@ -169,6 +172,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
TYPED_EVENT(TransparencyChanged, IInspectable, Control::TransparencyChangedEventArgs);
TYPED_EVENT(ReceivedOutput, IInspectable, IInspectable);
TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs);
// clang-format on
private:

View file

@ -80,6 +80,9 @@ namespace Microsoft.Terminal.Control
Boolean HasSelection { get; };
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
Microsoft.Terminal.Core.Point SelectionAnchor { get; };
Microsoft.Terminal.Core.Point SelectionEnd { get; };
Boolean MovingStart { get; };
String HoveredUriText { get; };
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
@ -110,6 +113,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, NoticeEventArgs> RaiseNotice;
event Windows.Foundation.TypedEventHandler<Object, TransparencyChangedEventArgs> TransparencyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
};
}

View file

@ -11,3 +11,4 @@
#include "ScrollPositionChangedArgs.g.cpp"
#include "RendererWarningArgs.g.cpp"
#include "TransparencyChangedEventArgs.g.cpp"
#include "UpdateSelectionMarkersEventArgs.g.cpp"

View file

@ -11,6 +11,7 @@
#include "ScrollPositionChangedArgs.g.h"
#include "RendererWarningArgs.g.h"
#include "TransparencyChangedEventArgs.g.h"
#include "UpdateSelectionMarkersEventArgs.g.h"
#include "cppwinrt_utils.h"
namespace winrt::Microsoft::Terminal::Control::implementation
@ -131,4 +132,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(double, Opacity);
};
struct UpdateSelectionMarkersEventArgs : public UpdateSelectionMarkersEventArgsT<UpdateSelectionMarkersEventArgs>
{
public:
UpdateSelectionMarkersEventArgs(const bool clearMarkers) :
_ClearMarkers(clearMarkers)
{
}
WINRT_PROPERTY(bool, ClearMarkers, false);
};
}

View file

@ -67,4 +67,9 @@ namespace Microsoft.Terminal.Control
{
Double Opacity { get; };
}
runtimeclass UpdateSelectionMarkersEventArgs
{
Boolean ClearMarkers { get; };
}
}

View file

@ -83,6 +83,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.TransparencyChanged({ this, &TermControl::_coreTransparencyChanged });
_core.RaiseNotice({ this, &TermControl::_coreRaisedNotice });
_core.HoveredHyperlinkChanged({ this, &TermControl::_hoveredHyperlinkChanged });
_core.UpdateSelectionMarkers({ this, &TermControl::_updateSelectionMarkers });
_interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
_interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });
@ -333,6 +334,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto bg = newAppearance.DefaultBackground();
_changeBackgroundColor(bg);
// Update selection markers
Windows::UI::Xaml::Media::SolidColorBrush selectionBackgroundBrush{ til::color{ newAppearance.SelectionBackground() } };
SelectionStartIcon().Foreground(selectionBackgroundBrush);
SelectionEndIcon().Foreground(selectionBackgroundBrush);
// Set TSF Foreground
Media::SolidColorBrush foregroundBrush{};
foregroundBrush.Color(static_cast<til::color>(newAppearance.DefaultForeground()));
@ -1659,6 +1665,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
update.newValue = args.ViewTop();
_updateScrollBar->Run(update);
_updatePatternLocations->Run();
_updateSelectionMarkers(nullptr, winrt::make<UpdateSelectionMarkersEventArgs>(false));
}
// Method Description:
@ -2464,8 +2472,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.ClearHoveredCell();
}
winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable sender,
IInspectable args)
winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable /*sender*/,
IInspectable /*args*/)
{
auto weakThis{ get_weak() };
co_await resume_foreground(Dispatcher());
@ -2488,12 +2496,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
HyperlinkTooltipBorder().BorderThickness(newThickness);
// Compute the location of the top left corner of the cell in DIPS
const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top };
const til::point startPos{ lastHoveredCell.Value() };
const til::size fontSize{ til::math::rounding, _core.FontSize() };
const til::point posInPixels{ startPos * fontSize };
const til::point posInDIPs{ posInPixels / SwapChainPanel().CompositionScaleX() };
const til::point locationInDIPs{ posInDIPs + marginsInDips };
const til::point locationInDIPs{ _toPosInDips(lastHoveredCell.Value()) };
// Move the border to the top left corner of the cell
OverlayCanvas().SetLeft(HyperlinkTooltipBorder(),
@ -2505,10 +2508,71 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
winrt::fire_and_forget TermControl::_updateSelectionMarkers(IInspectable /*sender*/, Control::UpdateSelectionMarkersEventArgs args)
{
auto weakThis{ get_weak() };
co_await resume_foreground(Dispatcher());
if (weakThis.get() && args)
{
if (_core.HasSelection() && !args.ClearMarkers())
{
// show/update selection markers
// figure out which endpoint to move, get it and the relevant icon (hide the other icon)
const auto movingStart{ _core.MovingStart() };
const auto selectionAnchor{ movingStart ? _core.SelectionAnchor() : _core.SelectionEnd() };
const auto& icon{ movingStart ? SelectionStartIcon() : SelectionEndIcon() };
const auto& otherIcon{ movingStart ? SelectionEndIcon() : SelectionStartIcon() };
icon.Opacity(1);
otherIcon.Opacity(0);
// Compute the location of the top left corner of the cell in DIPS
const til::point locationInDIPs{ _toPosInDips(selectionAnchor) };
// Move the icon to the top left corner of the cell
SelectionCanvas().SetLeft(icon,
(locationInDIPs.x() - SwapChainPanel().ActualOffset().x));
SelectionCanvas().SetTop(icon,
(locationInDIPs.y() - SwapChainPanel().ActualOffset().y));
}
else
{
// hide selection markers
SelectionStartIcon().Opacity(0);
SelectionEndIcon().Opacity(0);
}
}
}
til::point TermControl::_toPosInDips(const til::point terminalCellPos)
{
const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top };
const til::size fontSize{ til::math::rounding, _core.FontSize() };
const til::point posInPixels{ terminalCellPos * fontSize };
const til::point posInDIPs{ posInPixels / SwapChainPanel().CompositionScaleX() };
return posInDIPs + marginsInDips;
}
void TermControl::_coreFontSizeChanged(const int fontWidth,
const int fontHeight,
const bool isInitialChange)
{
// scale the selection markers to be the size of a cell
auto scaleIconMarker = [fontWidth, fontHeight](Windows::UI::Xaml::Controls::FontIcon icon) {
const auto size{ icon.DesiredSize() };
const auto scaleX = fontWidth / size.Width;
const auto scaleY = fontHeight / size.Height;
Windows::UI::Xaml::Media::ScaleTransform transform{};
transform.ScaleX(transform.ScaleX() * scaleX);
transform.ScaleY(transform.ScaleY() * scaleY);
icon.RenderTransform(transform);
// now hide the icon
icon.Opacity(0);
};
scaleIconMarker(SelectionStartIcon());
scaleIconMarker(SelectionEndIcon());
// Don't try to inspect the core here. The Core is raising this while
// it's holding its write lock. If the handlers calls back to some
// method on the TermControl on the same thread, and that _method_ calls

View file

@ -267,6 +267,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
winrt::fire_and_forget _hoveredHyperlinkChanged(IInspectable sender, IInspectable args);
winrt::fire_and_forget _updateSelectionMarkers(IInspectable sender, Control::UpdateSelectionMarkersEventArgs args);
void _coreFontSizeChanged(const int fontWidth,
const int fontHeight,
@ -274,6 +275,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args);
void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args);
void _coreWarningBell(const IInspectable& sender, const IInspectable& args);
til::point _toPosInDips(const til::point terminalCellPos);
};
}

View file

@ -73,6 +73,23 @@
</ToolTipService.ToolTip>
</Border>
</Canvas>
<Canvas x:Name="SelectionCanvas"
Visibility="Visible">
<!-- LOAD BEARING Use Opacity instead of Visibility for these two FontIcons.
Visibility::Collapsed prevents us from aquiring the desired size of the icons,
resulting in an inability to scale them upon a FontSize change event -->
<FontIcon x:Name="SelectionStartIcon"
FontFamily="Segoe MDL2 Assets"
Glyph="&#xE893;"
Opacity="0">
</FontIcon>
<FontIcon x:Name="SelectionEndIcon"
FontFamily="Segoe MDL2 Assets"
Glyph="&#xE892;"
Opacity="0">
</FontIcon>
</Canvas>
</SwapChainPanel>
<!--

View file

@ -227,16 +227,33 @@ public:
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
enum class SelectionExpansionMode
enum class SelectionDirection
{
Cell,
Word,
Line
Left,
Right,
Up,
Down
};
void MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode);
enum class SelectionExpansion
{
Char,
Word,
Line, // Mouse selection only!
Viewport,
Buffer
};
void MultiClickSelection(const COORD viewportPos, SelectionExpansion expansionMode);
void SetSelectionAnchor(const COORD position);
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansionMode> newExpansionMode = std::nullopt);
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansion> newExpansionMode = std::nullopt);
void SetBlockSelection(const bool isEnabled) noexcept;
void UpdateSelection(SelectionDirection direction, SelectionExpansion mode);
using UpdateSelectionParams = std::optional<std::pair<SelectionDirection, SelectionExpansion>>;
static UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey);
bool MovingStart() const noexcept;
til::point SelectionStartForRendering() const;
til::point SelectionEndForRendering() const;
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace);
#pragma endregion
@ -308,7 +325,7 @@ private:
std::optional<SelectionAnchors> _selection;
bool _blockSelection;
std::wstring _wordDelimiters;
SelectionExpansionMode _multiClickSelectionMode;
SelectionExpansion _multiClickSelectionMode;
#pragma endregion
// TODO: These members are not shared by an alt-buffer. They should be
@ -375,6 +392,10 @@ private:
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos, bool& targetStart) const;
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
COORD _ConvertToBufferCell(const COORD viewportPos) const;
void _MoveByChar(SelectionDirection direction, COORD& pos);
void _MoveByWord(SelectionDirection direction, COORD& pos);
void _MoveByViewport(SelectionDirection direction, COORD& pos);
void _MoveByBuffer(SelectionDirection direction, COORD& pos);
#pragma endregion
Microsoft::Console::VirtualTerminal::SgrStack _sgrStack;

View file

@ -82,6 +82,24 @@ const COORD Terminal::GetSelectionEnd() const noexcept
return _selection->end;
}
til::point Terminal::SelectionStartForRendering() const
{
auto pos{ _selection->start };
const auto bufferSize{ _buffer->GetSize() };
bufferSize.DecrementInBounds(pos);
pos.Y = std::clamp<short>(base::ClampSub(pos.Y, _VisibleStartIndex()), bufferSize.Top(), bufferSize.BottomInclusive());
return pos;
}
til::point Terminal::SelectionEndForRendering() const
{
auto pos{ _selection->end };
const auto bufferSize{ _buffer->GetSize() };
bufferSize.IncrementInBounds(pos);
pos.Y = std::clamp<short>(base::ClampSub(pos.Y, _VisibleStartIndex()), bufferSize.Top(), bufferSize.BottomInclusive());
return pos;
}
// Method Description:
// - Checks if selection is active
// Return Value:
@ -100,8 +118,8 @@ const bool Terminal::IsBlockSelection() const noexcept
// - Perform a multi-click selection at viewportPos expanding according to the expansionMode
// Arguments:
// - viewportPos: the (x,y) coordinate on the visible viewport
// - expansionMode: the SelectionExpansionMode to dictate the boundaries of the selection anchors
void Terminal::MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode)
// - expansionMode: the SelectionExpansion to dictate the boundaries of the selection anchors
void Terminal::MultiClickSelection(const COORD viewportPos, SelectionExpansion expansionMode)
{
// set the selection pivot to expand the selection using SetSelectionEnd()
_selection = SelectionAnchors{};
@ -124,7 +142,7 @@ void Terminal::SetSelectionAnchor(const COORD viewportPos)
_selection = SelectionAnchors{};
_selection->pivot = _ConvertToBufferCell(viewportPos);
_multiClickSelectionMode = SelectionExpansionMode::Cell;
_multiClickSelectionMode = SelectionExpansion::Char;
SetSelectionEnd(viewportPos);
_selection->start = _selection->pivot;
@ -136,7 +154,7 @@ void Terminal::SetSelectionAnchor(const COORD viewportPos)
// Arguments:
// - viewportPos: the (x,y) coordinate on the visible viewport
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for ShiftClick
void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionExpansionMode> newExpansionMode)
void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionExpansion> newExpansionMode)
{
if (!_selection.has_value())
{
@ -210,15 +228,15 @@ std::pair<COORD, COORD> Terminal::_ExpandSelectionAnchors(std::pair<COORD, COORD
const auto bufferSize = _buffer->GetSize();
switch (_multiClickSelectionMode)
{
case SelectionExpansionMode::Line:
case SelectionExpansion::Line:
start = { bufferSize.Left(), start.Y };
end = { bufferSize.RightInclusive(), end.Y };
break;
case SelectionExpansionMode::Word:
case SelectionExpansion::Word:
start = _buffer->GetWordStart(start, _wordDelimiters);
end = _buffer->GetWordEnd(end, _wordDelimiters);
break;
case SelectionExpansionMode::Cell:
case SelectionExpansion::Char:
default:
// no expansion is necessary
break;
@ -235,6 +253,229 @@ void Terminal::SetBlockSelection(const bool isEnabled) noexcept
_blockSelection = isEnabled;
}
Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey)
{
if (mods.IsShiftPressed() && !mods.IsAltPressed())
{
if (mods.IsCtrlPressed())
{
// Ctrl + Shift + _
switch (vkey)
{
case VK_LEFT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Word };
case VK_RIGHT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Word };
case VK_HOME:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Buffer };
case VK_END:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Buffer };
}
}
else
{
// Shift + _
switch (vkey)
{
case VK_HOME:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Viewport };
case VK_END:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Viewport };
case VK_PRIOR:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Up, SelectionExpansion::Viewport };
case VK_NEXT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Down, SelectionExpansion::Viewport };
case VK_LEFT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Char };
case VK_RIGHT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Char };
case VK_UP:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Up, SelectionExpansion::Char };
case VK_DOWN:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Down, SelectionExpansion::Char };
}
}
}
return std::nullopt;
}
void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion mode)
{
// 1. Figure out which endpoint to update
// One of the endpoints is the pivot, signifying that the other endpoint is the one we want to move.
const bool movingEnd{ _selection->start == _selection->pivot };
auto targetPos{ movingEnd ? _selection->end : _selection->start };
// 2. Perform the movement
switch (mode)
{
case SelectionExpansion::Char:
_MoveByChar(direction, targetPos);
break;
case SelectionExpansion::Word:
_MoveByWord(direction, targetPos);
break;
case SelectionExpansion::Viewport:
_MoveByViewport(direction, targetPos);
break;
case SelectionExpansion::Buffer:
_MoveByBuffer(direction, targetPos);
break;
}
// 3. Actually modify the selection
// NOTE: targetStart doesn't matter here
bool targetStart = false;
std::tie(_selection->start, _selection->end) = _PivotSelection(targetPos, targetStart);
// 4. Scroll (if necessary)
if (const auto viewport = _GetVisibleViewport(); !viewport.IsInBounds(targetPos))
{
if (const auto amtAboveView = viewport.Top() - targetPos.Y; amtAboveView > 0)
{
// anchor is above visible viewport, scroll by that amount
_scrollOffset += amtAboveView;
}
else
{
// anchor is below visible viewport, scroll by that amount
const auto amtBelowView = targetPos.Y - viewport.BottomInclusive();
_scrollOffset -= amtBelowView;
}
_NotifyScrollEvent();
_buffer->GetRenderTarget().TriggerScroll();
}
}
void Terminal::_MoveByChar(SelectionDirection direction, COORD& pos)
{
switch (direction)
{
case SelectionDirection::Left:
_buffer->GetSize().DecrementInBounds(pos);
pos = _buffer->GetGlyphStart(pos);
break;
case SelectionDirection::Right:
_buffer->GetSize().IncrementInBounds(pos);
pos = _buffer->GetGlyphEnd(pos);
break;
case SelectionDirection::Up:
{
const auto bufferSize{ _buffer->GetSize() };
pos = { pos.X, std::clamp(base::ClampSub<short, short>(pos.Y, 1).RawValue(), bufferSize.Top(), bufferSize.BottomInclusive()) };
break;
}
case SelectionDirection::Down:
{
const auto bufferSize{ _buffer->GetSize() };
pos = { pos.X, std::clamp(base::ClampAdd<short, short>(pos.Y, 1).RawValue(), bufferSize.Top(), bufferSize.BottomInclusive()) };
break;
}
}
}
void Terminal::_MoveByWord(SelectionDirection direction, COORD& pos)
{
switch (direction)
{
case SelectionDirection::Left:
const auto wordStartPos{ _buffer->GetWordStart(pos, _wordDelimiters) };
if (_buffer->GetSize().CompareInBounds(_selection->pivot, pos) < 0)
{
// If we're moving towards the pivot, move one more cell
pos = wordStartPos;
_buffer->GetSize().DecrementInBounds(pos);
}
else if (wordStartPos == pos)
{
// already at the beginning of the current word,
// move to the beginning of the previous word
_buffer->GetSize().DecrementInBounds(pos);
pos = _buffer->GetWordStart(pos, _wordDelimiters);
}
else
{
// move to the beginning of the current word
pos = wordStartPos;
}
break;
case SelectionDirection::Right:
const auto wordEndPos{ _buffer->GetWordEnd(pos, _wordDelimiters) };
if (_buffer->GetSize().CompareInBounds(pos, _selection->pivot) < 0)
{
// If we're moving towards the pivot, move one more cell
pos = _buffer->GetWordEnd(pos, _wordDelimiters);
_buffer->GetSize().IncrementInBounds(pos);
}
else if (wordEndPos == pos)
{
// already at the end of the current word,
// move to the end of the next word
_buffer->GetSize().IncrementInBounds(pos);
pos = _buffer->GetWordEnd(pos, _wordDelimiters);
}
else
{
// move to the end of the current word
pos = wordEndPos;
}
break;
case SelectionDirection::Up:
_MoveByChar(direction, pos);
pos = _buffer->GetWordStart(pos, _wordDelimiters);
break;
case SelectionDirection::Down:
_MoveByChar(direction, pos);
pos = _buffer->GetWordEnd(pos, _wordDelimiters);
break;
}
}
void Terminal::_MoveByViewport(SelectionDirection direction, COORD& pos)
{
const auto bufferSize{ _buffer->GetSize() };
switch (direction)
{
case SelectionDirection::Left:
pos = { bufferSize.Left(), pos.Y };
break;
case SelectionDirection::Right:
pos = { bufferSize.RightInclusive(), pos.Y };
break;
case SelectionDirection::Up:
{
const auto viewportHeight{ _mutableViewport.Height() };
const auto newY{ base::ClampSub<short, short>(pos.Y, viewportHeight) };
pos = newY < bufferSize.Top() ? bufferSize.Origin() : COORD{ pos.X, newY };
break;
}
case SelectionDirection::Down:
{
const auto viewportHeight{ _mutableViewport.Height() };
const auto mutableBottom{ _mutableViewport.BottomInclusive() };
const auto newY{ base::ClampAdd<short, short>(pos.Y, viewportHeight) };
pos = newY > mutableBottom ? COORD{ bufferSize.RightInclusive(), mutableBottom } : COORD{ pos.X, newY };
break;
}
}
}
void Terminal::_MoveByBuffer(SelectionDirection direction, COORD& pos)
{
const auto bufferSize{ _buffer->GetSize() };
switch (direction)
{
case SelectionDirection::Left:
case SelectionDirection::Up:
pos = bufferSize.Origin();
break;
case SelectionDirection::Right:
case SelectionDirection::Down:
pos = { bufferSize.RightInclusive(), _mutableViewport.BottomInclusive() };
break;
}
}
// Method Description:
// - clear selection data and disable rendering it
#pragma warning(disable : 26440) // changing this to noexcept would require a change to ConHost's selection model

View file

@ -206,7 +206,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
realCoordEnd.Y -= gsl::narrow<short>(_VisibleStartIndex());
SetSelectionAnchor(realCoordStart);
SetSelectionEnd(realCoordEnd, SelectionExpansionMode::Cell);
SetSelectionEnd(realCoordEnd, SelectionExpansion::Char);
}
const std::wstring_view Terminal::GetConsoleTitle() const noexcept

View file

@ -130,7 +130,7 @@ namespace TerminalCoreUnitTests
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Word);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Word);
ValidateSingleRowSelection(term, expected);
};
@ -142,7 +142,7 @@ namespace TerminalCoreUnitTests
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Line);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Line);
ValidateSingleRowSelection(term, expected);
};
@ -501,7 +501,7 @@ namespace TerminalCoreUnitTests
// Simulate double click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, (4 + gsl::narrow<SHORT>(text.size()) - 1), 10 }));
@ -519,7 +519,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
@ -546,7 +546,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (15,10)
// this is over the '>' char
auto clickPos = COORD{ 15, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
// ---Validate selection area---
// "Terminal" is in class 2
@ -572,7 +572,7 @@ namespace TerminalCoreUnitTests
term.Write(text);
// Simulate double click at (x,y) = (5,10)
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansionMode::Word);
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansion::Word);
// Simulate move to (x,y) = (21,10)
//
@ -601,7 +601,7 @@ namespace TerminalCoreUnitTests
term.Write(text);
// Simulate double click at (x,y) = (21,10)
term.MultiClickSelection({ 21, 10 }, Terminal::SelectionExpansionMode::Word);
term.MultiClickSelection({ 21, 10 }, Terminal::SelectionExpansion::Word);
// Simulate move to (x,y) = (5,10)
//
@ -622,7 +622,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
@ -636,7 +636,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
// Simulate move to (x,y) = (7,10)
term.SetSelectionEnd({ 7, 10 });
@ -653,7 +653,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
// Simulate move to (x,y) = (5,11)
term.SetSelectionEnd({ 5, 11 });
@ -691,7 +691,7 @@ namespace TerminalCoreUnitTests
// Step 1: Create a selection on "doubleClickMe"
{
// Simulate double click at (x,y) = (5,10)
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansionMode::Word);
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansion::Word);
// Validate selection area: "doubleClickMe" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 16, 10 }));
@ -704,7 +704,7 @@ namespace TerminalCoreUnitTests
// buffer: doubleClickMe dragThroughHere
// ^ ^
// start finish
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Cell);
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Char);
// Validate selection area: "doubleClickMe drag" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 21, 10 }));
@ -717,7 +717,7 @@ namespace TerminalCoreUnitTests
// buffer: doubleClickMe dragThroughHere
// ^ ^ ^
// start click finish
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Word);
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Word);
// Validate selection area: "doubleClickMe dragThroughHere" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
@ -730,7 +730,7 @@ namespace TerminalCoreUnitTests
// buffer: doubleClickMe dragThroughHere |
// ^ ^ ^
// start click finish (boundary)
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Line);
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Line);
// Validate selection area: "doubleClickMe dragThroughHere..." selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 99, 10 }));
@ -743,7 +743,7 @@ namespace TerminalCoreUnitTests
// buffer: doubleClickMe dragThroughHere
// ^ ^ ^
// start click finish
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Word);
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Word);
// Validate selection area: "doubleClickMe dragThroughHere" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
@ -825,7 +825,7 @@ namespace TerminalCoreUnitTests
// Step 4: Shift+Click at (5,10)
{
term.SetSelectionEnd({ 5, 10 }, ::Terminal::SelectionExpansionMode::Cell);
term.SetSelectionEnd({ 5, 10 }, Terminal::SelectionExpansion::Char);
// Validate selection area
// NOTE: Pivot should still be (10, 10)
@ -834,7 +834,7 @@ namespace TerminalCoreUnitTests
// Step 5: Shift+Click back at (20,10)
{
term.SetSelectionEnd({ 20, 10 }, ::Terminal::SelectionExpansionMode::Cell);
term.SetSelectionEnd({ 20, 10 }, Terminal::SelectionExpansion::Char);
// Validate selection area
// NOTE: Pivot should still be (10, 10)

View file

@ -2253,7 +2253,7 @@ void TextBufferTests::GetGlyphBoundaries()
_buffer->Write(iter, target);
auto start = _buffer->GetGlyphStart(target);
auto end = _buffer->GetGlyphEnd(target);
auto end = _buffer->GetGlyphEnd(target, true);
VERIFY_ARE_EQUAL(test.start, start);
VERIFY_ARE_EQUAL(wideGlyph ? test.wideGlyphEnd : test.normalEnd, end);

View file

@ -493,6 +493,7 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
// actual failure from the API itself.
[[nodiscard]] HRESULT DxEngine::_CreateSurfaceHandle() noexcept
{
#pragma warning(suppress : 26447)
wil::unique_hmodule hDComp{ LoadLibraryEx(L"Dcomp.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) };
RETURN_LAST_ERROR_IF(hDComp.get() == nullptr);

View file

@ -295,7 +295,7 @@ void UiaTextRangeBase::_expandToEnclosingUnit(TextUnit unit)
if (unit == TextUnit_Character)
{
_start = buffer.GetGlyphStart(_start, documentEnd);
_end = buffer.GetGlyphEnd(_start, documentEnd);
_end = buffer.GetGlyphEnd(_start, true, documentEnd);
}
else if (unit <= TextUnit_Word)
{