package tui import ( "strings" "github.com/charmbracelet/lipgloss" "github.com/lerko/nib/internal/db" ) const maxSuggestions = 5 type autocompleteModel struct { suggestions []string cursor int active bool prefix string tokenStart int tokenEnd int } func (a *autocompleteModel) moveUp() { if a.cursor > 0 { a.cursor-- } } func (a *autocompleteModel) moveDown() { if a.cursor < len(a.suggestions)-1 { a.cursor++ } } func (a autocompleteModel) selected() string { if len(a.suggestions) == 0 || a.cursor >= len(a.suggestions) { return "" } return a.suggestions[a.cursor] } func (a autocompleteModel) visibleCount() int { if len(a.suggestions) > maxSuggestions { return maxSuggestions } return len(a.suggestions) } func (a autocompleteModel) view(width int) string { if !a.active || len(a.suggestions) == 0 { return "" } var b strings.Builder n := a.visibleCount() for i := 0; i < n; i++ { tag := "#" + a.suggestions[i] if i == a.cursor { b.WriteString(acSelectedStyle.Render(" › " + tag)) } else { b.WriteString(acItemStyle.Render(" " + tag)) } if i < n-1 { b.WriteString("\n") } } if len(a.suggestions) > maxSuggestions { b.WriteString("\n") b.WriteString(acItemStyle.Render(" …")) } box := lipgloss.NewStyle(). Width(min(30, width)). Render(b.String()) return box } func tagTokenAtCursor(val string, cursorPos int) (tokenStart, tokenEnd int, prefix string, ok bool) { if cursorPos > len(val) { cursorPos = len(val) } start := cursorPos for start > 0 && val[start-1] != ' ' { start-- } if start >= len(val) || val[start] != '#' { return 0, 0, "", false } end := cursorPos for end < len(val) && val[end] != ' ' { end++ } prefix = strings.ToLower(val[start+1 : cursorPos]) return start, end, prefix, true } func filterTagSuggestions(tags []db.TagCount, prefix string) []string { if prefix == "" { return nil } prefix = strings.ToLower(prefix) var result []string for _, tc := range tags { lower := strings.ToLower(tc.Tag) if strings.HasPrefix(lower, prefix) && lower != prefix { result = append(result, tc.Tag) } } return result }