1package ui23import (4 "strconv"5 "strings"67 "charm.land/lipgloss/v2"8 "github.com/charmbracelet/x/ansi"910 "src.rest/cat/012/internal/cowork"11 "src.rest/cat/012/internal/sheet"12 "src.rest/cat/012/internal/ui/theme"13)1415// Drawing agents' suggestions: each cell a suggestion waiting would set16// has a ◇ in its top-left corner (theme.Suggested), the context line17// says what the agent would put there and why when the pointer is on18// one, and the status line counts those waiting. Agents themselves are19// drawn as the others are (sharedraw.go), their initial a ◆.2021// agentFrame is what a frame draws of suggestions, worked out once as22// it starts: the cells of the sheet shown that suggestions waiting set,23// and how many suggestions wait.24type agentFrame struct {25 cells map[sheet.Addr]suggestedCell26 waiting int27}2829// suggestedCell is a cell of a suggestion.30type suggestedCell struct {31 s *cowork.Suggestion32 i int33}3435// frameAgents works out the suggestions for the frame about to be36// drawn.37func (m *Model) frameAgents() {38 f := &m.agents.frame39 clear(f.cells)40 f.waiting = 041 if m.share.seat == nil {42 return43 }44 pending := m.board().Pending()45 f.waiting = len(pending)46 name := m.sheet.Name()47 for _, s := range pending {48 for i, c := range s.Cells {49 if c.Sheet != name || !s.Whole() && s.States[i] != cowork.CellPending {50 continue51 }52 if f.cells == nil {53 f.cells = map[sheet.Addr]suggestedCell{}54 }55 f.cells[c.At] = suggestedCell{s, i}56 }57 }58}5960// suggestedMark draws the ◇ of a cell a suggestion would set over the61// first column of its text.62func (m *Model) suggestedMark(text string, a sheet.Addr, base lipgloss.Style, colored bool) string {63 if _, ok := m.agents.frame.cells[a]; !ok || m.sheet.ColWidth(a.Col) < 2 {64 return text65 }66 mark := m.th.Suggested67 if colored {68 mark = base.Bold(true)69 }70 return mark.Render("◇") + ansi.Cut(text, 1, m.sheet.ColWidth(a.Col))71}7273// agentLine is the context line's word on a cell a suggestion would74// set: whose, what it would put there, what's there now and why.75func (m *Model) agentLine() string {76 sc, ok := m.agents.frame.cells[m.cur]77 if !ok {78 return ""79 }80 s, c := sc.s, sc.s.Cells[sc.i]81 what := shownInput(c.Now)82 if c.Look && c.Was == c.Now {83 what = "a new format"84 }85 line := m.th.Suggested.Render("◇ ") + m.th.Peer[s.Color%theme.Peers].Render(" ◆ "+s.Agent+" ") +86 m.th.Muted.Render(" suggests ") + m.th.Key.Render(what) + m.th.Muted.Render(", now "+shownInput(c.Was))87 hint := " " + m.th.KeyHints(keyLabel(keyFor("agent.review")), "review")88 if s.Message != "" {89 // The message gives way to the key, cut to what's left.90 room := m.width - ansi.StringWidth(line) - ansi.StringWidth(hint) - 291 if room > 3 {92 line += m.th.Muted.Render(ansi.Truncate(": "+s.Message, room, "…"))93 }94 }95 return line + hint96}9798// keyFor is the first key bound to a command.99func keyFor(id string) string {100 if keys := keysFor(id, false); len(keys) > 0 {101 return keys[0]102 }103 return ""104}105106// shownInput is an input as live mode shows it: blank for none.107func shownInput(in string) string {108 if in == "" {109 return "blank"110 }111 return in112}113114// agentsStatus is the status line's count of suggestions waiting.115func (m *Model) agentsStatus() string {116 n := m.agents.frame.waiting117 if n == 0 {118 return ""119 }120 what := strconv.Itoa(n) + " suggestions"121 if n == 1 {122 what = "1 suggestion"123 }124 return " " + m.th.Suggested.Render("◇ "+what)125}126127// peerInitial is what stands for a participant on a row header: an128// agent's ◆, a person's initial.129func peerInitial(name string, agent bool) string {130 if agent {131 return "◆"132 }133 return initial(name)134}135136// peerLabel is a participant's name as the status line shows it, an137// agent's after its ◆.138func peerLabel(name string, agent bool) string {139 name = ansi.Truncate(name, 10, "…")140 if agent {141 return "◆ " + name142 }143 return strings.TrimSpace(name)144}