diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index fbe1aac91a..a1d913d40a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -304,7 +304,7 @@ namespace Avalonia.Controls break; case Key.X: - if(modifiers == InputModifiers.Control) + if (modifiers == InputModifiers.Control) { Copy(); DeleteSelection(); @@ -360,6 +360,11 @@ namespace Avalonia.Controls break; case Key.Back: + if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd) + { + SetSelectionForControlBackspace(modifiers); + } + if (!DeleteSelection() && CaretIndex > 0) { CaretIndex -= DeleteCharacter(CaretIndex - 1); @@ -368,6 +373,11 @@ namespace Avalonia.Controls break; case Key.Delete: + if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd) + { + SetSelectionForControlDelete(modifiers); + } + if (!DeleteSelection() && caretIndex < text.Length) { DeleteCharacter(CaretIndex); @@ -700,6 +710,26 @@ namespace Avalonia.Controls return i; } + private void SetSelectionForControlBackspace(InputModifiers modifiers) + { + SelectionStart = CaretIndex; + MoveHorizontal(-1, modifiers); + SelectionEnd = CaretIndex; + } + + private void SetSelectionForControlDelete(InputModifiers modifiers) + { + SelectionStart = CaretIndex; + MoveHorizontal(1, modifiers); + SelectionEnd = CaretIndex; + + string selection = GetSelection(); + if (selection != " " && selection.EndsWith(" ")) + { + SelectionEnd = CaretIndex - 1; + } + } + UndoRedoState UndoRedoHelper.IUndoRedoHost.UndoRedoState { get { return new UndoRedoState(Text, CaretIndex); } diff --git a/src/Avalonia.Controls/Utils/StringUtils.cs b/src/Avalonia.Controls/Utils/StringUtils.cs index 8845631bba..8571d663f5 100644 --- a/src/Avalonia.Controls/Utils/StringUtils.cs +++ b/src/Avalonia.Controls/Utils/StringUtils.cs @@ -190,6 +190,12 @@ namespace Avalonia.Controls.Utils { i = cursor; + // skip any whitespace before the word + while (i < cr && char.IsWhiteSpace(text[i])) + { + i++; + } + // skip to the end of the current word while (i < cr && !char.IsWhiteSpace(text[i])) { diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index d19bfebb00..5619311537 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -2,6 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Platform; +using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -15,5 +19,95 @@ namespace Avalonia.Controls.UnitTests BindingMode.TwoWay, TextBox.TextProperty.GetMetadata(typeof(TextBox)).DefaultBindingMode); } + + [Fact] + public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection() + { + AvaloniaLocator.CurrentMutable + .Bind() + .ToConstant(TestServices.MockThreadingInterface.ThreadingInterface); + + TextBox textBox = new TextBox + { + Text = "First Second Third Fourth", + CaretIndex = 5 + }; + + // (First| Second Third Fourth) + RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); + Assert.Equal(" Second Third Fourth", textBox.Text); + + // ( Second |Third Fourth) + textBox.CaretIndex = 8; + RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); + Assert.Equal(" Third Fourth", textBox.Text); + + // ( Thi|rd Fourth) + textBox.CaretIndex = 4; + RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); + Assert.Equal(" rd Fourth", textBox.Text); + + // ( rd F[ou]rth) + textBox.SelectionStart = 5; + textBox.SelectionEnd = 7; + + RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); + Assert.Equal(" rd Frth", textBox.Text); + + // ( |rd Frth) + textBox.CaretIndex = 1; + RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); + Assert.Equal("rd Frth", textBox.Text); + } + + [Fact] + public void Control_Delete_Should_Remove_The_Word_After_The_Caret_If_There_Is_No_Selection() + { + AvaloniaLocator.CurrentMutable + .Bind() + .ToConstant(TestServices.MockThreadingInterface.ThreadingInterface); + + TextBox textBox = new TextBox + { + Text = "First Second Third Fourth", + CaretIndex = 19 + }; + + // (First Second Third |Fourth) + RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); + Assert.Equal("First Second Third ", textBox.Text); + + // (First Second| Third ) + textBox.CaretIndex = 12; + RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); + Assert.Equal("First Second ", textBox.Text); + + // (First Sec|ond ) + textBox.CaretIndex = 9; + RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); + Assert.Equal("First Sec ", textBox.Text); + + // (Fi[rs]t Sec ) + textBox.SelectionStart = 2; + textBox.SelectionEnd = 4; + + RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); + Assert.Equal("Fit Sec ", textBox.Text); + + // (Fit Sec| ) + textBox.CaretIndex = 7; + RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); + Assert.Equal("Fit Sec", textBox.Text); + } + + private void RaiseKeyEvent(TextBox textBox, Key key, InputModifiers inputModifiers) + { + textBox.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Modifiers = inputModifiers, + Key = key + }); + } } }