Skip to content

Commit f07db02

Browse files
rastislavsvobodaMrJul
authored andcommitted
fix TextBox multiline selection with up/down keys till start/end of text (#18746)
* fix: TextBox multiline selection with up/down keys till start/end of text * refactor to preserve public API of TextPresenter * refactor and cleanup
1 parent 90416f3 commit f07db02

File tree

2 files changed

+118
-48
lines changed

2 files changed

+118
-48
lines changed

src/Avalonia.Controls/TextBox.cs

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,59 +1464,23 @@ protected override void OnKeyDown(KeyEventArgs e)
14641464
break;
14651465

14661466
case Key.Up:
1467+
selection = DetectSelection();
1468+
MoveVertical(LogicalDirection.Backward, selection);
1469+
if (caretIndex != _presenter.CaretIndex)
14671470
{
1468-
selection = DetectSelection();
1469-
1470-
if (!selection && SelectionStart != SelectionEnd)
1471-
{
1472-
ClearSelectionAndMoveCaretToTextPosition(LogicalDirection.Backward);
1473-
}
1474-
1475-
_presenter.MoveCaretVertical(LogicalDirection.Backward);
1476-
1477-
if (caretIndex != _presenter.CaretIndex)
1478-
{
1479-
movement = true;
1480-
}
1481-
1482-
if (selection)
1483-
{
1484-
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
1485-
}
1486-
else
1487-
{
1488-
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
1489-
}
1490-
1491-
break;
1471+
movement = true;
14921472
}
1473+
break;
1474+
14931475
case Key.Down:
1476+
selection = DetectSelection();
1477+
MoveVertical(LogicalDirection.Forward, selection);
1478+
if (caretIndex != _presenter.CaretIndex)
14941479
{
1495-
selection = DetectSelection();
1496-
1497-
if (!selection && SelectionStart != SelectionEnd)
1498-
{
1499-
ClearSelectionAndMoveCaretToTextPosition(LogicalDirection.Forward);
1500-
}
1501-
1502-
_presenter.MoveCaretVertical();
1503-
1504-
if (caretIndex != _presenter.CaretIndex)
1505-
{
1506-
movement = true;
1507-
}
1508-
1509-
if (selection)
1510-
{
1511-
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
1512-
}
1513-
else
1514-
{
1515-
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
1516-
}
1517-
1518-
break;
1480+
movement = true;
15191481
}
1482+
break;
1483+
15201484
case Key.Back:
15211485
{
15221486
SnapshotUndoRedo();
@@ -2038,6 +2002,50 @@ private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting, boo
20382002
}
20392003
}
20402004

2005+
private void MoveVertical(LogicalDirection direction, bool isSelecting)
2006+
{
2007+
if (_presenter is null)
2008+
{
2009+
return;
2010+
}
2011+
2012+
if (isSelecting)
2013+
{
2014+
var oldCaretIndex = _presenter.CaretIndex;
2015+
_presenter.MoveCaretVertical(direction);
2016+
var newCaretIndex = _presenter.CaretIndex;
2017+
2018+
if (oldCaretIndex == newCaretIndex)
2019+
{
2020+
var text = Text ?? string.Empty;
2021+
2022+
// caret did not move while we are selecting so we could not move to previous/next line,
2023+
// but check if we are already at the 'boundary' of the text
2024+
if (direction == LogicalDirection.Forward && newCaretIndex < text.Length)
2025+
{
2026+
_presenter.MoveCaretToTextPosition(text.Length);
2027+
}
2028+
else if (direction == LogicalDirection.Backward && newCaretIndex > 0)
2029+
{
2030+
_presenter.MoveCaretToTextPosition(0);
2031+
}
2032+
}
2033+
2034+
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
2035+
}
2036+
else
2037+
{
2038+
if (SelectionStart != SelectionEnd)
2039+
{
2040+
ClearSelectionAndMoveCaretToTextPosition(direction);
2041+
}
2042+
2043+
_presenter.MoveCaretVertical(direction);
2044+
2045+
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
2046+
}
2047+
}
2048+
20412049
private void MoveHome(bool document)
20422050
{
20432051
if (_presenter is null)

tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,6 +1875,68 @@ public void When_Select_All_From_Position_Down_Should_Remove_Selection_Moving_Ca
18751875
}
18761876
}
18771877

1878+
[Theory]
1879+
[InlineData(0)]
1880+
[InlineData(4)]
1881+
[InlineData(8)]
1882+
public void When_Selecting_Multiline_Selection_Should_Be_Extended_With_Up_Arrow_Key_Till_Start_Of_Text(int caretOffsetFromEnd)
1883+
{
1884+
using (UnitTestApplication.Start(Services))
1885+
{
1886+
var tb = new TextBox
1887+
{
1888+
Template = CreateTemplate(),
1889+
Text = """
1890+
AAAAAA
1891+
BBBB
1892+
CCCCCCCC
1893+
""",
1894+
AcceptsReturn = true
1895+
};
1896+
tb.ApplyTemplate();
1897+
tb.Measure(Size.Infinity);
1898+
tb.CaretIndex = tb.Text.Length - caretOffsetFromEnd;
1899+
1900+
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
1901+
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
1902+
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
1903+
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
1904+
1905+
Assert.Equal(0, tb.SelectionEnd);
1906+
}
1907+
}
1908+
1909+
[Theory]
1910+
[InlineData(0)]
1911+
[InlineData(3)]
1912+
[InlineData(6)]
1913+
public void When_Selecting_Multiline_Selection_Should_Be_Extended_With_Down_Arrow_Key_Till_End_Of_Text(int caretOffsetFromStart)
1914+
{
1915+
using (UnitTestApplication.Start(Services))
1916+
{
1917+
var tb = new TextBox
1918+
{
1919+
Template = CreateTemplate(),
1920+
Text = """
1921+
AAAAAA
1922+
BBBB
1923+
CCCCCCCC
1924+
""",
1925+
AcceptsReturn = true
1926+
};
1927+
tb.ApplyTemplate();
1928+
tb.Measure(Size.Infinity);
1929+
tb.CaretIndex = caretOffsetFromStart;
1930+
1931+
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
1932+
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
1933+
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
1934+
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
1935+
1936+
Assert.Equal(tb.Text.Length, tb.SelectionEnd);
1937+
}
1938+
}
1939+
18781940
[Fact]
18791941
public void TextBox_In_AdornerLayer_Will_Not_Cause_Collection_Modified_In_VisualLayerManager_Measure()
18801942
{

0 commit comments

Comments
 (0)