fix(editor): parse EDITOR command with arguments
Amolith
created 1 week ago
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
Change summary
internal/tui/components/chat/editor/editor.go | 37 ++++++++++++++++++++
1 file changed, 36 insertions(+), 1 deletion(-)
Detailed changes
@@ -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