feat(textarea): get the word under the cursor (#814)

* feat(textarea): get the word under the cursor

* fix: lint

* Update textarea/textarea_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* test: fix

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker
2025-07-16 16:15:46 -03:00
committed by GitHub
co-authored by Copilot
parent a4c42b5791
commit 1e2ffbbcf5
3 changed files with 94 additions and 3 deletions
+3 -3
View File
@@ -518,9 +518,9 @@ func (m Model) canSelect(file string) bool {
}
// HighlightedPath returns the path of the currently highlighted file or directory.
func (M Model) HighlightedPath() string {
if len(M.files) == 0 || M.selected < 0 || M.selected >= len(M.files) {
func (m Model) HighlightedPath() string {
if len(m.files) == 0 || m.selected < 0 || m.selected >= len(m.files) {
return ""
}
return filepath.Join(M.CurrentDirectory, M.files[M.selected].Name())
return filepath.Join(m.CurrentDirectory, m.files[m.selected].Name())
}
+35
View File
@@ -722,6 +722,41 @@ func (m *Model) Reset() {
m.SetCursorColumn(0)
}
// Word returns the word at the cursor position.
// A word is delimited by spaces or line-breaks.
func (m *Model) Word() string {
line := m.value[m.row]
col := m.col - 1
if col < 0 {
return ""
}
// If cursor is beyond the line, return empty string
if col >= len(line) {
return ""
}
// If cursor is on a space, return empty string
if unicode.IsSpace(line[col]) {
return ""
}
// Find the start of the word by moving left
start := col
for start > 0 && !unicode.IsSpace(line[start-1]) {
start--
}
// Find the end of the word by moving right
end := col
for end < len(line) && !unicode.IsSpace(line[end]) {
end++
}
return string(line[start:end])
}
// san initializes or retrieves the rune sanitizer.
func (m *Model) san() runeutil.Sanitizer {
if m.rsan == nil {
+56
View File
@@ -1738,6 +1738,62 @@ func TestView(t *testing.T) {
}
}
func TestWord(t *testing.T) {
textarea := newTextArea()
textarea.SetHeight(3)
textarea.SetWidth(20)
textarea.CharLimit = 500
textarea, _ = textarea.Update(nil)
t.Run("regular input", func(t *testing.T) {
input := "Word1 Word2 Word3 Word4"
for _, k := range input {
textarea, _ = textarea.Update(keyPress(k))
textarea.View()
}
expect := "Word4"
if word := textarea.Word(); word != expect {
t.Fatalf("Expected last word to be '%s', got '%s'", expect, word)
}
})
t.Run("navigate", func(t *testing.T) {
for _, k := range []tea.KeyPressMsg{
{Code: tea.KeyLeft, Mod: tea.ModAlt, Text: "alt+left"},
{Code: tea.KeyLeft, Mod: tea.ModAlt, Text: "alt+left"},
{Code: tea.KeyRight, Text: "right"},
} {
textarea, _ = textarea.Update(k)
textarea.View()
}
expect := "Word3"
if word := textarea.Word(); word != expect {
t.Fatalf("Expected last word to be '%s', got '%s'", expect, word)
}
})
t.Run("delete", func(t *testing.T) {
for _, k := range []tea.KeyPressMsg{
{Code: tea.KeyEnd, Text: "end"},
{Code: tea.KeyBackspace, Mod: tea.ModAlt, Text: "alt+backspace"},
{Code: tea.KeyBackspace, Mod: tea.ModAlt, Text: "alt+backspace"},
{Code: tea.KeyBackspace, Text: "backspace"},
} {
textarea, _ = textarea.Update(k)
textarea.View()
}
expect := "Word2"
if word := textarea.Word(); word != expect {
t.Fatalf("Expected last word to be '%s', got '%s'", expect, word)
}
})
}
func newTextArea() Model {
textarea := New()