user.go

  1package chat
  2
  3import (
  4	"fmt"
  5	"path/filepath"
  6	"strings"
  7
  8	"charm.land/lipgloss/v2"
  9	"github.com/charmbracelet/crush/internal/message"
 10	"github.com/charmbracelet/crush/internal/ui/common"
 11	"github.com/charmbracelet/crush/internal/ui/styles"
 12	"github.com/charmbracelet/x/ansi"
 13)
 14
 15type UserMessageItem struct {
 16	id          string
 17	content     string
 18	attachments []message.BinaryContent
 19	sty         *styles.Styles
 20}
 21
 22func NewUserMessage(id, content string, attachments []message.BinaryContent, sty *styles.Styles) *UserMessageItem {
 23	return &UserMessageItem{
 24		id:          id,
 25		content:     content,
 26		attachments: attachments,
 27		sty:         sty,
 28	}
 29}
 30
 31// ID implements Identifiable.
 32func (m *UserMessageItem) ID() string {
 33	return m.id
 34}
 35
 36// FocusStyle returns the focus style.
 37func (m *UserMessageItem) FocusStyle() lipgloss.Style {
 38	return m.sty.Chat.Message.UserFocused
 39}
 40
 41// BlurStyle returns the blur style.
 42func (m *UserMessageItem) BlurStyle() lipgloss.Style {
 43	return m.sty.Chat.Message.UserBlurred
 44}
 45
 46// HighlightStyle returns the highlight style.
 47func (m *UserMessageItem) HighlightStyle() lipgloss.Style {
 48	return m.sty.TextSelection
 49}
 50
 51// Render implements MessageItem.
 52func (m *UserMessageItem) Render(width int) string {
 53	cappedWidth := min(width, maxTextWidth)
 54	renderer := common.MarkdownRenderer(m.sty, cappedWidth)
 55	result, err := renderer.Render(m.content)
 56	var rendered string
 57	if err != nil {
 58		rendered = m.content
 59	} else {
 60		rendered = strings.TrimSuffix(result, "\n")
 61	}
 62
 63	if len(m.attachments) > 0 {
 64		attachmentsStr := m.renderAttachments(cappedWidth)
 65		rendered = strings.Join([]string{rendered, "", attachmentsStr}, "\n")
 66	}
 67	return rendered
 68}
 69
 70// renderAttachments renders attachments with wrapping if they exceed the width.
 71func (m *UserMessageItem) renderAttachments(width int) string {
 72	const maxFilenameWidth = 10
 73
 74	attachments := make([]string, len(m.attachments))
 75	for i, attachment := range m.attachments {
 76		filename := filepath.Base(attachment.Path)
 77		attachments[i] = m.sty.Chat.Message.Attachment.Render(fmt.Sprintf(
 78			" %s %s ",
 79			styles.DocumentIcon,
 80			ansi.Truncate(filename, maxFilenameWidth, "..."),
 81		))
 82	}
 83
 84	// Wrap attachments into lines that fit within the width.
 85	var lines []string
 86	var currentLine []string
 87	currentWidth := 0
 88
 89	for _, att := range attachments {
 90		attWidth := lipgloss.Width(att)
 91		sepWidth := 1
 92		if len(currentLine) == 0 {
 93			sepWidth = 0
 94		}
 95
 96		if currentWidth+sepWidth+attWidth > width && len(currentLine) > 0 {
 97			lines = append(lines, strings.Join(currentLine, " "))
 98			currentLine = []string{att}
 99			currentWidth = attWidth
100		} else {
101			currentLine = append(currentLine, att)
102			currentWidth += sepWidth + attWidth
103		}
104	}
105
106	if len(currentLine) > 0 {
107		lines = append(lines, strings.Join(currentLine, " "))
108	}
109
110	return strings.Join(lines, "\n")
111}