From ca5b6666ddf12e50ea85540b8e2dc65f74122424 Mon Sep 17 00:00:00 2001 From: csvenke Date: Wed, 10 Dec 2025 03:27:50 +0100 Subject: [PATCH] feat: improve terminal multiplexer integration * replace hooks system with terminal interface supporting * add automatic terminal detection based on environment variables * implement terminal-specific editor opening strategies (zellij edit, tmux respawn-pane, direct exec) --- internal/app/app.go | 40 ++------------- internal/hooks/hooks.go | 33 ------------- internal/terminal/terminal.go | 91 +++++++++++++++++++++++++++++++++++ main.go | 4 +- 4 files changed, 98 insertions(+), 70 deletions(-) delete mode 100644 internal/hooks/hooks.go create mode 100644 internal/terminal/terminal.go diff --git a/internal/app/app.go b/internal/app/app.go index 79886c7..7e8195c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,21 +2,18 @@ package app import ( "fmt" - "os" - "os/exec" - "syscall" "github.com/samber/mo" "dev/internal/filesystem" - "dev/internal/hooks" "dev/internal/projects" + "dev/internal/terminal" "dev/internal/tui" ) type Config struct { - Icons tui.Icons - Hooks []hooks.Hook + Icons tui.Icons + Terminal terminal.Terminal } func Run(args []string, cfg Config, fs filesystem.FileSystem) mo.Result[string] { @@ -40,34 +37,7 @@ func Run(args []string, cfg Config, fs filesystem.FileSystem) mo.Result[string] return mo.Err[string](err) } - hooks.RunAll(cfg.Hooks, tuiResult.Name) + cfg.Terminal.RenameTab(tuiResult.Name) - return openEditor(tuiResult.Path) -} - -func openEditor(path string) mo.Result[string] { - editor, err := getEditorFromEnv().Get() - if err != nil { - return mo.Err[string](err) - } - - editorPath, err := exec.LookPath(editor) - if err != nil { - return mo.Err[string](err) - } - if err := syscall.Exec(editorPath, []string{editor, path}, os.Environ()); err != nil { - return mo.Err[string](err) - } - - return mo.Ok(editorPath) -} - -func getEditorFromEnv() mo.Result[string] { - if editor := os.Getenv("VISUAL"); editor != "" { - return mo.Ok(editor) - } - if editor := os.Getenv("EDITOR"); editor != "" { - return mo.Ok(editor) - } - return mo.Err[string](fmt.Errorf("$VISUAL or $EDITOR is not set")) + return cfg.Terminal.OpenEditor(tuiResult.Path) } diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go deleted file mode 100644 index a6dbea1..0000000 --- a/internal/hooks/hooks.go +++ /dev/null @@ -1,33 +0,0 @@ -package hooks - -import ( - "os" - "os/exec" - - "github.com/samber/lo" -) - -type Hook func(projectName string) error - -var Default = []Hook{ - tmuxHook, - zellijHook, -} - -func RunAll(hooks []Hook, projectName string) { - lo.ForEach(hooks, func(hook Hook, _ int) { _ = hook(projectName) }) -} - -func tmuxHook(name string) error { - if os.Getenv("TMUX") == "" { - return nil - } - return exec.Command("tmux", "rename-window", name).Run() -} - -func zellijHook(name string) error { - if os.Getenv("ZELLIJ") == "" { - return nil - } - return exec.Command("zellij", "action", "rename-tab", name).Run() -} diff --git a/internal/terminal/terminal.go b/internal/terminal/terminal.go new file mode 100644 index 0000000..d7236e0 --- /dev/null +++ b/internal/terminal/terminal.go @@ -0,0 +1,91 @@ +package terminal + +import ( + "fmt" + "os" + "os/exec" + + "github.com/samber/mo" +) + +type Terminal interface { + OpenEditor(path string) mo.Result[string] + RenameTab(name string) error +} + +func Detect() Terminal { + if os.Getenv("ZELLIJ") != "" { + return &Zellij{} + } + if os.Getenv("TMUX") != "" { + return &Tmux{} + } + return &Default{} +} + +type Zellij struct{} + +func (z *Zellij) OpenEditor(path string) mo.Result[string] { + return run("zellij", "", "edit", "--cwd", path, "-i", ".") +} + +func (z *Zellij) RenameTab(name string) error { + return exec.Command("zellij", "action", "rename-tab", name).Run() +} + +type Tmux struct{} + +func (t *Tmux) OpenEditor(path string) mo.Result[string] { + editor, err := getEditorFromEnv().Get() + if err != nil { + return mo.Err[string](err) + } + return run("tmux", "", "respawn-pane", "-k", "-c", path, editor, ".") +} + +func (t *Tmux) RenameTab(name string) error { + return exec.Command("tmux", "rename-window", name).Run() +} + +type Default struct{} + +func (d *Default) OpenEditor(path string) mo.Result[string] { + editor, err := getEditorFromEnv().Get() + if err != nil { + return mo.Err[string](err) + } + return run(editor, path, ".") +} + +func (d *Default) RenameTab(name string) error { + return nil +} + +func run(name string, dir string, args ...string) mo.Result[string] { + path, err := exec.LookPath(name) + if err != nil { + return mo.Err[string](err) + } + + cmd := exec.Command(path, args...) + cmd.Dir = dir + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return mo.Err[string](err) + } + + return mo.Ok(path) +} + +func getEditorFromEnv() mo.Result[string] { + if editor := os.Getenv("VISUAL"); editor != "" { + return mo.Ok(editor) + } + if editor := os.Getenv("EDITOR"); editor != "" { + return mo.Ok(editor) + } + return mo.Err[string](fmt.Errorf("$VISUAL or $EDITOR is not set")) +} diff --git a/main.go b/main.go index 806e73e..317f8ff 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,7 @@ import ( "dev/internal/app" "dev/internal/filesystem" - "dev/internal/hooks" + "dev/internal/terminal" "dev/internal/tui" ) @@ -37,7 +37,7 @@ func main() { Icons: tui.Icons{ Dir: "", }, - Hooks: hooks.Default, + Terminal: terminal.Detect(), } fs := &filesystem.RealFileSystem{}