okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.pettui: pointer app model, shared styles, reused glamour renderers, own transcript pane, reuse rows across older pages, gogc 500b9f48f

main
3.9 KiB120 linesraw
1package tui
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
7
8	"github.com/charmbracelet/x/ansi"
9)
10
11// chipRows is the height of the strip of cards above the composer: the
12// thumbnail area and a border above and below it.
13const chipRows = thumbRows + 2
14
15// chip is one card of the strip: a title naming its marker, up to thumbRows
16// lines of content, and a caption.
17type chip struct {
18	title, caption string
19	content        []string
20}
21
22// chips are the attachments the composer still marks, in reading order.
23func (m ChatModel) chips() []chip {
24	var chips []chip
25	seen := make(map[string]bool)
26	for _, match := range anyMarker.FindAllString(m.TextArea.Value(), -1) {
27		if seen[match] {
28			continue
29		}
30		seen[match] = true
31		if found := imageMarker.FindStringSubmatch(match); found != nil {
32			n, _ := strconv.Atoi(found[1])
33			if image, ok := m.Images.byNumber[n]; ok {
34				chips = append(chips, m.imageChip(n, image))
35			}
36		} else if found := pasteMarker.FindStringSubmatch(match); found != nil {
37			n, _ := strconv.Atoi(found[1])
38			if text, ok := m.Pastes.byNumber[n]; ok {
39				chips = append(chips, m.pasteChip(n, text))
40			}
41		}
42	}
43	return chips
44}
45
46func (m ChatModel) imageChip(n int, image pastedImage) chip {
47	content := []string{"", DefaultStyles.Faint.Render("image")}
48	if image.thumb != nil {
49		content = placeholderRows(*image.thumb)
50	}
51	return chip{
52		title:   "image #" + strconv.Itoa(n),
53		caption: fmt.Sprintf("%d×%d", image.Width, image.Height),
54		content: content,
55	}
56}
57
58func (m ChatModel) pasteChip(n int, text string) chip {
59	lines := strings.Split(text, "\n")
60	caption := "+" + strconv.Itoa(len(lines)) + " lines"
61	if len(lines) <= collapseLines {
62		caption = strconv.Itoa(len([]rune(text))) + " chars"
63	}
64	content := make([]string, 0, thumbRows)
65	for _, line := range lines[:min(len(lines), thumbRows)] {
66		line = ansi.Truncate(strings.ReplaceAll(line, "\t", "  "), thumbCols, "")
67		// padded to the full width, so the card keeps the lines left-aligned
68		content = append(content, DefaultStyles.Faint.Render(line+strings.Repeat(" ", thumbCols-ansi.StringWidth(line))))
69	}
70	return chip{title: "paste #" + strconv.Itoa(n), caption: caption, content: content}
71}
72
73// chipStrip lays the chips out side by side within width, chipRows rows
74// high, or returns nil when there are none. Cards that do not fit are
75// counted at the end instead.
76func (m ChatModel) chipStrip(width int) []string {
77	chips := m.chips()
78	if len(chips) == 0 {
79		return nil
80	}
81	const cardWidth = thumbCols + 2
82	shown := min(len(chips), (width+1)/(cardWidth+1))
83	if shown < len(chips) && shown*(cardWidth+1)+len("+99") > width {
84		shown-- // room for the count of the rest
85	}
86	rows := make([]string, chipRows)
87	for i, c := range chips[:max(0, shown)] {
88		for r, line := range m.card(c) {
89			rows[r] += strings.Repeat(" ", min(i, 1)) + line
90		}
91	}
92	if hidden := len(chips) - max(0, shown); hidden > 0 {
93		rows[chipRows/2] += DefaultStyles.Faint.Render(" +" + strconv.Itoa(hidden))
94	}
95	return rows
96}
97
98// card draws c in a rounded border thumbCols+2 cells wide, its content
99// centred, its title on the top edge and its caption on the bottom one.
100func (m ChatModel) card(c chip) []string {
101	edge := func(left, label, right string) string {
102		label = ansi.Truncate(label, thumbCols-2, "…")
103		fill := thumbCols - ansi.StringWidth(label) - 2
104		return DefaultStyles.Decor.Render(left+strings.Repeat("─", fill-fill/2)+" ") +
105			DefaultStyles.Muted.Render(label) +
106			DefaultStyles.Decor.Render(" "+strings.Repeat("─", fill/2)+right)
107	}
108	side := DefaultStyles.Decor.Render("│")
109	rows := []string{edge("╭", c.title, "╮")}
110	top := (thumbRows - len(c.content)) / 2
111	for r := range thumbRows {
112		line := ""
113		if r >= top && r-top < len(c.content) {
114			line = c.content[r-top]
115		}
116		pad := thumbCols - ansi.StringWidth(line)
117		rows = append(rows, side+strings.Repeat(" ", pad/2)+line+strings.Repeat(" ", pad-pad/2)+side)
118	}
119	return append(rows, edge("╰", c.caption, "╯"))
120}