okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
f7fcae1
main
1package main 2 3import ( 4"charm.land/bubbles/v2/textinput" 5 tea"charm.land/bubbletea/v2" 6"context" 7"example.com/charm-tui-worked/internal/display" 8"fmt" 9"strings" 10"time" 11) 12 13type debounceMsg struct { 14ticket ticket 15err error 16} 17type loadedMsg struct { 18generation uint64 19rows []string 20err error 21} 22 23func debounceCmd (t ticket ) tea.Cmd { 24return func () tea.Msg {return debounceMsg {ticket :t ,err :await (t .ctx ,150 * time .Millisecond )} } 25} 26func loadCmd (t ticket ,search searchFunc ) tea.Cmd { 27// The closure captures values and a service, never the live model. 28return func () tea.Msg { 29rows ,err := search (t .ctx ,t .query ) 30return loadedMsg {generation :t .generation ,rows :rows ,err :err } 31 } 32} 33 34type model struct { 35input textinput.Model 36state searchState 37search searchFunc 38initial ticket 39width ,height int 40} 41 42func newModel (ctx context.Context ,search searchFunc )* model { 43input := textinput .New () 44input .Prompt = "/ " 45input .Placeholder = "try slow, error, or queue" 46input .SetVirtualCursor (true ) 47input .SetWidth (55 ) 48m := & model {input :input ,state :searchState {root :ctx },search :search ,width :60 ,height :12 } 49m .initial = m .state .change ("" ) 50return m 51} 52 53func (m * model )Init () tea.Cmd { 54return tea .Batch (m .input .Focus (),debounceCmd (m .initial ),tea .RequestBackgroundColor ) 55} 56 57func (m * model )Update (msg tea.Msg ) (tea.Model , tea.Cmd ) { 58switch msg := msg .(type ) { 59case tea.WindowSizeMsg : 60m .width = min (72 ,max (0 ,msg .Width )) 61m .height = min (12 ,max (0 ,msg .Height )) 62m .input .SetWidth (max (1 ,m .width - 3 )) 63return m ,nil 64case tea.BackgroundColorMsg : 65m .input .SetStyles (textinput .DefaultStyles (msg .IsDark ())) 66return m ,nil 67case tea.KeyPressMsg : 68switch msg .String () { 69case "esc" ,"ctrl+c" : 70m .state .stop () 71return m ,tea .Quit 72case "ctrl+r" : 73return m ,debounceCmd (m .state .change (m .input .Value ())) 74 } 75case debounceMsg : 76// begin also rejects a duplicated current-generation debounce delivery. 77if ! m .state .begin (msg .ticket .generation ) { 78return m ,nil 79 } 80if msg .err != nil { 81m .state .complete (msg .ticket .generation ,nil ,msg .err ) 82return m ,nil 83 } 84return m ,loadCmd (msg .ticket ,m .search ) 85case loadedMsg : 86m .state .complete (msg .generation ,msg .rows ,msg .err ) 87return m ,nil 88 } 89before := m .input .Value () 90var childCmd tea.Cmd 91m .input ,childCmd = m .input .Update (msg ) 92if m .input .Value ()!= before { 93t := m .state .change (m .input .Value ()) 94return m ,tea .Batch (childCmd ,debounceCmd (t )) 95 } 96return m ,childCmd 97} 98 99func (m * model )View () tea.View { 100if m .width <= 0 || m .height <= 0 { 101return tea .NewView ("" ) 102 } 103if m .width < 20 || m .height < 6 { 104return tea .NewView (display .Line ("resize · esc quit" ,m .width )) 105 } 106status := "waiting for input" 107switch m .state .phase { 108case waiting : 109status = "waiting for typing to settle" 110case loading : 111status = "searching…" 112case ready : 113status = fmt .Sprintf ("%d matches" ,len (m .state .rows )) 114case failed : 115status = "search failed · ctrl+r retry" 116 } 117rows := make ([]string ,0 ,len (m .state .rows )) 118for _ ,row := range m .state .rows { 119rows = append (rows ,display .SingleLine (row )) 120 } 121if m .state .phase == ready && len (rows )== 0 { 122rows = append (rows ,"no matches · edit the query" ) 123 } 124if m .state .err != nil { 125rows = append (rows ,display .SingleLine (m .state .err .Error ())) 126 } 127// All four chrome rows are explicitly clipped to one line. 128content := []string { 129display .Line ("server search · simulated service" ,m .width ), 130display .Line (m .input .View (),m .width ), 131display .Line (status ,m .width ), 132display .FillRows (rows ,m .width ,display .BodyRows (m .height ,3 ,1 )), 133display .Line ("esc quit · ctrl+r retry" ,m .width ), 134 } 135return tea .NewView (strings .Join (content ,"\n" )) 136}