diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-07 11:39:36 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-07 11:39:36 +0300 |
| commit | 9a901054e8828054f7b26514b72b6e938f97e4a7 (patch) | |
| tree | c2f1419a5a18f3cc8c23b5a46713831ff043a964 /docs/coverage.html | |
| parent | fb06bfa9dc7140f987bdbad59467a84610221fbb (diff) | |
chore: prune legacy helper scripts in llminputs/
Diffstat (limited to 'docs/coverage.html')
| -rw-r--r-- | docs/coverage.html | 573 |
1 files changed, 270 insertions, 303 deletions
diff --git a/docs/coverage.html b/docs/coverage.html index 2003a0d..8f6eb62 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-lsp: Go Coverage Report</title> + <title>hexai-action: Go Coverage Report</title> <style> body { background: black; @@ -55,71 +55,73 @@ <div id="nav"> <select id="files"> - <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option> + <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-action/main.go (0.0%)</option> - <option value="file1">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option> + <option value="file1">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option> - <option value="file2">codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go (69.3%)</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/parse.go (92.6%)</option> + <option value="file4">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (76.5%)</option> - <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (91.9%)</option> + <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option> - <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (48.7%)</option> + <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (91.9%)</option> - <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option> + <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (48.7%)</option> - <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option> + <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option> - <option value="file9">codeberg.org/snonux/hexai/internal/hexaicli/run.go (78.8%)</option> + <option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option> - <option value="file10">codeberg.org/snonux/hexai/internal/hexailsp/run.go (92.5%)</option> + <option value="file10">codeberg.org/snonux/hexai/internal/hexaicli/run.go (78.8%)</option> - <option value="file11">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option> + <option value="file11">codeberg.org/snonux/hexai/internal/hexailsp/run.go (92.5%)</option> - <option value="file12">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option> + <option value="file12">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option> - <option value="file13">codeberg.org/snonux/hexai/internal/llm/openai.go (85.5%)</option> + <option value="file13">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option> - <option value="file14">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option> + <option value="file14">codeberg.org/snonux/hexai/internal/llm/openai.go (85.5%)</option> - <option value="file15">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option> + <option value="file15">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option> - <option value="file16">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option> + <option value="file16">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option> - <option value="file17">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option> + <option value="file17">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option> - <option value="file18">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option> + <option value="file18">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option> - <option value="file19">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option> + <option value="file19">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option> - <option value="file20">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option> + <option value="file20">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option> - <option value="file21">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option> + <option value="file21">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option> - <option value="file22">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (81.9%)</option> + <option value="file22">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option> - <option value="file23">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.6%)</option> + <option value="file23">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (81.9%)</option> - <option value="file24">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option> + <option value="file24">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.6%)</option> - <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option> + <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option> - <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (55.6%)</option> + <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option> - <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.0%)</option> + <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (55.6%)</option> - <option value="file28">codeberg.org/snonux/hexai/internal/lsp/server.go (82.1%)</option> + <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.0%)</option> - <option value="file29">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option> + <option value="file29">codeberg.org/snonux/hexai/internal/lsp/server.go (82.1%)</option> - <option value="file30">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (60.0%)</option> + <option value="file30">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option> - <option value="file31">codeberg.org/snonux/hexai/internal/textutil/textutil.go (89.0%)</option> + <option value="file31">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (60.0%)</option> - <option value="file32">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option> + <option value="file32">codeberg.org/snonux/hexai/internal/textutil/textutil.go (89.0%)</option> + + <option value="file33">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option> </select> </div> @@ -142,7 +144,42 @@ </div> <div id="content"> - <pre class="file" id="file0" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp. + <pre class="file" id="file0" style="display: none">package main + +import ( + "context" + "flag" + "fmt" + "os" + + "codeberg.org/snonux/hexai/internal/hexaiaction" +) + +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") + tmuxPercent := flag.Int("tmux-percent", 33, "tmux split size percentage (1-100)") + flag.Parse() + + opts := hexaiaction.Options{ + Infile: *infile, Outfile: *outfile, + ForceTmux: *forceTmux, NoTmux: *noTmux, 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) + os.Exit(1) + }</span> +} + +</pre> + + <pre class="file" id="file1" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp. package main import ( @@ -169,7 +206,7 @@ func main() <span class="cov8" title="1">{ } </pre> - <pre class="file" id="file1" style="display: none">// Summary: Hexai CLI entrypoint; parses flags and delegates to internal/hexaicli. + <pre class="file" id="file2" style="display: none">// Summary: Hexai CLI entrypoint; parses flags and delegates to internal/hexaicli. package main import ( @@ -196,246 +233,6 @@ func main() <span class="cov8" title="1">{ } </pre> - <pre class="file" id="file2" style="display: none">package main - -import ( - "context" - "flag" - "fmt" - "io" - "os" - "path/filepath" - "time" - - "codeberg.org/snonux/hexai/internal/hexaiaction" - "codeberg.org/snonux/hexai/internal/tmux" - "golang.org/x/term" -) - -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") - // Tmux/UI flags - 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") - tmuxPercent := flag.Int("tmux-percent", 33, "tmux split size percentage (1-100)") - flag.Parse() - - // Child mode: run TUI and write atomically to -outfile - if *uiChild </span><span class="cov0" title="0">{ - if err := runChild(*infile, *outfile); err != nil </span><span class="cov0" title="0">{ - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - }</span> - <span class="cov0" title="0">return</span> - } - - // Parent mode: decide inline vs tmux - <span class="cov0" title="0">if shouldRunInTmux(*forceTmux, *noTmux) </span><span class="cov0" title="0">{ - if err := runInTmuxParent(*tmuxTarget, *tmuxSplit, *tmuxPercent); err != nil </span><span class="cov0" title="0">{ - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - }</span> - <span class="cov0" title="0">return</span> - } - - // Inline path: only if we have a TTY for UI; otherwise echo input - <span class="cov0" title="0">if isTTY(os.Stdout.Fd()) && isTTY(os.Stdin.Fd()) </span><span class="cov0" title="0">{ - in, out, closeIn, closeOut, err := openIO(*infile, *outfile) - if err != nil </span><span class="cov0" title="0">{ - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - }</span> - <span class="cov0" title="0">defer closeIn() - defer closeOut() - if err := hexaiactionRun(context.Background(), in, out, os.Stderr); err != nil </span><span class="cov0" title="0">{ - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - }</span> - <span class="cov0" title="0">return</span> - } - - // Fallback: no TTY and tmux not available; echo input to output - <span class="cov0" title="0">if err := echoThrough(*infile, *outfile); err != nil </span><span class="cov0" title="0">{ - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - }</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="cov10" title="6">{ - in := io.Reader(os.Stdin) - out := io.Writer(os.Stdout) - closeIn := func() </span>{<span class="cov0" title="0">}</span> - <span class="cov10" title="6">closeOut := func() </span>{<span class="cov1" title="1">}</span> - - <span class="cov10" title="6">if path := infile; path != "" </span><span class="cov9" title="5">{ - f, err := os.Open(path) - if err != nil </span><span class="cov1" title="1">{ - 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="cov7" title="4">in = f - closeIn = func() </span><span class="cov7" title="4">{ _ = f.Close() }</span> - } - <span class="cov9" title="5">if path := outfile; path != "" </span><span class="cov7" title="4">{ - f, err := os.Create(path) - if err != nil </span><span class="cov1" title="1">{ - 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> - } - <span class="cov7" title="4">return in, out, closeIn, closeOut, nil</span> -} - -// runChild runs the interactive flow and writes the final output atomically to outfile. -var hexaiactionRun = hexaiaction.Run - -func runChild(infile, outfile string) error <span class="cov6" title="3">{ - if outfile == "" </span><span class="cov1" title="1">{ - // No atomic handoff needed; just run normally to stdout - in, out, closeIn, closeOut, err := openIO(infile, "") - if err != nil </span><span class="cov0" title="0">{ - return err - }</span> - <span class="cov1" title="1">defer closeIn() - defer closeOut() - return hexaiactionRun(context.Background(), in, out, os.Stderr)</span> - } - <span class="cov4" title="2">tmp := outfile + ".tmp" - in, out, closeIn, closeOut, err := openIO(infile, tmp) - if err != nil </span><span class="cov0" title="0">{ - return err - }</span> - <span class="cov4" title="2">defer closeIn() - if err := hexaiactionRun(context.Background(), in, out, os.Stderr); err != nil </span><span class="cov1" title="1">{ - // On error, try to echo input to tmp to avoid blocking - closeOut() - if copyErr := echoThrough(infile, tmp); copyErr != nil </span><span class="cov0" title="0">{ - return fmt.Errorf("hexai-action child: %v; echo failed: %v", err, copyErr) - }</span> - } else<span class="cov1" title="1"> { - closeOut() - }</span> - <span class="cov4" title="2">return os.Rename(tmp, outfile)</span> -} - -var isTTYFn = isTTY -var tmuxAvailableFn = tmux.Available -var splitRunFn = tmux.SplitRun -var osExecutableFn = os.Executable - -func shouldRunInTmux(forceTmux, noTmux bool) bool <span class="cov9" title="5">{ - if noTmux </span><span class="cov1" title="1">{ - return false - }</span> - <span class="cov7" title="4">if forceTmux </span><span class="cov1" title="1">{ - return true - }</span> - // Auto: prefer tmux when stdio are not TTYs (Helix :pipe scenario) - <span class="cov6" title="3">if !(isTTYFn(os.Stdin.Fd()) && isTTYFn(os.Stdout.Fd())) && tmuxAvailableFn() </span><span class="cov1" title="1">{ - return true - }</span> - <span class="cov4" title="2">return false</span> -} - -func isTTY(fd uintptr) bool <span class="cov0" title="0">{ return term.IsTerminal(int(fd)) }</span> - -func runInTmuxParent(target, split string, percent int) error <span class="cov6" title="3">{ - // Prepare temp files - dir, err := os.MkdirTemp("", "hexai-action-") - if err != nil </span><span class="cov0" title="0">{ - return err - }</span> - <span class="cov6" title="3">defer func() </span><span class="cov6" title="3">{ _ = os.RemoveAll(dir) }</span>() - <span class="cov6" title="3">inPath := filepath.Join(dir, "input.txt") - outPath := filepath.Join(dir, "reply.txt") - // Read stdin and persist to inPath - if err := persistStdin(inPath); err != nil </span><span class="cov0" title="0">{ - return err - }</span> - // Build child argv - <span class="cov6" title="3">exe, err := osExecutableFn() - if err != nil </span><span class="cov1" title="1">{ - return err - }</span> - <span class="cov4" title="2">argv := []string{exe, "-ui-child", "-infile", inPath, "-outfile", outPath} - // Spawn tmux split - 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> - // Wait for outfile to appear - <span class="cov1" title="1">if err := waitForFile(outPath, 60*time.Second); err != nil </span><span class="cov0" title="0">{ - return err - }</span> - // Print to stdout - <span class="cov1" title="1">return catFileToStdout(outPath)</span> -} - -func persistStdin(path string) error <span class="cov9" title="5">{ - f, err := os.Create(path) - if err != nil </span><span class="cov1" title="1">{ - return err - }</span> - <span class="cov7" title="4">defer func() </span><span class="cov7" title="4">{ _ = f.Close() }</span>() - <span class="cov7" title="4">if _, err := io.Copy(f, os.Stdin); err != nil </span><span class="cov0" title="0">{ - return err - }</span> - <span class="cov7" title="4">return f.Sync()</span> -} - -func waitForFile(path string, timeout time.Duration) error <span class="cov6" title="3">{ - deadline := time.Now().Add(timeout) - for </span><span class="cov9" title="5">{ - if _, err := os.Stat(path); err == nil </span><span class="cov4" title="2">{ - return nil - }</span> - <span class="cov6" title="3">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">time.Sleep(200 * time.Millisecond)</span> - } -} - -func catFileToStdout(path string) error <span class="cov6" title="3">{ - f, err := os.Open(path) - if err != nil </span><span class="cov1" title="1">{ - return err - }</span> - <span class="cov4" title="2">defer func() </span><span class="cov4" title="2">{ _ = f.Close() }</span>() - <span class="cov4" title="2">_, err = io.Copy(os.Stdout, f) - return err</span> -} - -func echoThrough(infile, outfile string) error <span class="cov7" title="4">{ - // Read from infile or stdin and write to outfile or stdout - var in io.Reader = os.Stdin - var out io.Writer = os.Stdout - if infile != "" </span><span class="cov6" title="3">{ - f, err := os.Open(infile) - if err != nil </span><span class="cov0" title="0">{ - return err - }</span> - <span class="cov6" title="3">defer func() </span><span class="cov6" title="3">{ _ = f.Close() }</span>() - <span class="cov6" title="3">in = f</span> - } - <span class="cov7" title="4">if outfile != "" </span><span class="cov6" title="3">{ - f, err := os.Create(outfile) - if err != nil </span><span class="cov1" title="1">{ - return err - }</span> - <span class="cov4" title="2">defer func() </span><span class="cov4" title="2">{ _ = f.Close() }</span>() - <span class="cov4" title="2">out = f</span> - } - <span class="cov6" title="3">_, err := io.Copy(out, in) - return err</span> -} -</pre> - <pre class="file" id="file3" style="display: none">// Summary: Application configuration model and loader; reads ~/.config/hexai/config.toml and merges defaults. package appconfig @@ -1268,6 +1065,176 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="9">{ <pre class="file" id="file4" style="display: none">package hexaiaction import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "time" + + "codeberg.org/snonux/hexai/internal/tmux" + "golang.org/x/term" +) + +// Options configures the command-line orchestration for hexai-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="cov0" title="0">{ + if opts.UIChild </span><span class="cov0" title="0">{ + return runChild(ctx, opts.Infile, opts.Outfile, stdout, stderr) + }</span> + <span class="cov0" title="0">if shouldRunInTmux(opts.ForceTmux, opts.NoTmux) </span><span class="cov0" title="0">{ + 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="cov0" title="0">if isTTYFn(os.Stdout.Fd()) && 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="cov0" title="0">return echoThrough(opts.Infile, opts.Outfile, stdin, stdout)</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="5">{ + if noTmux </span><span class="cov1" title="1">{ return false }</span> + <span class="cov8" title="4">if forceTmux </span><span class="cov1" title="1">{ return true }</span> + <span class="cov7" title="3">if !(isTTYFn(os.Stdin.Fd()) && isTTYFn(os.Stdout.Fd())) && 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="cov4" title="2">{ + in := io.Reader(os.Stdin) + out := io.Writer(os.Stdout) + closeIn := func() </span>{<span class="cov0" title="0">}</span> + <span class="cov4" title="2">closeOut := func() </span>{<span class="cov0" title="0">}</span> + <span class="cov4" title="2">if path := infile; path != "" </span><span class="cov4" title="2">{ + 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="cov4" title="2">in = f + closeIn = func() </span><span class="cov4" title="2">{ _ = f.Close() }</span> + } + <span class="cov4" title="2">if path := outfile; path != "" </span><span class="cov4" title="2">{ + 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="cov4" title="2">out = f + closeOut = func() </span><span class="cov4" title="2">{ _ = f.Close() }</span> + } + <span class="cov4" title="2">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="cov4" title="2">{ + if outfile == "" </span><span class="cov1" title="1">{ + // No atomic handoff needed; just run normally to provided stdout + var in io.Reader = os.Stdin + if infile != "" </span><span class="cov1" title="1">{ + f, err := os.Open(infile) + 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">in = f</span> + } + <span class="cov1" title="1">return runFn(ctx, in, stdout, stderr)</span> + } + <span class="cov1" title="1">tmp := outfile + ".tmp" + in, out, closeIn, closeOut, err := openIO(infile, tmp) + if err != nil </span><span class="cov0" title="0">{ return err }</span> + <span class="cov1" title="1">defer closeIn() + 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) + }</span> + } else<span class="cov1" title="1"> { + closeOut() + }</span> + <span class="cov1" title="1">return os.Rename(tmp, outfile)</span> +} + +func runInTmuxParent(stdin io.Reader, stdout io.Writer, target, split string, percent int) error <span class="cov7" title="3">{ + dir, err := os.MkdirTemp("", "hexai-action-") + if err != nil </span><span class="cov0" title="0">{ return err }</span> + <span class="cov7" title="3">defer func() </span><span class="cov7" title="3">{ _ = os.RemoveAll(dir) }</span>() + <span class="cov7" title="3">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="3">exe, err := osExecutableFn() + if err != nil </span><span class="cov1" title="1">{ return err }</span> + <span class="cov4" title="2">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="cov1" title="1">if err := waitForFile(outPath, 60*time.Second); err != nil </span><span class="cov0" title="0">{ return err }</span> + <span class="cov1" title="1">return catFileTo(stdout, outPath)</span> +} + +func persistStdin(path string, stdin io.Reader) error <span class="cov8" title="4">{ + f, err := os.Create(path) + if err != nil </span><span class="cov0" title="0">{ return err }</span> + <span class="cov8" title="4">defer func() </span><span class="cov8" title="4">{ _ = f.Close() }</span>() + <span class="cov8" title="4">if _, err := io.Copy(f, stdin); err != nil </span><span class="cov0" title="0">{ return err }</span> + <span class="cov8" title="4">return f.Sync()</span> +} + +func waitForFile(path string, timeout time.Duration) error <span class="cov4" title="2">{ + deadline := time.Now().Add(timeout) + for </span><span class="cov7" title="3">{ + if _, err := os.Stat(path); err == nil </span><span class="cov1" title="1">{ 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="cov1" title="1">time.Sleep(200 * time.Millisecond)</span> + } +} + +func catFileTo(w io.Writer, path string) error <span class="cov1" title="1">{ + f, err := os.Open(path) + 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">_, err = io.Copy(w, f) + return err</span> +} + +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">{ + f, err := os.Open(infile) + 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">in = f</span> + } + <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="cov4" title="2">_, err := io.Copy(out, in) + return err</span> +} +</pre> + + <pre class="file" id="file5" style="display: none">package hexaiaction + +import ( "bufio" "io" "strings" @@ -1336,7 +1303,7 @@ func ExtractInstruction(sel string) (string, string) <span class="cov10" title=" // helpers moved to textutil </pre> - <pre class="file" id="file5" style="display: none">package hexaiaction + <pre class="file" id="file6" style="display: none">package hexaiaction import ( "context" @@ -1429,7 +1396,7 @@ func timeout8s(parent context.Context) (context.Context, context.CancelFunc) <sp }</span> </pre> - <pre class="file" id="file6" style="display: none">package hexaiaction + <pre class="file" id="file7" style="display: none">package hexaiaction import ( "context" @@ -1505,7 +1472,7 @@ func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg a // client construction is shared via internal/llmutils </pre> - <pre class="file" id="file7" style="display: none">package hexaiaction + <pre class="file" id="file8" style="display: none">package hexaiaction import ( "fmt" @@ -1625,7 +1592,7 @@ func RunTUI() (ActionKind, error) <span class="cov0" title="0">{ } </pre> - <pre class="file" id="file8" style="display: none">package hexaiaction + <pre class="file" id="file9" style="display: none">package hexaiaction import ( "fmt" @@ -1662,7 +1629,7 @@ func (oneLineDelegate) Render(w io.Writer, m list.Model, index int, listItem lis } </pre> - <pre class="file" id="file9" style="display: none">// Summary: Hexai CLI runner; reads input, creates an LLM client, builds messages, + <pre class="file" id="file10" style="display: none">// Summary: Hexai CLI runner; reads input, creates an LLM client, builds messages, // streams or collects the model output, and prints a short summary to stderr. package hexaicli @@ -1814,7 +1781,7 @@ func newClientFromConfig(cfg appconfig.App) (llm.Client, error) <span class="cov }</span> </pre> - <pre class="file" id="file10" style="display: none">// Summary: Hexai LSP runner; configures logging, loads config, builds the LLM client, + <pre class="file" id="file11" style="display: none">// Summary: Hexai LSP runner; configures logging, loads config, builds the LLM client, // and constructs/runs the LSP server (with injectable factory for tests). package hexailsp @@ -1960,7 +1927,7 @@ func makeServerOptions(cfg appconfig.App, logContext bool, client llm.Client) ls }</span> </pre> - <pre class="file" id="file11" style="display: none">// Summary: GitHub Copilot client for chat and Codex-style code completion. + <pre class="file" id="file12" style="display: none">// Summary: GitHub Copilot client for chat and Codex-style code completion. package llm import ( @@ -2355,7 +2322,7 @@ func (c copilotClient) CodeCompletion(ctx context.Context, prompt string, suffix // (no streaming decoder needed; we parse whole body lines) </pre> - <pre class="file" id="file12" style="display: none">// Summary: Ollama client against a local server; supports chat responses and streaming via /api/chat. + <pre class="file" id="file13" style="display: none">// Summary: Ollama client against a local server; supports chat responses and streaming via /api/chat. package llm import ( @@ -2573,7 +2540,7 @@ func handleOllamaNon2xx(resp *http.Response, start time.Time) error <span class= } </pre> - <pre class="file" id="file13" style="display: none">// Summary: OpenAI client implementation for chat completions with optional streaming and detailed logging. + <pre class="file" id="file14" style="display: none">// Summary: OpenAI client implementation for chat completions with optional streaming and detailed logging. package llm import ( @@ -2876,7 +2843,7 @@ func parseOpenAIStream(resp *http.Response, start time.Time, onDelta func(string } </pre> - <pre class="file" id="file14" style="display: none">// Summary: LLM provider interfaces, request options, configuration, and factory to build a client from config. + <pre class="file" id="file15" style="display: none">// Summary: LLM provider interfaces, request options, configuration, and factory to build a client from config. package llm import ( @@ -2997,7 +2964,7 @@ func NewFromConfig(cfg Config, openAIAPIKey, copilotAPIKey string) (Client, erro } </pre> - <pre class="file" id="file15" style="display: none">package llm + <pre class="file" id="file16" style="display: none">package llm import "errors" @@ -3005,7 +2972,7 @@ import "errors" func nilStringErr(msg string) (string, error) <span class="cov10" title="2">{ return "", errors.New(msg) }</span> </pre> - <pre class="file" id="file16" style="display: none">package llmutils + <pre class="file" id="file17" style="display: none">package llmutils import ( "os" @@ -3042,7 +3009,7 @@ func NewClientFromApp(cfg appconfig.App) (llm.Client, error) <span class="cov10" </pre> - <pre class="file" id="file17" style="display: none">package logging + <pre class="file" id="file18" style="display: none">package logging // ChatLogger provides a structured way to log chat interactions. type ChatLogger struct { @@ -3073,7 +3040,7 @@ func (cl ChatLogger) LogStart(stream bool, model string, temp float64, maxTokens } </pre> - <pre class="file" id="file18" style="display: none">// Summary: ANSI-styled logging utilities with a bound standard logger and configurable preview truncation. + <pre class="file" id="file19" style="display: none">// Summary: ANSI-styled logging utilities with a bound standard logger and configurable preview truncation. package logging import ( @@ -3129,7 +3096,7 @@ func PreviewForLog(s string) string <span class="cov7" title="32">{ } </pre> - <pre class="file" id="file19" style="display: none">// Summary: Builds additional context snippets based on configured mode and truncates text by token heuristic. + <pre class="file" id="file20" style="display: none">// Summary: Builds additional context snippets based on configured mode and truncates text by token heuristic. package lsp import ( @@ -3215,7 +3182,7 @@ func truncateToApproxTokens(text string, maxTokens int) string <span class="cov8 } </pre> - <pre class="file" id="file20" style="display: none">// Summary: In-memory document model for the LSP; tracks text, lines, and applies edits. + <pre class="file" id="file21" style="display: none">// Summary: In-memory document model for the LSP; tracks text, lines, and applies edits. package lsp import ( @@ -3362,7 +3329,7 @@ func firstLine(s string) string <span class="cov8" title="25">{ } </pre> - <pre class="file" id="file21" style="display: none">// Summary: LSP JSON-RPC handlers; implements core methods and integrates with the LLM client when enabled. + <pre class="file" id="file22" style="display: none">// Summary: LSP JSON-RPC handlers; implements core methods and integrates with the LLM client when enabled. package lsp import ( @@ -3809,7 +3776,7 @@ func (s *Server) fallbackCompletionItems(docStr string) []CompletionItem <span c }</span> </pre> - <pre class="file" id="file22" style="display: none">// Summary: Code Action handlers and helpers split from handlers.go for clarity. + <pre class="file" id="file23" style="display: none">// Summary: Code Action handlers and helpers split from handlers.go for clarity. package lsp import ( @@ -4336,7 +4303,7 @@ func exportName(name string) string <span class="cov2" title="2">{ } </pre> - <pre class="file" id="file23" style="display: none">// Summary: Completion handlers split from handlers.go to reduce file size and isolate feature logic. + <pre class="file" id="file24" style="display: none">// Summary: Completion handlers split from handlers.go to reduce file size and isolate feature logic. package lsp import ( @@ -4731,7 +4698,7 @@ func (s *Server) postProcessCompletion(text string, leftOfCursor string, current } </pre> - <pre class="file" id="file24" style="display: none">// Summary: Document open/change/close and in-editor chat handlers split out of handlers.go. + <pre class="file" id="file25" style="display: none">// Summary: Document open/change/close and in-editor chat handlers split out of handlers.go. package lsp import ( @@ -5060,7 +5027,7 @@ func (s *Server) deferShowDocument(uri string, sel Range) <span class="cov1" tit } </pre> - <pre class="file" id="file25" style="display: none">// Summary: ExecuteCommand handler to support post-edit navigation (jump to generated test). + <pre class="file" id="file26" style="display: none">// Summary: ExecuteCommand handler to support post-edit navigation (jump to generated test). package lsp import ( @@ -5096,7 +5063,7 @@ func (s *Server) handleExecuteCommand(req Request) <span class="cov8" title="1"> } </pre> - <pre class="file" id="file26" style="display: none">// Summary: Initialization and lifecycle handlers split from handlers.go. + <pre class="file" id="file27" style="display: none">// Summary: Initialization and lifecycle handlers split from handlers.go. package lsp import ( @@ -5139,7 +5106,7 @@ func (s *Server) handleExit() <span class="cov0" title="0">{ }</span> </pre> - <pre class="file" id="file27" style="display: none">// Summary: Generic LSP helpers shared across handlers (LLM opts, prompts, text utils, counters). + <pre class="file" id="file28" style="display: none">// Summary: Generic LSP helpers shared across handlers (LLM opts, prompts, text utils, counters). package lsp import ( @@ -5602,7 +5569,7 @@ func collectSemicolonMarkers(line string, lineNum int) []TextEdit <span class="c } </pre> - <pre class="file" id="file28" style="display: none">// Summary: Minimal LSP server over stdio; manages documents, dispatches requests, and tracks stats. + <pre class="file" id="file29" style="display: none">// Summary: Minimal LSP server over stdio; manages documents, dispatches requests, and tracks stats. package lsp import ( @@ -5863,7 +5830,7 @@ func (s *Server) Run() error <span class="cov1" title="1">{ } </pre> - <pre class="file" id="file29" style="display: none">// Summary: LSP transport utilities to read and write JSON-RPC messages with Content-Length framing. + <pre class="file" id="file30" style="display: none">// Summary: LSP transport utilities to read and write JSON-RPC messages with Content-Length framing. package lsp import ( @@ -5931,7 +5898,7 @@ func (s *Server) writeMessage(v any) <span class="cov10" title="18">{ } </pre> - <pre class="file" id="file30" style="display: none">package testutil + <pre class="file" id="file31" style="display: none">package testutil // MultilineDocBlock returns a realistic multi-line documentation block. func MultilineDocBlock() string <span class="cov8" title="1">{ @@ -5959,7 +5926,7 @@ func MalformedJSON() string <span class="cov0" title="0">{ }</span> </pre> - <pre class="file" id="file31" style="display: none">package textutil + <pre class="file" id="file32" style="display: none">package textutil import "strings" @@ -6075,7 +6042,7 @@ func FindStrictInlineTag(line string) (text string, left, right int, ok bool) <s </pre> - <pre class="file" id="file32" style="display: none">package tmux + <pre class="file" id="file33" style="display: none">package tmux import ( "os" |
