Files
nib-v1/internal/tui/autocomplete.go
lerko e22e040688
CI / test (pull_request) Successful in 2m31s
feat(tui): add tag autocomplete and query composition
Tag autocomplete shows suggestions when typing #partial in capture bar.
Tab/enter accepts, up/down navigates, esc dismisses.

Query composition extends ? search with date filters (@today, @week,
@month, <7d, >30d), card type filters (^snippet), all composable
with existing text and tag filters.
2026-05-21 12:12:07 -04:00

113 lines
2.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}