summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-07 14:32:48 +0300
committerPaul Buetow <paul@buetow.org>2025-09-07 14:32:48 +0300
commit90e586831c0351fb5808ef5c1eca0692178731c9 (patch)
tree28b5edd41ec468ae34dc7f05eb3b994e07b02d71
parent23482b5d8da5c67da1fc501ddbafdd123be3972c (diff)
tui: hide title in hexai-tmux-action menu to save space
-rw-r--r--TODO.md121
-rw-r--r--docs/coverage.html172
-rw-r--r--docs/coverage.out5379
-rw-r--r--internal/hexaiaction/tui.go2
4 files changed, 2681 insertions, 2993 deletions
diff --git a/TODO.md b/TODO.md
index 276009f..e69de29 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,121 +0,0 @@
-Comprehensive plan: integrate Helix + tmux flow into hexai-tmux-action
-
-Summary of current setup
-- Helix keybinding pipes selection to the tmux action command.
-- The tmux action writes stdin to a temp file, opens a tmux split-pane, runs `hexai-tmux-action -infile <input> -outfile <reply>.tmp`, then atomically renames to `<reply>` and prints the reply back to Helix.
-- This works but requires shell scripts and out-of-band temp files.
-
-Goal
-- Helix should call hexai-tmux-action directly: `:pipe hexai-tmux-action`.
-- hexai-tmux-action itself handles: reading stdin; presenting TUI in a tmux pane; executing the chosen action; printing the result to stdout for Helix to apply.
-- Consolidate all tmux logic in a reusable `internal/tmux` package for future features.
-
-Proposed CLI/UX
-- Default (auto):
- - If running in an interactive TTY, run TUI inline and print the result to stdout.
- - If stdin/stdout are pipes (Helix `:pipe`) and a tmux session is available, spawn a temporary tmux pane to render the TUI, then return the final output on stdout.
- - If tmux is not available and no TTY is present, fall back to a sensible non-interactive mode: echo input or use instruction-based rewrite if an inline instruction is detected.
-- Flags (public):
- - `-tmux` (bool, default: auto): force enabling tmux-pane mode.
- - `-no-tmux` (bool): force disabling tmux-pane mode even if available.
- - `-infile`, `-outfile`: keep for compatibility/testing, but not needed for Helix.
-- Flags (internal/private; hidden in help):
- - `-ui-child` (bool): internal child mode that assumes `-infile` and `-outfile` and runs the interactive TUI on the attached TTY, writing final output to outfile.
- - `-tmux-target` (string, optional): tmux target pane/window (advanced users).
- - `-tmux-split` (enum: `v|h`, default `v`): split orientation.
-
-High-level design
-1) IO orchestration (parent process)
- - Determine whether to show TUI inline or via tmux based on TTY detection and `-tmux`/`-no-tmux`.
- - Inline path: run current `hexaiaction.Run(ctx, in, out, err)` (unchanged behavior) and exit.
- - Tmux path: write stdin to a secure temp file, spawn a tmux split-pane that executes a `hexai-tmux-action -ui-child -infile <in> -outfile <out>.tmp`, wait for completion (by process exit and/or file rename), then print `<out>` to stdout.
-
-2) Child/TUI execution (`-ui-child`)
- - Read from `-infile`, parse input (diagnostics + selection), construct LLM client, show Bubble Tea menu, run selected action, write result to `-outfile.tmp`, fsync, rename to `-outfile`.
- - On error, write a human-readable message to stderr and a minimal fallback to outfile (e.g., echo selection) to avoid blocking Helix.
-
-3) Tmux integration package (`internal/tmux`)
- - Responsibilities:
- - Detect availability: binary present and inside a tmux session (`$TMUX` set) or a viable target.
- - Run commands in a new split pane and return control immediately or after completion.
- - Small helpers for file-based rendezvous (optional), e.g., `WaitForFile(path, timeout)`.
- - Minimal API (initial):
- - `func Available() bool`
- - `type SplitOpts struct { Target string; Vertical bool; Percent int }`
- - `func SplitRun(opts SplitOpts, argv []string) error` — runs `tmux split-window ... <argv>` and returns once tmux has launched the child process.
- - `func HasBinary() bool` and `func InSession() bool` (if we want finer checks).
- - Implementation details:
- - Shell out to `tmux` (no lib dep). Build command like: `tmux split-window -v -p 33 "<cmd>"`.
- - Quote/escape argv safely. Prefer `exec.Command` for the child in a shell wrapper, or join argv for `tmux`’s command string.
- - Avoid writing to `~/.hx-*`; use `os.CreateTemp("", "hexai-tmux-action-*" )` under `$TMPDIR`.
-
-4) hexaiaction refactor (internal package)
- - Separate concerns to keep functions small/testable:
- - New function `ChooseAction(ctx, stdin, stderr) (ActionKind, InputParts, error)` that parses input and (conditionally) runs TUI.
- - Existing action runners remain unchanged (rewrite, document, diagnostics, gotest).
- - Keep `Run` as a thin orchestrator that assumes interactive mode; the parent (cmd) decides inline vs tmux/child.
- - Ensure unit-testable seams: parse, instruction extraction, action execution already have tests; add tests for new branching logic where possible without tmux.
-
-5) Robustness and UX details
- - TTY detection: use `golang.org/x/term` or a small `isatty` helper to decide inline vs tmux.
- - Timeouts: child actions already use short timeouts; parent wait for outfile should have a reasonable deadline (e.g., 60s) to avoid hanging Helix.
- - Atomic writes: write to `outfile.tmp`, `Sync`, then `Rename` for a clear completion signal.
- - Cleanup: always remove temp files (defer and signal handling for SIGINT/SIGTERM).
- - Logging: log to stderr with clear `hexai-tmux-action` prefixes; keep stdout clean for Helix’s `:pipe`.
-
-Helix configuration after change
-- Replace the current keybinding pipeline with a single call:
- - `C-a = ":pipe hexai-tmux-action"`
-
-Migration plan
-1) Implement tmux package and integrate auto-mode in `cmd/hexai-tmux-action/main.go`.
-2) Keep legacy flags (`-infile`, `-outfile`) for compatibility and tests.
-3) Update README and docs to show new Helix keybinding and describe flags.
-4) Mark shell scripts (`llminputs/ai`, `llminputs/hx.hexai-action-prompt`) as deprecated in repo notes; retain them temporarily.
-5) After a stabilization period, remove the scripts (or move to `scripts/legacy/`).
-
-Testing plan
-- Unit tests:
- - `internal/tmux`: mock `exec.Command` via a small command-runner interface; verify command assembly and availability checks.
- - `internal/hexaiaction`: tests for tmux split orchestration and child flow.
- - hexaiaction: existing tests continue to pass; add tests for non-interactive fallback behavior when no TTY.
-- Integration tests (manual or scripted):
- - Run under tmux: verify a pane opens, TUI choice is applied, stdout contains result.
- - Run outside tmux: verify inline TUI works in terminal; verify fallback behavior under `:pipe` without tmux.
- - Coverage target: ensure at least 85% unit test coverage for all new code paths added in this integration (verify with `go test -cover ./...` and per-package `-coverprofile`).
-
-Edge cases and mitigations
-- No tmux, no TTY: skip TUI; if inline instruction detected (`;...;`, `// ...`, etc.), run rewrite; else echo selection.
-- tmux available but spawning fails: warn on stderr and fall back to non-interactive mode.
-- Large inputs: spill to temp files; ensure temp dir has space; surface errors clearly.
-- Windows: tmux mode auto-disables; inline mode continues to work.
-
-Implementation steps (incremental)
-1) Add `internal/tmux` with `Available`, `SplitRun`, and helpers.
-2) Wire flags (`-ui-child`, `-tmux-target`, `-tmux-split`, `-tmux-percent`).
-3) Parent flow: detect mode, manage temp files, spawn child via tmux when selected, wait/print result.
-4) Child flow: reuse existing `hexaiaction.Run` to keep logic centralized; ensure outfile atomic write.
-5) Docs: update README with new Helix config and flags.
-6) Optional: add `-tmux-target`/`-tmux-split` for power users.
-
-Notes on code organization
-- Place all tmux-related code under `internal/tmux` and keep functions well under 50 lines.
-- Keep command entrypoint (`cmd/hexai-tmux-action/main.go`) small and focused on wiring/mode selection.
-- Avoid duplication across `hexaiaction` and `tmux`; IO/file and action logic remain in `hexaiaction`.
-
-Outcome
-- One-step Helix integration (`:pipe hexai-tmux-action`).
-- No helper scripts required; cross-platform friendly with graceful fallbacks.
-- Reusable tmux utilities for future features.
-
-Progress
-- [x] Add `internal/tmux` with `Available`, `SplitRun`, quoting helpers.
-- [x] Wire flags in tmux action: `-ui-child`, `-tmux-target`, `-tmux-split`, `-tmux-percent`.
-- [x] Parent tmux orchestration: write stdin to temp, split tmux, wait for outfile, print to stdout.
-- [x] Child mode: atomic `outfile.tmp` write and rename, with error echo fallback.
-- [x] Unit tests for `internal/tmux` and tmux orchestration in action (validate locally; target ≥85% coverage for new code).
-- [x] Update README/docs for new Helix keybinding and flags.
-- [ ] Delete legacy helper scripts (`llminputs/ai`, `llminputs/hx.hexai-action-prompt`) when ready; no deprecation notice.
-- [x] Ran coverage locally. Notes:
- - `mage coverage` now passes (HTML at docs/coverage.html). Total cross-package coverage ≈ 84%.
- - New package `internal/tmux` is ≥85% covered. The action entrypoint package sits ~69% overall; newly added helper paths are covered (openIO, runChild, runInTmuxParent, etc.). The `main()` remains intentionally untested.
diff --git a/docs/coverage.html b/docs/coverage.html
index 90eae60..fb5d655 100644
--- a/docs/coverage.html
+++ b/docs/coverage.html
@@ -3,7 +3,7 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>hexai-action: Go Coverage Report</title>
+ <title>hexai-lsp: Go Coverage Report</title>
<style>
body {
background: black;
@@ -55,15 +55,15 @@
<div id="nav">
<select id="files">
- <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-action/main.go (0.0%)</option>
+ <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option>
- <option value="file1">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option>
+ <option value="file1">codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go (0.0%)</option>
<option value="file2">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option>
<option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (91.6%)</option>
- <option value="file4">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (81.5%)</option>
+ <option value="file4">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (84.5%)</option>
<option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option>
@@ -144,7 +144,34 @@
</div>
<div id="content">
- <pre class="file" id="file0" style="display: none">package main
+ <pre class="file" id="file0" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp.
+package main
+
+import (
+ "flag"
+ "log"
+ "os"
+
+ "codeberg.org/snonux/hexai/internal"
+ "codeberg.org/snonux/hexai/internal/hexailsp"
+)
+
+func main() <span class="cov8" title="1">{
+ logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)")
+ showVersion := flag.Bool("version", false, "print version and exit")
+ flag.Parse()
+ if *showVersion </span><span class="cov8" title="1">{
+ log.Println(internal.Version)
+ return
+ }</span>
+
+ <span class="cov0" title="0">if err := hexailsp.Run(*logPath, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
+ log.Fatalf("server error: %v", err)
+ }</span>
+}
+</pre>
+
+ <pre class="file" id="file1" style="display: none">package main
import (
"context"
@@ -158,8 +185,6 @@ import (
func main() <span class="cov0" title="0">{
infile := flag.String("infile", "", "Read input from this file instead of stdin")
outfile := flag.String("outfile", "", "Write output to this file instead of stdout")
- forceTmux := flag.Bool("tmux", false, "Force running the UI in a tmux split-pane (auto if not set)")
- noTmux := flag.Bool("no-tmux", false, "Disable tmux mode even if available")
uiChild := flag.Bool("ui-child", false, "INTERNAL: run interactive UI and write to -outfile atomically")
tmuxTarget := flag.String("tmux-target", "", "tmux split target (advanced)")
tmuxSplit := flag.String("tmux-split", "v", "tmux split orientation: v or h")
@@ -168,8 +193,7 @@ func main() <span class="cov0" title="0">{
opts := hexaiaction.Options{
Infile: *infile, Outfile: *outfile,
- ForceTmux: *forceTmux, NoTmux: *noTmux, UIChild: *uiChild,
- TmuxTarget: *tmuxTarget, TmuxSplit: *tmuxSplit, TmuxPercent: *tmuxPercent,
+ UIChild: *uiChild, TmuxTarget: *tmuxTarget, TmuxSplit: *tmuxSplit, TmuxPercent: *tmuxPercent,
}
if err := hexaiaction.RunCommand(context.Background(), opts, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
fmt.Fprintln(os.Stderr, err)
@@ -179,33 +203,6 @@ func main() <span class="cov0" title="0">{
</pre>
- <pre class="file" id="file1" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp.
-package main
-
-import (
- "flag"
- "log"
- "os"
-
- "codeberg.org/snonux/hexai/internal"
- "codeberg.org/snonux/hexai/internal/hexailsp"
-)
-
-func main() <span class="cov8" title="1">{
- logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)")
- showVersion := flag.Bool("version", false, "print version and exit")
- flag.Parse()
- if *showVersion </span><span class="cov8" title="1">{
- log.Println(internal.Version)
- return
- }</span>
-
- <span class="cov0" title="0">if err := hexailsp.Run(*logPath, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
- log.Fatalf("server error: %v", err)
- }</span>
-}
-</pre>
-
<pre class="file" id="file2" style="display: none">// Summary: Hexai CLI entrypoint; parses flags and delegates to internal/hexaicli.
package main
@@ -1076,75 +1073,55 @@ import (
"golang.org/x/term"
)
-// Options configures the command-line orchestration for hexai-action.
+// Options configures the command-line orchestration for hexai-tmux-action.
type Options struct {
Infile string
Outfile string
- ForceTmux bool
- NoTmux bool
UIChild bool
TmuxTarget string
TmuxSplit string // "v" or "h"
TmuxPercent int // 1-100
}
-// RunCommand is the CLI orchestrator used by cmd/hexai-action. It decides whether
-// to run inline, in a tmux split pane, or in child mode; then delegates to Run.
-func RunCommand(ctx context.Context, opts Options, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov6" title="3">{
+// RunCommand is the CLI orchestrator used by cmd/hexai-tmux-action. It runs in tmux
+// split-pane mode by default, or child mode when -ui-child is set.
+func RunCommand(ctx context.Context, opts Options, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov4" title="2">{
if opts.UIChild </span><span class="cov1" title="1">{
return runChild(ctx, opts.Infile, opts.Outfile, stdout, stderr)
}</span>
- <span class="cov4" title="2">if shouldRunInTmux(opts.ForceTmux, opts.NoTmux) </span><span class="cov1" title="1">{
- return runInTmuxParent(stdin, stdout, opts.TmuxTarget, opts.TmuxSplit, opts.TmuxPercent)
- }</span>
- // Inline path: only if we have a TTY for UI; otherwise echo input
- <span class="cov1" title="1">if isTTYFn(os.Stdout.Fd()) &amp;&amp; isTTYFn(os.Stdin.Fd()) </span><span class="cov0" title="0">{
- in, out, closeIn, closeOut, err := openIO(opts.Infile, opts.Outfile)
- if err != nil </span><span class="cov0" title="0">{ return err }</span>
- <span class="cov0" title="0">defer closeIn(); defer closeOut()
- return Run(ctx, in, out, stderr)</span>
- }
- // Fallback: echo
- <span class="cov1" title="1">return echoThrough(opts.Infile, opts.Outfile, stdin, stdout)</span>
+ // Always use tmux path
+ <span class="cov1" title="1">return runInTmuxParent(stdin, stdout, opts.TmuxTarget, opts.TmuxSplit, opts.TmuxPercent)</span>
}
// seams for unit tests
var isTTYFn = func(fd uintptr) bool <span class="cov0" title="0">{ return term.IsTerminal(int(fd)) }</span>
-var tmuxAvailableFn = tmux.Available
var splitRunFn = tmux.SplitRun
var osExecutableFn = os.Executable
var runFn = Run
-func shouldRunInTmux(forceTmux, noTmux bool) bool <span class="cov10" title="7">{
- if noTmux </span><span class="cov4" title="2">{ return false }</span>
- <span class="cov8" title="5">if forceTmux </span><span class="cov4" title="2">{ return true }</span>
- <span class="cov6" title="3">if !(isTTYFn(os.Stdin.Fd()) &amp;&amp; isTTYFn(os.Stdout.Fd())) &amp;&amp; tmuxAvailableFn() </span><span class="cov1" title="1">{ return true }</span>
- <span class="cov4" title="2">return false</span>
-}
-
// openIO returns readers/writers for infile/outfile flags with deferred closers.
-func openIO(infile, outfile string) (io.Reader, io.Writer, func(), func(), error) <span class="cov6" title="3">{
+func openIO(infile, outfile string) (io.Reader, io.Writer, func(), func(), error) <span class="cov7" title="3">{
in := io.Reader(os.Stdin)
out := io.Writer(os.Stdout)
closeIn := func() </span>{<span class="cov0" title="0">}</span>
- <span class="cov6" title="3">closeOut := func() </span>{<span class="cov0" title="0">}</span>
- <span class="cov6" title="3">if path := infile; path != "" </span><span class="cov6" title="3">{
+ <span class="cov7" title="3">closeOut := func() </span>{<span class="cov0" title="0">}</span>
+ <span class="cov7" title="3">if path := infile; path != "" </span><span class="cov7" title="3">{
f, err := os.Open(path)
- if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-action: cannot open infile: %w", err) }
- <span class="cov6" title="3">in = f
- closeIn = func() </span><span class="cov6" title="3">{ _ = f.Close() }</span>
+ if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-tmux-action: cannot open infile: %w", err) }
+ <span class="cov7" title="3">in = f
+ closeIn = func() </span><span class="cov7" title="3">{ _ = f.Close() }</span>
}
- <span class="cov6" title="3">if path := outfile; path != "" </span><span class="cov6" title="3">{
+ <span class="cov7" title="3">if path := outfile; path != "" </span><span class="cov7" title="3">{
f, err := os.Create(path)
- if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-action: cannot open outfile: %w", err) }
- <span class="cov6" title="3">out = f
- closeOut = func() </span><span class="cov6" title="3">{ _ = f.Close() }</span>
+ if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-tmux-action: cannot open outfile: %w", err) }
+ <span class="cov7" title="3">out = f
+ closeOut = func() </span><span class="cov7" title="3">{ _ = f.Close() }</span>
}
- <span class="cov6" title="3">return in, out, closeIn, closeOut, nil</span>
+ <span class="cov7" title="3">return in, out, closeIn, closeOut, nil</span>
}
// runChild runs the interactive flow and writes the final output atomically when outfile is set.
-func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Writer) error <span class="cov6" title="3">{
+func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Writer) error <span class="cov7" title="3">{
if outfile == "" </span><span class="cov1" title="1">{
// No atomic handoff needed; just run normally to provided stdout
var in io.Reader = os.Stdin
@@ -1163,7 +1140,7 @@ func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Wri
if err := runFn(ctx, in, out, stderr); err != nil </span><span class="cov0" title="0">{
closeOut()
if copyErr := echoThrough(infile, tmp, os.Stdin, stdout); copyErr != nil </span><span class="cov0" title="0">{
- return fmt.Errorf("hexai-action child: %v; echo failed: %v", err, copyErr)
+ return fmt.Errorf("hexai-tmux-action child: %v; echo failed: %v", err, copyErr)
}</span>
} else<span class="cov4" title="2"> {
closeOut()
@@ -1171,35 +1148,35 @@ func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Wri
<span class="cov4" title="2">return os.Rename(tmp, outfile)</span>
}
-func runInTmuxParent(stdin io.Reader, stdout io.Writer, target, split string, percent int) error <span class="cov7" title="4">{
- dir, err := os.MkdirTemp("", "hexai-action-")
+func runInTmuxParent(stdin io.Reader, stdout io.Writer, target, split string, percent int) error <span class="cov8" title="4">{
+ dir, err := os.MkdirTemp("", "hexai-tmux-action-")
if err != nil </span><span class="cov0" title="0">{ return err }</span>
- <span class="cov7" title="4">defer func() </span><span class="cov7" title="4">{ _ = os.RemoveAll(dir) }</span>()
- <span class="cov7" title="4">inPath := filepath.Join(dir, "input.txt")
+ <span class="cov8" title="4">defer func() </span><span class="cov8" title="4">{ _ = os.RemoveAll(dir) }</span>()
+ <span class="cov8" title="4">inPath := filepath.Join(dir, "input.txt")
outPath := filepath.Join(dir, "reply.txt")
if err := persistStdin(inPath, stdin); err != nil </span><span class="cov0" title="0">{ return err }</span>
- <span class="cov7" title="4">exe, err := osExecutableFn()
+ <span class="cov8" title="4">exe, err := osExecutableFn()
if err != nil </span><span class="cov1" title="1">{ return err }</span>
- <span class="cov6" title="3">argv := []string{exe, "-ui-child", "-infile", inPath, "-outfile", outPath}
+ <span class="cov7" title="3">argv := []string{exe, "-ui-child", "-infile", inPath, "-outfile", outPath}
opts := tmux.SplitOpts{Target: target, Vertical: split != "h", Percent: percent}
if err := splitRunFn(opts, argv); err != nil </span><span class="cov1" title="1">{ return err }</span>
<span class="cov4" title="2">if err := waitForFile(outPath, 60*time.Second); err != nil </span><span class="cov0" title="0">{ return err }</span>
<span class="cov4" title="2">return catFileTo(stdout, outPath)</span>
}
-func persistStdin(path string, stdin io.Reader) error <span class="cov8" title="5">{
+func persistStdin(path string, stdin io.Reader) error <span class="cov10" title="5">{
f, err := os.Create(path)
if err != nil </span><span class="cov0" title="0">{ return err }</span>
- <span class="cov8" title="5">defer func() </span><span class="cov8" title="5">{ _ = f.Close() }</span>()
- <span class="cov8" title="5">if _, err := io.Copy(f, stdin); err != nil </span><span class="cov0" title="0">{ return err }</span>
- <span class="cov8" title="5">return f.Sync()</span>
+ <span class="cov10" title="5">defer func() </span><span class="cov10" title="5">{ _ = f.Close() }</span>()
+ <span class="cov10" title="5">if _, err := io.Copy(f, stdin); err != nil </span><span class="cov0" title="0">{ return err }</span>
+ <span class="cov10" title="5">return f.Sync()</span>
}
-func waitForFile(path string, timeout time.Duration) error <span class="cov6" title="3">{
+func waitForFile(path string, timeout time.Duration) error <span class="cov7" title="3">{
deadline := time.Now().Add(timeout)
- for </span><span class="cov7" title="4">{
+ for </span><span class="cov8" title="4">{
if _, err := os.Stat(path); err == nil </span><span class="cov4" title="2">{ return nil }</span>
- <span class="cov4" title="2">if time.Now().After(deadline) </span><span class="cov1" title="1">{ return fmt.Errorf("hexai-action: timeout waiting for reply file") }</span>
+ <span class="cov4" title="2">if time.Now().After(deadline) </span><span class="cov1" title="1">{ return fmt.Errorf("hexai-tmux-action: timeout waiting for reply file") }</span>
<span class="cov1" title="1">time.Sleep(200 * time.Millisecond)</span>
}
}
@@ -1212,7 +1189,8 @@ func catFileTo(w io.Writer, path string) error <span class="cov4" title="2">{
return err</span>
}
-func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) error <span class="cov6" title="3">{
+// echoThrough no longer used in tmux-only flow, but kept for potential reuse.
+func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) error <span class="cov4" title="2">{
var in io.Reader = stdin
var out io.Writer = stdout
if infile != "" </span><span class="cov1" title="1">{
@@ -1221,13 +1199,13 @@ func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) erro
<span class="cov1" title="1">defer func() </span><span class="cov1" title="1">{ _ = f.Close() }</span>()
<span class="cov1" title="1">in = f</span>
}
- <span class="cov6" title="3">if outfile != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="2">if outfile != "" </span><span class="cov1" title="1">{
f, err := os.Create(outfile)
if err != nil </span><span class="cov0" title="0">{ return err }</span>
<span class="cov1" title="1">defer func() </span><span class="cov1" title="1">{ _ = f.Close() }</span>()
<span class="cov1" title="1">out = f</span>
}
- <span class="cov6" title="3">_, err := io.Copy(out, in)
+ <span class="cov4" title="2">_, err := io.Copy(out, in)
return err</span>
}
</pre>
@@ -1410,26 +1388,26 @@ import (
"codeberg.org/snonux/hexai/internal/llmutils"
)
-// Run executes the hexai-action command flow.
+// Run executes the hexai-tmux-action command flow.
// seams for testability
var chooseActionFn = RunTUI
var newClientFromApp = llmutils.NewClientFromApp
func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov6" title="3">{
- logger := log.New(stderr, "hexai-action ", log.LstdFlags|log.Lmsgprefix)
+ logger := log.New(stderr, "hexai-tmux-action ", log.LstdFlags|log.Lmsgprefix)
cfg := appconfig.Load(logger)
client, err := newClientFromApp(cfg)
if err != nil </span><span class="cov1" title="1">{
- fmt.Fprintf(stderr, logging.AnsiBase+"hexai-action: LLM disabled: %v"+logging.AnsiReset+"\n", err)
+ fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err)
return err
}</span>
<span class="cov4" title="2">parts, err := ParseInput(stdin)
if err != nil </span><span class="cov0" title="0">{
- fmt.Fprintln(stderr, logging.AnsiBase+"hexai-action: failed to read input"+logging.AnsiReset)
+ fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset)
return err
}</span>
<span class="cov4" title="2">if strings.TrimSpace(parts.Selection) == "" </span><span class="cov0" title="0">{
- return fmt.Errorf("hexai-action: no input provided on stdin")
+ return fmt.Errorf("hexai-tmux-action: no input provided on stdin")
}</span>
<span class="cov4" title="2">kind, err := chooseActionFn()
if err != nil </span><span class="cov0" title="0">{
@@ -1450,7 +1428,7 @@ func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg a
case ActionRewrite:<span class="cov4" title="2">
instr, cleaned := ExtractInstruction(parts.Selection)
if strings.TrimSpace(instr) == "" </span><span class="cov0" title="0">{
- fmt.Fprintln(stderr, logging.AnsiBase+"hexai-action: no inline instruction found; echoing input"+logging.AnsiReset)
+ fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset)
return parts.Selection, nil
}</span>
<span class="cov4" title="2">cctx, cancel := timeout10s(ctx)
diff --git a/docs/coverage.out b/docs/coverage.out
index e1b4e9b..40ded60 100644
--- a/docs/coverage.out
+++ b/docs/coverage.out
@@ -1,16 +1,14 @@
mode: atomic
-codeberg.org/snonux/hexai/cmd/hexai-action/main.go:12.13,28.110 11 0
-codeberg.org/snonux/hexai/cmd/hexai-action/main.go:28.110,31.6 2 0
codeberg.org/snonux/hexai/cmd/hexai/main.go:14.13,17.18 3 1
codeberg.org/snonux/hexai/cmd/hexai/main.go:17.18,20.3 2 1
codeberg.org/snonux/hexai/cmd/hexai/main.go:22.2,22.104 1 0
codeberg.org/snonux/hexai/cmd/hexai/main.go:22.104,24.3 1 0
-codeberg.org/snonux/hexai/cmd/hexai-action/main.go:12.13,28.110 11 0
-codeberg.org/snonux/hexai/cmd/hexai-action/main.go:28.110,31.6 2 0
codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:13.13,17.18 4 0
codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:17.18,20.3 2 0
codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:22.2,22.79 1 0
codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:22.79,24.3 1 0
+codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go:12.13,25.110 9 0
+codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go:25.110,28.6 2 0
codeberg.org/snonux/hexai/internal/appconfig/config.go:89.29,136.2 2 0
codeberg.org/snonux/hexai/internal/appconfig/config.go:140.35,142.19 2 0
codeberg.org/snonux/hexai/internal/appconfig/config.go:142.19,144.3 1 0
@@ -275,101 +273,88 @@ codeberg.org/snonux/hexai/internal/appconfig/config.go:818.61,821.3 2 0
codeberg.org/snonux/hexai/internal/appconfig/config.go:823.2,823.10 1 0
codeberg.org/snonux/hexai/internal/appconfig/config.go:823.10,825.3 1 0
codeberg.org/snonux/hexai/internal/appconfig/config.go:826.2,826.13 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:29.101,30.21 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:30.21,32.6 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:33.5,33.53 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:33.53,35.6 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:37.5,37.58 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:37.58,39.23 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:39.23,39.37 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:40.9,41.41 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:44.5,44.65 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:48.37,48.72 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:54.51,55.15 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.15,55.31 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:56.5,56.18 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:56.18,56.33 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:57.5,57.82 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:57.82,57.97 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:58.5,58.17 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:62.83,65.23 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:65.24,65.25 0 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:66.5,66.24 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:66.25,66.26 0 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:67.5,67.35 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:67.35,69.23 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.23,69.48 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.49,69.50 0 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.59,69.60 0 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:70.9,71.26 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:71.26,71.43 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:73.5,73.36 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:73.36,75.23 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.23,75.48 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.49,75.50 0 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.59,75.60 0 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:76.9,77.27 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:77.27,77.44 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:79.5,79.43 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:83.92,84.22 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:84.22,87.25 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:87.25,89.27 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:89.27,89.41 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:90.13,90.25 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:90.25,90.42 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:91.13,91.19 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:93.9,93.46 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:95.5,97.19 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:97.19,97.33 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:98.5,99.55 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:99.55,101.82 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:101.82,103.10 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:104.11,106.6 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:107.5,107.35 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:110.98,112.19 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:112.19,112.33 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:113.5,113.18 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:113.18,113.43 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:114.5,116.55 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:116.55,116.69 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:117.5,118.19 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:118.19,118.33 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:119.5,121.50 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:121.50,121.64 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:122.5,122.64 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:122.64,122.78 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:123.5,123.38 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:126.55,128.19 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:128.19,128.33 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:129.5,129.18 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:129.18,129.35 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:130.5,130.48 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:130.48,130.62 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:131.5,131.20 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:134.60,136.9 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:136.9,137.48 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:137.48,137.62 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:138.9,138.39 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:138.39,138.108 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:139.9,139.43 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:143.48,145.19 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:145.19,145.33 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:146.5,146.18 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:146.18,146.35 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:27.101,28.21 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:28.21,30.6 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:32.5,32.93 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:36.37,36.72 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:42.83,45.23 3 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:45.24,45.25 0 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:46.5,46.24 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:46.25,46.26 0 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:47.5,47.35 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:47.35,49.23 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:49.23,49.48 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:49.49,49.50 0 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:49.59,49.60 0 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:50.9,51.26 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:51.26,51.43 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:53.5,53.36 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:53.36,55.23 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.23,55.48 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.49,55.50 0 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.59,55.60 0 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:56.9,57.27 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:57.27,57.44 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:59.5,59.43 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:63.92,64.22 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:64.22,67.25 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:67.25,69.27 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.27,69.41 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:70.13,70.25 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:70.25,70.42 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:71.13,71.19 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:73.9,73.46 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.5,77.19 3 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:77.19,77.33 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:78.5,79.55 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:79.55,81.82 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:81.82,83.10 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:84.11,86.6 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:87.5,87.35 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:90.98,92.19 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:92.19,92.33 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:93.5,93.18 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:93.18,93.43 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:94.5,96.55 3 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:96.55,96.69 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:97.5,98.19 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:98.19,98.33 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:99.5,101.50 3 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:101.50,101.64 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:102.5,102.64 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:102.64,102.78 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:103.5,103.38 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:106.55,108.19 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:108.19,108.33 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:109.5,109.18 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:109.18,109.35 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:110.5,110.48 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:110.48,110.62 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:111.5,111.20 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:114.60,116.9 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:116.9,117.48 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:117.48,117.62 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:118.9,118.39 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:118.39,118.113 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:119.9,119.43 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:123.48,125.19 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:12