@@ -1,15 +1,14 @@
11package editor
22
33import (
4− "context"
5− "errors"
64 "fmt"
75 "math/rand"
86 "net/http"
97 "os"
108 "path/filepath"
11− "runtime"
9+ "regexp"
1210 "slices"
11+ "strconv"
1312 "strings"
1413 "unicode"
1514
@@ -32,6 +31,7 @@ import (
3231 "github.com/charmbracelet/crush/internal/tui/styles"
3332 "github.com/charmbracelet/crush/internal/tui/util"
3433 "github.com/charmbracelet/x/ansi"
34+ "github.com/charmbracelet/x/editor"
3535)
3636
3737type Editor interface {
@@ -94,16 +94,6 @@ type OpenEditorMsg struct {
9494}
9595
9696func (m *editorCmp) openEditor(value string) tea.Cmd {
97− editor := os.Getenv("EDITOR")
98− if editor == "" {
99− // Use platform-appropriate default editor
100− if runtime.GOOS == "windows" {
101− editor = "notepad"
102− } else {
103− editor = "nvim"
104− }
105− }
106−
10797 tmpfile, err := os.CreateTemp("", "msg_*.md")
10898 if err != nil {
10999 return util.ReportError(err)
@@ -112,8 +102,18 @@ func (m *editorCmp) openEditor(value string) tea.Cmd {
112102 if _, err := tmpfile.WriteString(value); err != nil {
113103 return util.ReportError(err)
114104 }
115− cmdStr := editor + " " + tmpfile.Name()
116− return util.ExecShell(context.TODO(), cmdStr, func(err error) tea.Msg {
105+ cmd, err := editor.Command(
106+ "crush",
107+ tmpfile.Name(),
108+ editor.AtPosition(
109+ m.textarea.Line()+1,
110+ m.textarea.Column()+1,
111+ ),
112+ )
113+ if err != nil {
114+ return util.ReportError(err)
115+ }
116+ return tea.ExecProcess(cmd, func(err error) tea.Msg {
117117 if err != nil {
118118 return util.ReportError(err)
119119 }
@@ -147,7 +147,7 @@ func (m *editorCmp) send() tea.Cmd {
147147
148148 attachments := m.attachments
149149
150− if value == "" {
150+ if value == "" && !message.ContainsTextAttachment(attachments) {
151151 return nil
152152 }
153153
@@ -234,13 +234,31 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
234234 m.textarea.SetValue(msg.Text)
235235 m.textarea.MoveToEnd()
236236 case tea.PasteMsg:
237− content, path, err := pasteToFile(msg)
238− if errors.Is(err, errNotAFile) {
239− m.textarea, cmd = m.textarea.Update(msg)
240− return m, cmd
237+ // If pasted text has more than 2 newlines, treat it as a file attachment.
238+ if strings.Count(msg.Content, "\n") > 2 {
239+ content := []byte(msg.Content)
240+ if len(content) > maxAttachmentSize {
241+ return m, util.ReportWarn("Paste is too big (>5mb)")
242+ }
243+ name := fmt.Sprintf("paste_%d.txt", m.pasteIdx())
244+ mimeType := mimeOf(content)
245+ attachment := message.Attachment{
246+ FileName: name,
247+ FilePath: name,
248+ MimeType: mimeType,
249+ Content: content,
250+ }
251+ return m, util.CmdHandler(filepicker.FilePickedMsg{
252+ Attachment: attachment,
253+ })
241254 }
255+
256+ // Try to parse as a file path.
257+ content, path, err := filepathToFile(msg.Content)
242258 if err != nil {
243− return m, util.ReportError(err)
259+ // Not a file path, just update the textarea normally.
260+ m.textarea, cmd = m.textarea.Update(msg)
261+ return m, cmd
244262 }
245263
246264 if len(content) > maxAttachmentSize {
@@ -257,7 +275,6 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
257275 if !attachment.IsText() && !attachment.IsImage() {
258276 return m, util.ReportWarn("Invalid file content type: " + mimeType)
259277 }
260− m.textarea.InsertString(attachment.FileName)
261278 return m, util.CmdHandler(filepicker.FilePickedMsg{
262279 Attachment: attachment,
263280 })
@@ -628,33 +645,21 @@ func New(app *app.App) Editor {
628645
629646var maxAttachmentSize = 5 * 1024 * 1024 // 5MB
630647
631−var errNotAFile = errors.New("not a file")
632−
633−func pasteToFile(msg tea.PasteMsg) ([]byte, string, error) {
634− content, path, err := filepathToFile(msg.Content)
635− if err == nil {
636− return content, path, err
637− }
638−
639− if strings.Count(msg.Content, "\n") > 2 {
640− return contentToFile([]byte(msg.Content))
641− }
642−
643− return nil, "", errNotAFile
644−}
648+var pasteRE = regexp.MustCompile(`paste_(\d+).txt`)
645649
646−func contentToFile(content []byte) ([]byte, string, error) {
647− f, err := os.CreateTemp("", "paste_*.txt")
648− if err != nil {
649− return nil, "", err
650− }
651− if _, err := f.Write(content); err != nil {
652− return nil, "", err
653− }
654− if err := f.Close(); err != nil {
655− return nil, "", err
650+func (m *editorCmp) pasteIdx() int {
651+ result := 0
652+ for _, at := range m.attachments {
653+ found := pasteRE.FindStringSubmatch(at.FileName)
654+ if len(found) == 0 {
655+ continue
656+ }
657+ idx, err := strconv.Atoi(found[1])
658+ if err == nil {
659+ result = max(result, idx)
660+ }
656661 }
657− return content, f.Name(), nil
662+ return result + 1
658663}
659664
660665func filepathToFile(name string) ([]byte, string, error) {