e22e040688
CI / test (pull_request) Successful in 2m31s
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.
178 lines
3.1 KiB
Go
178 lines
3.1 KiB
Go
package tui
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"github.com/charmbracelet/bubbles/textinput"
|
||
tea "github.com/charmbracelet/bubbletea"
|
||
|
||
"github.com/lerko/nib/internal/db"
|
||
"github.com/lerko/nib/internal/parse"
|
||
)
|
||
|
||
type inputResult struct {
|
||
entity *db.Entity
|
||
query bool
|
||
body string
|
||
tags []string
|
||
dateFrom *string
|
||
dateTo *string
|
||
cardType *db.CardType
|
||
}
|
||
|
||
type inputModel struct {
|
||
ti textinput.Model
|
||
preview *parse.Result
|
||
}
|
||
|
||
func newInputModel() inputModel {
|
||
ti := textinput.New()
|
||
ti.Placeholder = "capture a thought…"
|
||
ti.Prompt = inputPromptStyle.Render("› ")
|
||
ti.CharLimit = 500
|
||
return inputModel{ti: ti}
|
||
}
|
||
|
||
func (i *inputModel) clearText() {
|
||
i.ti.SetValue("")
|
||
i.preview = nil
|
||
}
|
||
|
||
func (i inputModel) submit() *inputResult {
|
||
val := i.ti.Value()
|
||
if val == "" {
|
||
return nil
|
||
}
|
||
|
||
parsed, err := parse.Parse(val)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
if parsed.Query {
|
||
r := &inputResult{
|
||
query: true,
|
||
body: parsed.Body,
|
||
tags: parsed.FilterTags,
|
||
dateFrom: parsed.QueryDateFrom,
|
||
dateTo: parsed.QueryDateTo,
|
||
}
|
||
if parsed.QueryCardType != nil {
|
||
ct := db.CardType(*parsed.QueryCardType)
|
||
r.cardType = &ct
|
||
}
|
||
return r
|
||
}
|
||
|
||
e := &db.Entity{
|
||
Body: parsed.Body,
|
||
Title: parsed.Title,
|
||
Glyph: db.Glyph(parsed.Glyph),
|
||
Tags: parsed.Tags,
|
||
}
|
||
if parsed.TimeAnchor != nil {
|
||
e.TimeAnchor = parsed.TimeAnchor
|
||
}
|
||
if parsed.CardSuffix != nil {
|
||
ct := db.CardType(*parsed.CardSuffix)
|
||
e.CardType = &ct
|
||
}
|
||
if parsed.Pin {
|
||
e.Pinned = true
|
||
}
|
||
if parsed.Description != nil {
|
||
e.Description = parsed.Description
|
||
}
|
||
|
||
return &inputResult{entity: e}
|
||
}
|
||
|
||
func (i inputModel) updateKey(msg tea.KeyMsg) inputModel {
|
||
i.ti, _ = i.ti.Update(msg)
|
||
val := i.ti.Value()
|
||
if val != "" {
|
||
parsed, err := parse.Parse(val)
|
||
if err == nil {
|
||
i.preview = parsed
|
||
} else {
|
||
i.preview = nil
|
||
}
|
||
} else {
|
||
i.preview = nil
|
||
}
|
||
return i
|
||
}
|
||
|
||
func (i inputModel) viewBar(width int, focused bool) string {
|
||
tiView := i.ti.View()
|
||
if focused {
|
||
return tiView
|
||
}
|
||
val := i.ti.Value()
|
||
if val != "" {
|
||
return hintDescStyle.Render("› " + val)
|
||
}
|
||
return hintDescStyle.Render("› capture a thought…")
|
||
}
|
||
|
||
func (i inputModel) previewText() string {
|
||
if i.preview == nil {
|
||
return ""
|
||
}
|
||
|
||
p := i.preview
|
||
|
||
if p.Query {
|
||
q := "?"
|
||
if p.Body != "" {
|
||
q += p.Body
|
||
}
|
||
for _, t := range p.FilterTags {
|
||
q += " #" + t
|
||
}
|
||
if p.QueryDateFrom != nil {
|
||
q += " from:" + *p.QueryDateFrom
|
||
}
|
||
if p.QueryDateTo != nil {
|
||
q += " to:" + *p.QueryDateTo
|
||
}
|
||
if p.QueryCardType != nil {
|
||
q += " ^" + *p.QueryCardType
|
||
}
|
||
return "search: " + q
|
||
}
|
||
|
||
glyph := glyphForParsed(p.Glyph)
|
||
body := p.Body
|
||
if p.Title != nil {
|
||
body = *p.Title
|
||
}
|
||
|
||
var parts []string
|
||
parts = append(parts, glyph, body)
|
||
for _, t := range p.Tags {
|
||
parts = append(parts, "#"+t)
|
||
}
|
||
if p.Pin {
|
||
parts = append(parts, "•")
|
||
}
|
||
if p.CardSuffix != nil {
|
||
parts = append(parts, *p.CardSuffix)
|
||
}
|
||
|
||
return strings.Join(parts, " ")
|
||
}
|
||
|
||
func glyphForParsed(glyph string) string {
|
||
switch glyph {
|
||
case "todo":
|
||
return "○"
|
||
case "event":
|
||
return "◇"
|
||
case "reminder":
|
||
return "△"
|
||
default:
|
||
return "—"
|
||
}
|
||
}
|