mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
widget: delete whole words with key modifier
Delete entire words with key modifier, ie "ctrl + delete". Signed-off-by: Jack Mordaunt <jackmordaunt@gmail.com>
This commit is contained in:
committed by
Elias Naur
parent
d27d1a989e
commit
ef7b3e75f4
+57
-2
@@ -276,9 +276,17 @@ func (e *Editor) command(k key.Event) bool {
|
||||
case key.NameReturn, key.NameEnter:
|
||||
e.append("\n")
|
||||
case key.NameDeleteBackward:
|
||||
e.Delete(-1)
|
||||
if k.Modifiers == modSkip {
|
||||
e.deleteWord(-1)
|
||||
} else {
|
||||
e.Delete(-1)
|
||||
}
|
||||
case key.NameDeleteForward:
|
||||
e.Delete(1)
|
||||
if k.Modifiers == modSkip {
|
||||
e.deleteWord(1)
|
||||
} else {
|
||||
e.Delete(1)
|
||||
}
|
||||
case key.NameUpArrow:
|
||||
e.moveLines(-1)
|
||||
case key.NameDownArrow:
|
||||
@@ -824,6 +832,53 @@ func (e *Editor) moveWord(distance int) {
|
||||
}
|
||||
}
|
||||
|
||||
// deleteWord the next word(s) in the specified direction.
|
||||
// Unlike moveWord, deleteWord treats whitespace as a word itself.
|
||||
// Positive is forward, negative is backward.
|
||||
// Absolute values greater than one will delete that many words.
|
||||
func (e *Editor) deleteWord(distance int) {
|
||||
e.makeValid()
|
||||
// split the distance information into constituent parts to be
|
||||
// used independently.
|
||||
words, direction := distance, 1
|
||||
if distance < 0 {
|
||||
words, direction = distance*-1, -1
|
||||
}
|
||||
// atEnd if offset is at or beyond either side of the buffer.
|
||||
atEnd := func(offset int) bool {
|
||||
idx := e.rr.caret + offset*direction
|
||||
return idx <= 0 || idx >= e.rr.len()
|
||||
}
|
||||
// next returns the appropriate rune given the direction and offset.
|
||||
next := func(offset int) (r rune) {
|
||||
idx := e.rr.caret + offset*direction
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
} else if idx > e.rr.len() {
|
||||
idx = e.rr.len()
|
||||
}
|
||||
if direction < 0 {
|
||||
r, _ = e.rr.runeBefore(idx)
|
||||
} else {
|
||||
r, _ = e.rr.runeAt(idx)
|
||||
}
|
||||
return r
|
||||
}
|
||||
var runes = 1
|
||||
for ii := 0; ii < words; ii++ {
|
||||
if r := next(runes); unicode.IsSpace(r) {
|
||||
for r := next(runes); unicode.IsSpace(r) && !atEnd(runes); r = next(runes) {
|
||||
runes += 1
|
||||
}
|
||||
} else {
|
||||
for r := next(runes); !unicode.IsSpace(r) && !atEnd(runes); r = next(runes) {
|
||||
runes += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
e.Delete(runes * direction)
|
||||
}
|
||||
|
||||
func (e *Editor) scrollToCaret() {
|
||||
e.makeValid()
|
||||
l := e.lines[e.caret.line]
|
||||
|
||||
@@ -92,6 +92,7 @@ const (
|
||||
moveEnd
|
||||
moveCoord
|
||||
moveWord
|
||||
deleteWord
|
||||
moveLast // Mark end; never generated.
|
||||
)
|
||||
|
||||
@@ -143,6 +144,8 @@ func TestEditorCaretConsistency(t *testing.T) {
|
||||
e.moveCoord(image.Pt(int(x), int(y)))
|
||||
case moveWord:
|
||||
e.moveWord(int(distance))
|
||||
case deleteWord:
|
||||
e.deleteWord(int(distance))
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -203,6 +206,58 @@ func TestEditorMoveWord(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditorDeleteWord(t *testing.T) {
|
||||
type Test struct {
|
||||
Text string
|
||||
Start int
|
||||
Delete int
|
||||
|
||||
Want int
|
||||
Result string
|
||||
}
|
||||
tests := []Test{
|
||||
{"", 0, 0, 0, ""},
|
||||
{"", 0, -1, 0, ""},
|
||||
{"", 0, 1, 0, ""},
|
||||
{"hello", 0, -1, 0, "hello"},
|
||||
{"hello", 0, 1, 0, ""},
|
||||
{"hello world", 3, 1, 3, "hel world"},
|
||||
{"hello world", 3, -1, 0, "lo world"},
|
||||
{"hello world", 8, -1, 6, "hello rld"},
|
||||
{"hello world", 8, 1, 8, "hello wo"},
|
||||
{"hello world", 3, 1, 3, "hel world"},
|
||||
{"hello world", 3, 2, 3, "helworld"},
|
||||
{"hello world", 8, 1, 8, "hello "},
|
||||
{"hello world", 8, -1, 5, "hello world"},
|
||||
{"hello brave new world", 0, 3, 0, " new world"},
|
||||
}
|
||||
setup := func(t string) *Editor {
|
||||
e := new(Editor)
|
||||
gtx := layout.Context{
|
||||
Ops: new(op.Ops),
|
||||
Constraints: layout.Exact(image.Pt(100, 100)),
|
||||
}
|
||||
cache := text.NewCache(gofont.Collection())
|
||||
fontSize := unit.Px(10)
|
||||
font := text.Font{}
|
||||
e.SetText(t)
|
||||
e.Layout(gtx, cache, font, fontSize)
|
||||
return e
|
||||
}
|
||||
for ii, tt := range tests {
|
||||
e := setup(tt.Text)
|
||||
e.Move(tt.Start)
|
||||
e.deleteWord(tt.Delete)
|
||||
if e.rr.caret != tt.Want {
|
||||
t.Fatalf("[%d] deleteWord: bad caret position: got %d, want %d", ii, e.rr.caret, tt.Want)
|
||||
}
|
||||
if e.Text() != tt.Result {
|
||||
t.Fatalf("[%d] deleteWord: invalid result: got %q, want %q", ii, e.Text(), tt.Result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditorNoLayout(t *testing.T) {
|
||||
var e Editor
|
||||
e.SetText("hi!\n")
|
||||
|
||||
Reference in New Issue
Block a user