From 16cf0bd57e9b46dac9eec37f01035d2517fb0354 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sat, 19 Jul 2025 14:55:42 -0400 Subject: [PATCH 1/5] chore(messages): pre-allocate user message attachment slice --- internal/tui/components/chat/messages/messages.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index bff149c72807f637a58a6d14878b3ac209f05541..2d96bba6c9cb86de028d9521353d388c787844ef 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -193,17 +193,19 @@ func (m *messageCmp) renderAssistantMessage() string { return m.style().Render(joined) } -// renderUserMessage renders user messages with file attachments. -// Displays message content and any attached files with appropriate icons. +// renderUserMessage renders user messages with file attachments. It displays +// message content and any attached files with appropriate icons. func (m *messageCmp) renderUserMessage() string { t := styles.CurrentTheme() parts := []string{ m.toMarkdown(m.message.Content().String()), } + attachmentStyles := t.S().Text. MarginLeft(1). Background(t.BgSubtle) - attachments := []string{} + + attachments := make([]string, len(m.message.BinaryContent())) for _, attachment := range m.message.BinaryContent() { file := filepath.Base(attachment.Path) var filename string @@ -214,9 +216,11 @@ func (m *messageCmp) renderUserMessage() string { } attachments = append(attachments, attachmentStyles.Render(filename)) } + if len(attachments) > 0 { parts = append(parts, "", strings.Join(attachments, "")) } + joined := lipgloss.JoinVertical(lipgloss.Left, parts...) return m.style().Render(joined) } From a4ac72f06107002a4f3046d669a7a4569f0a4435 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sat, 19 Jul 2025 15:13:35 -0400 Subject: [PATCH 2/5] fix(messages): truncate attachment paths by rune, not by byte --- internal/tui/components/chat/messages/messages.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index 2d96bba6c9cb86de028d9521353d388c787844ef..f953b139968a4231dcc803c09f848363c7ed2160 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -209,11 +209,15 @@ func (m *messageCmp) renderUserMessage() string { for _, attachment := range m.message.BinaryContent() { file := filepath.Base(attachment.Path) var filename string - if len(file) > 10 { - filename = fmt.Sprintf(" %s %s... ", styles.DocumentIcon, file[0:7]) + runes := []rune(file) + + const truncatePathAt = 7 + if len(runes) > truncatePathAt { + filename = fmt.Sprintf(" %s %s... ", styles.DocumentIcon, string(runes[0:truncatePathAt])) } else { filename = fmt.Sprintf(" %s %s ", styles.DocumentIcon, file) } + attachments = append(attachments, attachmentStyles.Render(filename)) } From c37fe5317d05c0ca10c91efd592c8bb55205112a Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sat, 19 Jul 2025 15:16:03 -0400 Subject: [PATCH 3/5] chore(messages): no need to append to a pre-allocated slice --- internal/tui/components/chat/messages/messages.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index f953b139968a4231dcc803c09f848363c7ed2160..feb8c223e519b7aa428dcd11410db8f41bb3d894 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -206,7 +206,7 @@ func (m *messageCmp) renderUserMessage() string { Background(t.BgSubtle) attachments := make([]string, len(m.message.BinaryContent())) - for _, attachment := range m.message.BinaryContent() { + for i, attachment := range m.message.BinaryContent() { file := filepath.Base(attachment.Path) var filename string runes := []rune(file) @@ -218,7 +218,7 @@ func (m *messageCmp) renderUserMessage() string { filename = fmt.Sprintf(" %s %s ", styles.DocumentIcon, file) } - attachments = append(attachments, attachmentStyles.Render(filename)) + attachments[i] = attachmentStyles.Render(filename) } if len(attachments) > 0 { From 83d9cc342746eee815672345b5fe6a05cbaca1c6 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sun, 20 Jul 2025 00:03:30 -0400 Subject: [PATCH 4/5] fix(messages): properly measure width when rendering attachment paths --- .../tui/components/chat/messages/messages.go | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index feb8c223e519b7aa428dcd11410db8f41bb3d894..c7b69756d4e631ed4eb230cad4c5d001f06e4e38 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -207,18 +207,27 @@ func (m *messageCmp) renderUserMessage() string { attachments := make([]string, len(m.message.BinaryContent())) for i, attachment := range m.message.BinaryContent() { - file := filepath.Base(attachment.Path) - var filename string - runes := []rune(file) - - const truncatePathAt = 7 - if len(runes) > truncatePathAt { - filename = fmt.Sprintf(" %s %s... ", styles.DocumentIcon, string(runes[0:truncatePathAt])) + filename := filepath.Base(attachment.Path) + var displayFilename string + runes := []rune(filename) + + const truncateAtChars = 7 + const truncateSuffix = "..." + + // If the filename is too long, truncate it to fit within the maximum + // width we’ve chosen. + if lipgloss.Width(filename) > truncateAtChars+lipgloss.Width(truncateSuffix) { + displayFilename = fmt.Sprintf( + " %s %s%s ", + styles.DocumentIcon, + string(runes[0:truncateAtChars]), + truncateSuffix, + ) } else { - filename = fmt.Sprintf(" %s %s ", styles.DocumentIcon, file) + displayFilename = fmt.Sprintf(" %s %s ", styles.DocumentIcon, filename) } - attachments[i] = attachmentStyles.Render(filename) + attachments[i] = attachmentStyles.Render(displayFilename) } if len(attachments) > 0 { From 1999073112cdbaf57f0bea1ce6c3c34561aa354a Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Sun, 20 Jul 2025 00:12:32 -0400 Subject: [PATCH 5/5] chore(messages): greatly simplify filename truncation with ansi.Truncate --- .../tui/components/chat/messages/messages.go | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/internal/tui/components/chat/messages/messages.go b/internal/tui/components/chat/messages/messages.go index c7b69756d4e631ed4eb230cad4c5d001f06e4e38..e4def66708c3647e5aafd4562a8dc66323dd05c2 100644 --- a/internal/tui/components/chat/messages/messages.go +++ b/internal/tui/components/chat/messages/messages.go @@ -207,27 +207,13 @@ func (m *messageCmp) renderUserMessage() string { attachments := make([]string, len(m.message.BinaryContent())) for i, attachment := range m.message.BinaryContent() { + const maxFilenameWidth = 10 filename := filepath.Base(attachment.Path) - var displayFilename string - runes := []rune(filename) - - const truncateAtChars = 7 - const truncateSuffix = "..." - - // If the filename is too long, truncate it to fit within the maximum - // width we’ve chosen. - if lipgloss.Width(filename) > truncateAtChars+lipgloss.Width(truncateSuffix) { - displayFilename = fmt.Sprintf( - " %s %s%s ", - styles.DocumentIcon, - string(runes[0:truncateAtChars]), - truncateSuffix, - ) - } else { - displayFilename = fmt.Sprintf(" %s %s ", styles.DocumentIcon, filename) - } - - attachments[i] = attachmentStyles.Render(displayFilename) + attachments[i] = attachmentStyles.Render(fmt.Sprintf( + " %s %s ", + styles.DocumentIcon, + ansi.Truncate(filename, maxFilenameWidth, "..."), + )) } if len(attachments) > 0 {