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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 40 additions & 1 deletion widget/entry.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions widget/entry_test.go
Expand Up @@ -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")
Expand Down