From a7e9e15a5d796250136daeaed832d082db5a512e Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 19 Nov 2025 12:57:45 -0700 Subject: [PATCH] fix(editor): parse EDITOR command with arguments Current Crush gives the following error when I try to open my `EDITOR` with `ctrl+o`: ``` Error reported error="exec: \"zed --wait\": executable file not found in $PATH" source=path/to/internal/tui/util/util.go:27 ``` This fix properly handles EDITOR values containing arguments, like `zed --wait`, by using the shell interpreter we already depend on to separate the executable from its args before invoking `exec.CommandContext`. Assisted-by: Claude Sonnet 4.5 via Crush --- internal/tui/components/chat/editor/editor.go | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) 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