177 lines
3.1 KiB
Go
177 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
|
||
}
|
||
|
||
type inputModel struct {
|
||
ti textinput.Model
|
||
active bool
|
||
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) focus() {
|
||
i.active = true
|
||
i.ti.Focus()
|
||
}
|
||
|
||
func (i *inputModel) reset() {
|
||
i.active = false
|
||
i.ti.SetValue("")
|
||
i.ti.Blur()
|
||
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 {
|
||
return &inputResult{
|
||
query: true,
|
||
body: parsed.Body,
|
||
tags: parsed.FilterTags,
|
||
}
|
||
}
|
||
|
||
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) view(width int) string {
|
||
var b strings.Builder
|
||
b.WriteString(drawerBorderStyle.Render(strings.Repeat("─", width)))
|
||
b.WriteString("\n")
|
||
b.WriteString(i.ti.View())
|
||
b.WriteString("\n")
|
||
b.WriteString(drawerHintsStyle.Render("enter:submit esc:cancel ?:search -:todo @:event !:reminder"))
|
||
b.WriteString("\n")
|
||
b.WriteString(i.renderPreview(width))
|
||
return b.String()
|
||
}
|
||
|
||
func (i inputModel) renderPreview(width int) string {
|
||
if i.preview == nil {
|
||
return drawerPreviewStyle.Render("")
|
||
}
|
||
|
||
p := i.preview
|
||
|
||
if p.Query {
|
||
q := "?"
|
||
if p.Body != "" {
|
||
q += p.Body
|
||
}
|
||
for _, t := range p.FilterTags {
|
||
q += " #" + t
|
||
}
|
||
return drawerPreviewStyle.Render("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, tagStyle.Render("#"+t))
|
||
}
|
||
if p.Pin {
|
||
parts = append(parts, pinnedStyle.Render("•"))
|
||
}
|
||
if p.CardSuffix != nil {
|
||
parts = append(parts, affordanceStyle.Render(*p.CardSuffix))
|
||
}
|
||
|
||
line := strings.Join(parts, " ")
|
||
maxW := width - 4
|
||
if maxW > 0 && len(stripAnsi(line)) > maxW {
|
||
line = truncate(line, maxW)
|
||
}
|
||
|
||
return drawerPreviewStyle.Render(line)
|
||
}
|
||
|
||
func glyphForParsed(glyph string) string {
|
||
switch glyph {
|
||
case "todo":
|
||
return "○"
|
||
case "event":
|
||
return "◇"
|
||
case "reminder":
|
||
return "△"
|
||
default:
|
||
return "—"
|
||
}
|
||
}
|
||
|
||
func drawerLines() int {
|
||
return 3
|
||
}
|