diff --git a/internal/tui/components/chat/editor/editor.go b/internal/tui/components/chat/editor/editor.go index de1a98b34595613594e83063cc12add4ba820c84..022c43f2ed15b7ab9d46de9ebd988b1159cd0517 100644 --- a/internal/tui/components/chat/editor/editor.go +++ b/internal/tui/components/chat/editor/editor.go @@ -30,6 +30,7 @@ import ( "github.com/charmbracelet/crush/internal/tui/components/dialogs/quit" "github.com/charmbracelet/crush/internal/tui/styles" "github.com/charmbracelet/crush/internal/tui/util" + "mvdan.cc/sh/v3/syntax" ) type Editor interface { @@ -112,7 +113,41 @@ func (m *editorCmp) openEditor(value string) tea.Cmd { if _, err := tmpfile.WriteString(value); err != nil { return util.ReportError(err) } - c := exec.CommandContext(context.TODO(), editor, tmpfile.Name()) + + // Parse the EDITOR parts to separate the executable from its arguments. + // This properly handles EDITORs with args like `zed --wait`. + parsed, err := syntax.NewParser().Parse(strings.NewReader(editor), "") + if err != nil { + return util.ReportError(fmt.Errorf("failed to parse editor command: %w", err)) + } + + var cmdName string + var cmdArgs []string + if len(parsed.Stmts) > 0 && parsed.Stmts[0].Cmd != nil { + if callExpr, ok := parsed.Stmts[0].Cmd.(*syntax.CallExpr); ok && len(callExpr.Args) > 0 { + for i, arg := range callExpr.Args { + var argStr string + for _, part := range arg.Parts { + if lit, ok := part.(*syntax.Lit); ok { + argStr += lit.Value + } + } + if i == 0 { + cmdName = argStr + } else { + cmdArgs = append(cmdArgs, argStr) + } + } + } + } + + // Fallback if parsing borked + if cmdName == "" { + cmdName = editor + } + + cmdArgs = append(cmdArgs, tmpfile.Name()) + c := exec.CommandContext(context.TODO(), cmdName, cmdArgs...) c.Stdin = os.Stdin c.Stdout = os.Stdout c.Stderr = os.Stderr