From daab639b687a6c4fd372cdb434b671dd3eb84444 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 20 Feb 2024 17:48:54 -0800 Subject: [PATCH 1/4] add ctrl+backspace/delete bindings --- widget/entry.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/widget/entry.go b/widget/entry.go index 2b71e4dbc4..a335df38e5 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -880,6 +880,38 @@ func (e *Entry) typedKeyEnd(provider *RichText) { e.propertyLock.Unlock() } +// handler for Ctrl+[backspace/delete] - delete the word +// to the left or right of the cursor +func (e *Entry) deleteWord(right bool) { + provider := e.textProvider() + cursorRow, cursorCol := e.CursorRow, e.CursorColumn + + // start, end relative to text row + start, end := getTextWhitespaceRegion(provider.row(cursorRow), cursorCol, true) + if right { + start = cursorCol + } else { + end = cursorCol + } + if start == -1 || end == -1 { + return + } + deleteLen := end - start + + // convert start, end to absolute text position + b := provider.rowBoundary(cursorRow) + if b != nil { + start += b.begin + end += b.begin + } + + provider.deleteFromTo(start, end) + if !right { + e.CursorColumn = cursorCol - deleteLen + } + e.updateTextAndRefresh(provider.String(), false) +} + // TypedRune receives text input events when the Entry widget is focused. // // Implements: fyne.Focusable @@ -1176,6 +1208,11 @@ func (e *Entry) registerShortcut() { e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyLeft, Modifier: moveWordModifier | fyne.KeyModifierShift}, selectMoveWord) e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyRight, Modifier: moveWordModifier}, unselectMoveWord) e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyRight, Modifier: moveWordModifier | fyne.KeyModifierShift}, selectMoveWord) + + e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyBackspace, Modifier: moveWordModifier}, + func(fyne.Shortcut) { e.deleteWord(false) }) + e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyDelete, Modifier: moveWordModifier}, + func(fyne.Shortcut) { e.deleteWord(true) }) } func (e *Entry) requestFocus() { From 8a9a26bb59930468af37ef9e13a0e87ccd412078 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 20 Feb 2024 18:27:35 -0800 Subject: [PATCH 2/4] add unit test --- widget/entry.go | 3 +-- widget/entry_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/widget/entry.go b/widget/entry.go index a335df38e5..8fe150aa50 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -896,7 +896,6 @@ func (e *Entry) deleteWord(right bool) { if start == -1 || end == -1 { return } - deleteLen := end - start // convert start, end to absolute text position b := provider.rowBoundary(cursorRow) @@ -907,7 +906,7 @@ func (e *Entry) deleteWord(right bool) { provider.deleteFromTo(start, end) if !right { - e.CursorColumn = cursorCol - deleteLen + e.CursorColumn = cursorCol - (end - start) } e.updateTextAndRefresh(provider.String(), false) } diff --git a/widget/entry_test.go b/widget/entry_test.go index 9a00103ce5..304d63371f 100644 --- a/widget/entry_test.go +++ b/widget/entry_test.go @@ -772,6 +772,27 @@ func TestEntry_OnKeyDown_DeleteNewline(t *testing.T) { assert.Equal(t, "Hi", entry.Text) } +func TestEntry_DeleteWord(t *testing.T) { + entry := widget.NewMultiLineEntry() + entry.SetText("Hello world\nhere is a second line") + entry.CursorRow = 1 + entry.CursorColumn = 10 // right before "second" + modifier := fyne.KeyModifierControl + if runtime.GOOS == "darwin" { + modifier = fyne.KeyModifierAlt + } + // Ctrl+delete - delete word to right ("second") + entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyDelete}) + assert.Equal(t, "Hello world\nhere is a line", entry.Text) + assert.Equal(t, 10, entry.CursorColumn) + + entry.CursorColumn = 8 // right before "a" + // Ctrl+backspace - delete word to left ("is") + entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyBackspace}) + assert.Equal(t, "Hello world\nhere a line", entry.Text) + assert.Equal(t, 5, entry.CursorColumn) +} + func TestEntry_OnKeyDown_HomeEnd(t *testing.T) { entry := widget.NewEntry() entry.SetText("Hi") From da4d56c3cbe56216bfbffdb93b6d52c2c8012524 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 20 Feb 2024 18:35:34 -0800 Subject: [PATCH 3/4] add empty string case, move and rename unit test func --- widget/entry_test.go | 47 ++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/widget/entry_test.go b/widget/entry_test.go index 304d63371f..245a8a3e60 100644 --- a/widget/entry_test.go +++ b/widget/entry_test.go @@ -221,6 +221,32 @@ func TestEntry_Control_Word(t *testing.T) { assert.Equal(t, "", entry.SelectedText()) } +func TestEntry_Control_DeleteWord(t *testing.T) { + entry := widget.NewMultiLineEntry() + entry.SetText("Hello world\nhere is a second line") + entry.CursorRow = 1 + entry.CursorColumn = 10 // right before "second" + modifier := fyne.KeyModifierControl + if runtime.GOOS == "darwin" { + modifier = fyne.KeyModifierAlt + } + // Ctrl+delete - delete word to right ("second") + entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyDelete}) + assert.Equal(t, "Hello world\nhere is a line", entry.Text) + assert.Equal(t, 10, entry.CursorColumn) + + entry.CursorColumn = 8 // right before "a" + // Ctrl+backspace - delete word to left ("is") + entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyBackspace}) + assert.Equal(t, "Hello world\nhere a line", entry.Text) + assert.Equal(t, 5, entry.CursorColumn) + + // does nothing when nothing left to delete + entry.SetText("") + entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyBackspace}) + assert.Equal(t, "", entry.Text) +} + func TestEntry_CursorColumn_Wrap(t *testing.T) { entry := widget.NewMultiLineEntry() entry.SetText("a\nb") @@ -772,27 +798,6 @@ func TestEntry_OnKeyDown_DeleteNewline(t *testing.T) { assert.Equal(t, "Hi", entry.Text) } -func TestEntry_DeleteWord(t *testing.T) { - entry := widget.NewMultiLineEntry() - entry.SetText("Hello world\nhere is a second line") - entry.CursorRow = 1 - entry.CursorColumn = 10 // right before "second" - modifier := fyne.KeyModifierControl - if runtime.GOOS == "darwin" { - modifier = fyne.KeyModifierAlt - } - // Ctrl+delete - delete word to right ("second") - entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyDelete}) - assert.Equal(t, "Hello world\nhere is a line", entry.Text) - assert.Equal(t, 10, entry.CursorColumn) - - entry.CursorColumn = 8 // right before "a" - // Ctrl+backspace - delete word to left ("is") - entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyBackspace}) - assert.Equal(t, "Hello world\nhere a line", entry.Text) - assert.Equal(t, 5, entry.CursorColumn) -} - func TestEntry_OnKeyDown_HomeEnd(t *testing.T) { entry := widget.NewEntry() entry.SetText("Hi") From c313d1e0866d1d1ba472011fbf892e1e55a153ef Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 9 May 2024 20:05:16 -0700 Subject: [PATCH 4/4] don't crash when deleting backward with one space --- widget/entry.go | 5 ++++- widget/entry_test.go | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/widget/entry.go b/widget/entry.go index 8fe150aa50..31e6aa0f9a 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -2178,7 +2178,10 @@ func getTextWhitespaceRegion(row []rune, col int, expand bool) (int, int) { // IndexByte will find the position of the next unwanted character, this is to be the end // marker for the selection - end := strings.IndexByte(toks[endCheck:], c) + end := -1 + if endCheck != -1 { + end = strings.IndexByte(toks[endCheck:], c) + } if end == -1 { end = len(toks) // snap end to len(toks) if it results in -1 diff --git a/widget/entry_test.go b/widget/entry_test.go index 245a8a3e60..65d28ab15e 100644 --- a/widget/entry_test.go +++ b/widget/entry_test.go @@ -245,6 +245,12 @@ func TestEntry_Control_DeleteWord(t *testing.T) { entry.SetText("") entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyBackspace}) assert.Equal(t, "", entry.Text) + + // doesn't crash when trying to delete backward with one space + entry.SetText(" ") + entry.CursorRow = 0 + entry.CursorColumn = 1 + entry.TypedShortcut(&desktop.CustomShortcut{Modifier: modifier, KeyName: fyne.KeyBackspace}) } func TestEntry_CursorColumn_Wrap(t *testing.T) {