diff --git a/widget/entry.go b/widget/entry.go index 3cfe999936..5dd06268dc 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -877,6 +877,37 @@ 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 + } + + // 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 - (end - start) + } + e.updateTextAndRefresh(provider.String(), false) +} + func (e *Entry) typedKeyTab() { if dd, ok := fyne.CurrentApp().Driver().(desktop.Driver); ok { if dd.CurrentKeyModifiers()&fyne.KeyModifierShift != 0 { @@ -1214,6 +1245,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() { @@ -2189,7 +2225,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 588ee00bfe..457ee30bb0 100644 --- a/widget/entry_test.go +++ b/widget/entry_test.go @@ -222,6 +222,38 @@ 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) + + // 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) { entry := widget.NewMultiLineEntry() entry.SetText("a\nb")