diff --git a/filepicker/filepicker.go b/filepicker/filepicker.go index 90e79d6..5a5e5b6 100644 --- a/filepicker/filepicker.go +++ b/filepicker/filepicker.go @@ -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()) } diff --git a/textarea/textarea.go b/textarea/textarea.go index f68db02..4c44935 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -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 { diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index dc778fb..91a2840 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -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()