From a1161fd0e89b22fff9939c2d8de4824fcbd1338a Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Fri, 4 Jul 2025 23:41:29 +0300 Subject: [PATCH] /editor: non-successful editor exits should keep the text in the textarea # Conflicts: # packages/tui/internal/tui/tui.go --- .../tui/internal/components/chat/editor.go | 5 ++++ packages/tui/internal/tui/tui.go | 30 +++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go index 9466d541..3213f44b 100644 --- a/packages/tui/internal/components/chat/editor.go +++ b/packages/tui/internal/components/chat/editor.go @@ -27,6 +27,7 @@ type EditorComponent interface { Content(width int) string Lines() int Value() string + SetValue(value string) Focused() bool Focus() (tea.Model, tea.Cmd) Blur() @@ -230,6 +231,10 @@ func (m *editorComponent) Value() string { return m.textarea.Value() } +func (m *editorComponent) SetValue(value string) { + m.textarea.SetValue(value) +} + func (m *editorComponent) Submit() (tea.Model, tea.Cmd) { value := strings.TrimSpace(m.Value()) if value == "" { diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go index 150a1b26..38a24891 100644 --- a/packages/tui/internal/tui/tui.go +++ b/packages/tui/internal/tui/tui.go @@ -32,6 +32,11 @@ import ( // InterruptDebounceTimeoutMsg is sent when the interrupt key debounce timeout expires type InterruptDebounceTimeoutMsg struct{} +// EditorRestoreMsg is sent when the external editor fails and we need to restore the original content +type EditorRestoreMsg struct { + Content string +} + // InterruptKeyState tracks the state of interrupt key presses for debouncing type InterruptKeyState int @@ -486,6 +491,9 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Reset interrupt key state after timeout a.interruptKeyState = InterruptKeyIdle a.editor.SetInterruptKeyInDebounce(false) + case EditorRestoreMsg: + // Restore the original content to the textarea when editor fails + a.editor.SetValue(msg.Content) case dialog.FindSelectedMsg: return a.openFile(msg.FilePath) } @@ -789,20 +797,32 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd) c.Stdout = os.Stdout c.Stderr = os.Stderr cmd = tea.ExecProcess(c, func(err error) tea.Msg { + defer os.Remove(tmpfile.Name()) + + // Check if the process exited with non-zero status if err != nil { - slog.Error("Failed to open editor", "error", err) - return nil + if exitError, ok := err.(*exec.ExitError); ok { + slog.Warn("Editor exited with non-zero status", "exitCode", exitError.ExitCode()) + // Restore original content when editor exits unsuccessfully + return EditorRestoreMsg{Content: value} + } else { + slog.Error("Failed to open editor", "error", err) + // Restore original content when editor fails to start + return EditorRestoreMsg{Content: value} + } } + content, err := os.ReadFile(tmpfile.Name()) if err != nil { slog.Error("Failed to read file", "error", err) - return nil + // Restore original content when file read fails + return EditorRestoreMsg{Content: value} } if len(content) == 0 { slog.Warn("Message is empty") - return nil + // Restore original content when message is empty + return EditorRestoreMsg{Content: value} } - os.Remove(tmpfile.Name()) return app.SendMsg{ Text: string(content), }