summaryrefslogtreecommitdiff
path: root/docs/coverage.html
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-07 11:39:36 +0300
committerPaul Buetow <paul@buetow.org>2025-09-07 11:39:36 +0300
commit9a901054e8828054f7b26514b72b6e938f97e4a7 (patch)
treec2f1419a5a18f3cc8c23b5a46713831ff043a964 /docs/coverage.html
parentfb06bfa9dc7140f987bdbad59467a84610221fbb (diff)
chore: prune legacy helper scripts in llminputs/
Diffstat (limited to 'docs/coverage.html')
-rw-r--r--docs/coverage.html573
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()) &amp;&amp; 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()) &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>
-}
-
-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()) &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="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()) &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="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"