summaryrefslogtreecommitdiff
path: root/internal/editor
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-07 17:54:42 +0300
committerPaul Buetow <paul@buetow.org>2025-09-07 17:54:42 +0300
commit77e41a1018715fa5ac4e6156354710b3b224b4fc (patch)
tree23d847008c34a4c8b1329151fd33ac107fdefe76 /internal/editor
parenta240e1e106d3b8028bbb3667b44cf3085875e405 (diff)
feat: add Custom prompt action (p) with editor integration; shared editor helper in internal/editor; hexai CLI opens editor when no args
Diffstat (limited to 'internal/editor')
-rw-r--r--internal/editor/editor.go70
1 files changed, 70 insertions, 0 deletions
diff --git a/internal/editor/editor.go b/internal/editor/editor.go
new file mode 100644
index 0000000..44aa1d4
--- /dev/null
+++ b/internal/editor/editor.go
@@ -0,0 +1,70 @@
+package editor
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+// Resolve returns the editor command from HEXAI_EDITOR or EDITOR.
+func Resolve() (string, error) {
+ ed := strings.TrimSpace(os.Getenv("HEXAI_EDITOR"))
+ if ed == "" {
+ ed = strings.TrimSpace(os.Getenv("EDITOR"))
+ }
+ if ed == "" {
+ return "", errors.New("no editor configured (set HEXAI_EDITOR or EDITOR)")
+ }
+ return ed, nil
+}
+
+// RunEditor is the seam that invokes the editor on the given file path.
+// Override in tests to avoid launching a real editor.
+var RunEditor = func(editor, path string) error {
+ cmd := exec.Command(editor, path)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+// OpenTempAndEdit creates a temporary .md file, writes initial content if provided,
+// opens it in the resolved editor, then reads the final content and removes the file.
+// Returns the trimmed content.
+func OpenTempAndEdit(initial []byte) (string, error) {
+ ed, err := Resolve()
+ if err != nil {
+ return "", err
+ }
+ // Create temp file under system temp dir; ensure .md suffix
+ dir := os.TempDir()
+ f, err := os.CreateTemp(dir, "hexai-*.md")
+ if err != nil {
+ return "", err
+ }
+ path := f.Name()
+ defer func() { _ = os.Remove(path) }()
+ if len(initial) > 0 {
+ if _, err := f.Write(initial); err != nil {
+ _ = f.Close()
+ return "", err
+ }
+ }
+ if err := f.Sync(); err != nil {
+ _ = f.Close()
+ return "", err
+ }
+ if err := f.Close(); err != nil {
+ return "", err
+ }
+ if err := RunEditor(ed, path); err != nil {
+ return "", err
+ }
+ b, err := os.ReadFile(filepath.Clean(path))
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(b)), nil
+}