@@ -1,6 +1,9 @@
package message
-import "strings"
+import (
+ "slices"
+ "strings"
+)
type Attachment struct {
FilePath string
@@ -11,3 +14,10 @@ type Attachment struct {
func (a Attachment) IsText() bool { return strings.HasPrefix(a.MimeType, "text/") }
func (a Attachment) IsImage() bool { return strings.HasPrefix(a.MimeType, "image/") }
+
+// ContainsTextAttachment returns true if any of the attachments is a text attachments.
+func ContainsTextAttachment(attachments []Attachment) bool {
+ return slices.ContainsFunc(attachments, func(a Attachment) bool {
+ return a.IsText()
+ })
+}
@@ -1,13 +1,14 @@
package editor
import (
- "errors"
"fmt"
"math/rand"
"net/http"
"os"
"path/filepath"
+ "regexp"
"slices"
+ "strconv"
"strings"
"unicode"
@@ -146,7 +147,7 @@ func (m *editorCmp) send() tea.Cmd {
attachments := m.attachments
- if value == "" {
+ if value == "" && !message.ContainsTextAttachment(attachments) {
return nil
}
@@ -233,13 +234,31 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
m.textarea.SetValue(msg.Text)
m.textarea.MoveToEnd()
case tea.PasteMsg:
- content, path, err := pasteToFile(msg)
- if errors.Is(err, errNotAFile) {
- m.textarea, cmd = m.textarea.Update(msg)
- return m, cmd
+ // If pasted text has more than 2 newlines, treat it as a file attachment.
+ if strings.Count(msg.Content, "\n") > 2 {
+ content := []byte(msg.Content)
+ if len(content) > maxAttachmentSize {
+ return m, util.ReportWarn("Paste is too big (>5mb)")
+ }
+ name := fmt.Sprintf("paste_%d.txt", m.pasteIdx())
+ mimeType := mimeOf(content)
+ attachment := message.Attachment{
+ FileName: name,
+ FilePath: name,
+ MimeType: mimeType,
+ Content: content,
+ }
+ return m, util.CmdHandler(filepicker.FilePickedMsg{
+ Attachment: attachment,
+ })
}
+
+ // Try to parse as a file path.
+ content, path, err := filepathToFile(msg.Content)
if err != nil {
- return m, util.ReportError(err)
+ // Not a file path, just update the textarea normally.
+ m.textarea, cmd = m.textarea.Update(msg)
+ return m, cmd
}
if len(content) > maxAttachmentSize {
@@ -256,7 +275,6 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
if !attachment.IsText() && !attachment.IsImage() {
return m, util.ReportWarn("Invalid file content type: " + mimeType)
}
- m.textarea.InsertString(attachment.FileName)
return m, util.CmdHandler(filepicker.FilePickedMsg{
Attachment: attachment,
})
@@ -627,33 +645,21 @@ func New(app *app.App) Editor {
var maxAttachmentSize = 5 * 1024 * 1024 // 5MB
-var errNotAFile = errors.New("not a file")
-
-func pasteToFile(msg tea.PasteMsg) ([]byte, string, error) {
- content, path, err := filepathToFile(msg.Content)
- if err == nil {
- return content, path, err
- }
-
- if strings.Count(msg.Content, "\n") > 2 {
- return contentToFile([]byte(msg.Content))
- }
-
- return nil, "", errNotAFile
-}
+var pasteRE = regexp.MustCompile(`paste_(\d+).txt`)
-func contentToFile(content []byte) ([]byte, string, error) {
- f, err := os.CreateTemp("", "paste_*.txt")
- if err != nil {
- return nil, "", err
- }
- if _, err := f.Write(content); err != nil {
- return nil, "", err
- }
- if err := f.Close(); err != nil {
- return nil, "", err
+func (m *editorCmp) pasteIdx() int {
+ result := 0
+ for _, at := range m.attachments {
+ found := pasteRE.FindStringSubmatch(at.FileName)
+ if len(found) == 0 {
+ continue
+ }
+ idx, err := strconv.Atoi(found[1])
+ if err == nil {
+ result = max(result, idx)
+ }
}
- return content, f.Name(), nil
+ return result + 1
}
func filepathToFile(name string) ([]byte, string, error) {
@@ -223,8 +223,10 @@ func (m *messageCmp) renderAssistantMessage() string {
// 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()),
+ var parts []string
+
+ if s := m.message.Content().String(); s != "" {
+ parts = append(parts, m.toMarkdown(s))
}
attachmentStyle := t.S().Base.
@@ -256,7 +258,7 @@ func (m *messageCmp) renderUserMessage() string {
}
if len(attachments) > 0 {
- parts = append(parts, "", strings.Join(attachments, ""))
+ parts = append(parts, strings.Join(attachments, ""))
}
joined := lipgloss.JoinVertical(lipgloss.Left, parts...)