Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Ctrl+[backspace/delete] to delete the word to the left or right of the cursor #4662

Merged
merged 5 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion widget/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,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)
}

// TypedRune receives text input events when the Entry widget is focused.
//
// Implements: fyne.Focusable
Expand Down Expand Up @@ -1176,6 +1207,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() {
Expand Down Expand Up @@ -2142,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
Expand Down
32 changes: 32 additions & 0 deletions widget/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,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")
Expand Down