feat: paste as file (#1800)

Carlos Alexandro Becker created

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

Change summary

internal/ui/model/ui.go | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)

Detailed changes

internal/ui/model/ui.go 🔗

@@ -9,7 +9,9 @@ import (
 	"net/http"
 	"os"
 	"path/filepath"
+	"regexp"
 	"slices"
+	"strconv"
 	"strings"
 
 	"charm.land/bubbles/v2/help"
@@ -1917,6 +1919,25 @@ func (m *UI) handlePasteMsg(msg tea.PasteMsg) tea.Cmd {
 		return nil
 	}
 
+	// If pasted text has more than 2 newlines, treat it as a file attachment.
+	if strings.Count(msg.Content, "\n") > 2 {
+		return func() tea.Msg {
+			content := []byte(msg.Content)
+			if int64(len(content)) > maxAttachmentSize {
+				return uiutil.ReportWarn("Paste is too big (>5mb)")
+			}
+			name := fmt.Sprintf("paste_%d.txt", m.pasteIdx())
+			mimeBufferSize := min(512, len(content))
+			mimeType := http.DetectContentType(content[:mimeBufferSize])
+			return message.Attachment{
+				FileName: name,
+				FilePath: name,
+				MimeType: mimeType,
+				Content:  content,
+			}
+		}
+	}
+
 	var cmd tea.Cmd
 	path := strings.ReplaceAll(msg.Content, "\\ ", " ")
 	// Try to get an image.
@@ -1966,6 +1987,23 @@ func (m *UI) handlePasteMsg(msg tea.PasteMsg) tea.Cmd {
 	}
 }
 
+var pasteRE = regexp.MustCompile(`paste_(\d+).txt`)
+
+func (m *UI) pasteIdx() int {
+	result := 0
+	for _, at := range m.attachments.List() {
+		found := pasteRE.FindStringSubmatch(at.FileName)
+		if len(found) == 0 {
+			continue
+		}
+		idx, err := strconv.Atoi(found[1])
+		if err == nil {
+			result = max(result, idx)
+		}
+	}
+	return result + 1
+}
+
 // renderLogo renders the Crush logo with the given styles and dimensions.
 func renderLogo(t *styles.Styles, compact bool, width int) string {
 	return logo.Render(version.Version, compact, logo.Opts{