summaryrefslogtreecommitdiff
path: root/docs/coverage.html
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-07 11:26:10 +0300
committerPaul Buetow <paul@buetow.org>2025-09-07 11:26:10 +0300
commit8889949ad3851bfbf36ff5b73128286d67c88201 (patch)
tree0f515ae6ee3da898dea113799c09e943f3e3f8fb /docs/coverage.html
parent7c0266e94378f6121719939c6d53915eb72eed3e (diff)
tiding up
Diffstat (limited to 'docs/coverage.html')
-rw-r--r--docs/coverage.html513
1 files changed, 412 insertions, 101 deletions
diff --git a/docs/coverage.html b/docs/coverage.html
index 6b80630..2003a0d 100644
--- a/docs/coverage.html
+++ b/docs/coverage.html
@@ -59,19 +59,19 @@
<option value="file1">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option>
- <option value="file2">codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go (0.0%)</option>
+ <option value="file2">codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go (69.3%)</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="file5">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (81.1%)</option>
+ <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (91.9%)</option>
- <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (33.3%)</option>
+ <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (48.7%)</option>
- <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (47.3%)</option>
+ <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option>
- <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (91.7%)</option>
+ <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option>
<option value="file9">codeberg.org/snonux/hexai/internal/hexaicli/run.go (78.8%)</option>
@@ -119,6 +119,8 @@
<option value="file31">codeberg.org/snonux/hexai/internal/textutil/textutil.go (89.0%)</option>
+ <option value="file32">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
+
</select>
</div>
<div id="legend">
@@ -198,18 +200,240 @@ func main() <span class="cov8" title="1">{
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">{
- if err := hexaiaction.Run(context.Background(), os.Stdin, os.Stdout, os.Stderr); err != nil </span><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.
@@ -300,7 +524,7 @@ type App struct {
}
// Constructor: defaults for App (kept first among functions)
-func newDefaultConfig() App <span class="cov5" title="13">{
+func newDefaultConfig() App <span class="cov5" title="14">{
// Coding-friendly default temperature across providers
// Users can override per provider in config.toml (including 0.0).
t := 0.2
@@ -351,17 +575,17 @@ func newDefaultConfig() App <span class="cov5" title="13">{
// Load reads configuration from a file and merges with defaults.
// It respects the XDG Base Directory Specification.
-func Load(logger *log.Logger) App <span class="cov5" title="12">{
+func Load(logger *log.Logger) App <span class="cov5" title="13">{
cfg := newDefaultConfig()
if logger == nil </span><span class="cov3" title="4">{
return cfg // Return defaults if no logger is provided (e.g. in tests)
}</span>
- <span class="cov4" title="8">configPath, err := getConfigPath()
+ <span class="cov4" title="9">configPath, err := getConfigPath()
if err != nil </span><span class="cov0" title="0">{
logger.Printf("%v", err)
// Even if config path cannot be resolved, still allow env overrides below.
- }</span> else<span class="cov4" title="8"> {
+ }</span> else<span class="cov4" title="9"> {
if fileCfg, err := loadFromFile(configPath, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov3" title="4">{
cfg.mergeWith(fileCfg)
}</span>
@@ -370,10 +594,10 @@ func Load(logger *log.Logger) App <span class="cov5" title="12">{
}
// Environment overrides (take precedence over file)
- <span class="cov4" title="8">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{
cfg.mergeWith(envCfg)
}</span>
- <span class="cov4" title="8">return cfg</span>
+ <span class="cov4" title="9">return cfg</span>
}
// Private helpers
@@ -644,13 +868,13 @@ func (fc *fileConfig) toApp() App <span class="cov3" title="4">{
<span class="cov3" title="4">return out</span>
}
-func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov4" title="9">{
+func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov4" title="10">{
b, err := os.ReadFile(path)
- if err != nil </span><span class="cov2" title="3">{
+ if err != nil </span><span class="cov3" title="4">{
if !os.IsNotExist(err) &amp;&amp; logger != nil </span><span class="cov0" title="0">{
logger.Printf("cannot open TOML config file %s: %v", path, err)
}</span>
- <span class="cov2" title="3">return nil, err</span>
+ <span class="cov3" title="4">return nil, err</span>
}
<span class="cov4" title="6">var tables fileConfig
@@ -868,33 +1092,33 @@ func (a *App) mergeProviderFields(other *App) <span class="cov5" title="14">{
}</span>
}
-func getConfigPath() (string, error) <span class="cov4" title="9">{
+func getConfigPath() (string, error) <span class="cov4" title="10">{
var configPath string
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" </span><span class="cov4" title="7">{
configPath = filepath.Join(xdgConfigHome, "hexai", "config.toml")
- }</span> else<span class="cov2" title="2"> {
+ }</span> else<span class="cov2" title="3"> {
home, err := os.UserHomeDir()
if err != nil </span><span class="cov0" title="0">{
return "", fmt.Errorf("cannot find user home directory: %v", err)
}</span>
- <span class="cov2" title="2">configPath = filepath.Join(home, ".config", "hexai", "config.toml")</span>
+ <span class="cov2" title="3">configPath = filepath.Join(home, ".config", "hexai", "config.toml")</span>
}
- <span class="cov4" title="9">return configPath, nil</span>
+ <span class="cov4" title="10">return configPath, nil</span>
}
// --- Environment overrides ---
// loadFromEnv constructs an App containing only fields set via HEXAI_* env vars.
// These values should take precedence over file config when merged.
-func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="8">{
+func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="9">{
var out App
var any bool
// helpers
- getenv := func(k string) string </span><span class="cov10" title="192">{ return strings.TrimSpace(os.Getenv(k)) }</span>
- <span class="cov4" title="8">parseInt := func(k string) (int, bool) </span><span class="cov7" title="56">{
+ getenv := func(k string) string </span><span class="cov10" title="216">{ return strings.TrimSpace(os.Getenv(k)) }</span>
+ <span class="cov4" title="9">parseInt := func(k string) (int, bool) </span><span class="cov7" title="63">{
v := getenv(k)
- if v == "" </span><span class="cov7" title="49">{
+ if v == "" </span><span class="cov7" title="56">{
return 0, false
}</span>
<span class="cov4" title="7">n, err := strconv.Atoi(v)
@@ -906,9 +1130,9 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="8">{
}
<span class="cov4" title="7">return n, true</span>
}
- <span class="cov4" title="8">parseFloatPtr := func(k string) (*float64, bool) </span><span class="cov6" title="32">{
+ <span class="cov4" title="9">parseFloatPtr := func(k string) (*float64, bool) </span><span class="cov7" title="36">{
v := getenv(k)
- if v == "" </span><span class="cov6" title="28">{
+ if v == "" </span><span class="cov6" title="32">{
return nil, false
}</span>
<span class="cov3" title="4">f, err := strconv.ParseFloat(v, 64)
@@ -921,43 +1145,43 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="8">{
<span class="cov3" title="4">return &amp;f, true</span>
}
- <span class="cov4" title="8">if n, ok := parseInt("HEXAI_MAX_TOKENS"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if n, ok := parseInt("HEXAI_MAX_TOKENS"); ok </span><span class="cov1" title="1">{
out.MaxTokens = n
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_CONTEXT_MODE"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_CONTEXT_MODE"); s != "" </span><span class="cov1" title="1">{
out.ContextMode = s
any = true
}</span>
- <span class="cov4" title="8">if n, ok := parseInt("HEXAI_CONTEXT_WINDOW_LINES"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if n, ok := parseInt("HEXAI_CONTEXT_WINDOW_LINES"); ok </span><span class="cov1" title="1">{
out.ContextWindowLines = n
any = true
}</span>
- <span class="cov4" title="8">if n, ok := parseInt("HEXAI_MAX_CONTEXT_TOKENS"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if n, ok := parseInt("HEXAI_MAX_CONTEXT_TOKENS"); ok </span><span class="cov1" title="1">{
out.MaxContextTokens = n
any = true
}</span>
- <span class="cov4" title="8">if n, ok := parseInt("HEXAI_LOG_PREVIEW_LIMIT"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if n, ok := parseInt("HEXAI_LOG_PREVIEW_LIMIT"); ok </span><span class="cov1" title="1">{
out.LogPreviewLimit = n
any = true
}</span>
- <span class="cov4" title="8">if n, ok := parseInt("HEXAI_MANUAL_INVOKE_MIN_PREFIX"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if n, ok := parseInt("HEXAI_MANUAL_INVOKE_MIN_PREFIX"); ok </span><span class="cov1" title="1">{
out.ManualInvokeMinPrefix = n
any = true
}</span>
- <span class="cov4" title="8">if n, ok := parseInt("HEXAI_COMPLETION_DEBOUNCE_MS"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if n, ok := parseInt("HEXAI_COMPLETION_DEBOUNCE_MS"); ok </span><span class="cov1" title="1">{
out.CompletionDebounceMs = n
any = true
}</span>
- <span class="cov4" title="8">if n, ok := parseInt("HEXAI_COMPLETION_THROTTLE_MS"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if n, ok := parseInt("HEXAI_COMPLETION_THROTTLE_MS"); ok </span><span class="cov1" title="1">{
out.CompletionThrottleMs = n
any = true
}</span>
- <span class="cov4" title="8">if f, ok := parseFloatPtr("HEXAI_CODING_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if f, ok := parseFloatPtr("HEXAI_CODING_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.CodingTemperature = f
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_TRIGGER_CHARACTERS"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_TRIGGER_CHARACTERS"); s != "" </span><span class="cov1" title="1">{
parts := strings.Split(s, ",")
out.TriggerCharacters = nil
for _, p := range parts </span><span class="cov2" title="3">{
@@ -967,19 +1191,19 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="8">{
}
<span class="cov1" title="1">any = true</span>
}
- <span class="cov4" title="8">if s := getenv("HEXAI_INLINE_OPEN"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_INLINE_OPEN"); s != "" </span><span class="cov0" title="0">{
out.InlineOpen = s
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_INLINE_CLOSE"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_INLINE_CLOSE"); s != "" </span><span class="cov0" title="0">{
out.InlineClose = s
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_CHAT_SUFFIX"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_CHAT_SUFFIX"); s != "" </span><span class="cov0" title="0">{
out.ChatSuffix = s
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_CHAT_PREFIXES"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_CHAT_PREFIXES"); s != "" </span><span class="cov0" title="0">{
parts := strings.Split(s, ",")
out.ChatPrefixes = nil
for _, p := range parts </span><span class="cov0" title="0">{
@@ -989,52 +1213,52 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="8">{
}
<span class="cov0" title="0">any = true</span>
}
- <span class="cov4" title="8">if s := getenv("HEXAI_PROVIDER"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_PROVIDER"); s != "" </span><span class="cov1" title="1">{
out.Provider = s
any = true
}</span>
// Provider-specific
- <span class="cov4" title="8">if s := getenv("HEXAI_OPENAI_BASE_URL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_OPENAI_BASE_URL"); s != "" </span><span class="cov1" title="1">{
out.OpenAIBaseURL = s
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_OPENAI_MODEL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_OPENAI_MODEL"); s != "" </span><span class="cov1" title="1">{
out.OpenAIModel = s
any = true
}</span>
- <span class="cov4" title="8">if f, ok := parseFloatPtr("HEXAI_OPENAI_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if f, ok := parseFloatPtr("HEXAI_OPENAI_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.OpenAITemperature = f
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_OLLAMA_BASE_URL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_OLLAMA_BASE_URL"); s != "" </span><span class="cov1" title="1">{
out.OllamaBaseURL = s
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_OLLAMA_MODEL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_OLLAMA_MODEL"); s != "" </span><span class="cov1" title="1">{
out.OllamaModel = s
any = true
}</span>
- <span class="cov4" title="8">if f, ok := parseFloatPtr("HEXAI_OLLAMA_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if f, ok := parseFloatPtr("HEXAI_OLLAMA_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.OllamaTemperature = f
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_COPILOT_BASE_URL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_COPILOT_BASE_URL"); s != "" </span><span class="cov1" title="1">{
out.CopilotBaseURL = s
any = true
}</span>
- <span class="cov4" title="8">if s := getenv("HEXAI_COPILOT_MODEL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if s := getenv("HEXAI_COPILOT_MODEL"); s != "" </span><span class="cov1" title="1">{
out.CopilotModel = s
any = true
}</span>
- <span class="cov4" title="8">if f, ok := parseFloatPtr("HEXAI_COPILOT_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov4" title="9">if f, ok := parseFloatPtr("HEXAI_COPILOT_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.CopilotTemperature = f
any = true
}</span>
- <span class="cov4" title="8">if !any </span><span class="cov4" title="7">{
+ <span class="cov4" title="9">if !any </span><span class="cov4" title="8">{
return nil
}</span>
<span class="cov1" title="1">return &amp;out</span>
@@ -1125,16 +1349,16 @@ import (
)
// Render performs simple {{var}} replacement like LSP.
-func Render(t string, vars map[string]string) string <span class="cov10" title="8">{ return textutil.RenderTemplate(t, vars) }</span>
+func Render(t string, vars map[string]string) string <span class="cov9" title="8">{ return textutil.RenderTemplate(t, vars) }</span>
// StripFences removes surrounding markdown code fences.
-func StripFences(s string) string <span class="cov10" title="8">{ return textutil.StripCodeFences(s) }</span>
+func StripFences(s string) string <span class="cov10" title="9">{ return textutil.StripCodeFences(s) }</span>
type chatDoer interface {
Chat(ctx context.Context, msgs []llm.Message, opts ...llm.RequestOption) (string, error)
}
-func runRewrite(ctx context.Context, cfg appconfig.App, client chatDoer, instruction, selection string) (string, error) <span class="cov4" title="2">{
+func runRewrite(ctx context.Context, cfg appconfig.App, client chatDoer, instruction, selection string) (string, error) <span class="cov3" title="2">{
sys := cfg.PromptCodeActionRewriteSystem
user := Render(cfg.PromptCodeActionRewriteUser, map[string]string{"instruction": instruction, "selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
@@ -1142,11 +1366,11 @@ func runRewrite(ctx context.Context, cfg appconfig.App, client chatDoer, instruc
func runDiagnostics(ctx context.Context, cfg appconfig.App, client chatDoer, diags []string, selection string) (string, error) <span class="cov1" title="1">{
var b strings.Builder
- for i, d := range diags </span><span class="cov4" title="2">{
+ for i, d := range diags </span><span class="cov3" title="2">{
if strings.TrimSpace(d) == "" </span><span class="cov0" title="0">{
continue</span>
}
- <span class="cov4" title="2">b.WriteString(strings.TrimSpace(d))
+ <span class="cov3" title="2">b.WriteString(strings.TrimSpace(d))
if i &lt; len(diags)-1 </span><span class="cov1" title="1">{
b.WriteString("\n")
}</span>
@@ -1156,47 +1380,47 @@ func runDiagnostics(ctx context.Context, cfg appconfig.App, client chatDoer, dia
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))</span>
}
-func runDocument(ctx context.Context, cfg appconfig.App, client chatDoer, selection string) (string, error) <span class="cov4" title="2">{
+func runDocument(ctx context.Context, cfg appconfig.App, client chatDoer, selection string) (string, error) <span class="cov3" title="2">{
sys := cfg.PromptCodeActionDocumentSystem
user := Render(cfg.PromptCodeActionDocumentUser, map[string]string{"selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}</span>
-func runGoTest(ctx context.Context, cfg appconfig.App, client chatDoer, funcCode string) (string, error) <span class="cov4" title="2">{
+func runGoTest(ctx context.Context, cfg appconfig.App, client chatDoer, funcCode string) (string, error) <span class="cov3" title="2">{
sys := cfg.PromptCodeActionGoTestSystem
user := Render(cfg.PromptCodeActionGoTestUser, map[string]string{"function": funcCode})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}</span>
-func runOnce(ctx context.Context, client chatDoer, sys, user string) (string, error) <span class="cov0" title="0">{
+func runOnce(ctx context.Context, client chatDoer, sys, user string) (string, error) <span class="cov1" title="1">{
msgs := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}}
txt, err := client.Chat(ctx, msgs)
if err != nil </span><span class="cov0" title="0">{
return "", err
}</span>
- <span class="cov0" title="0">return strings.TrimSpace(StripFences(txt)), nil</span>
+ <span class="cov1" title="1">return strings.TrimSpace(StripFences(txt)), nil</span>
}
-func runOnceWithOpts(ctx context.Context, client chatDoer, sys, user string, opts []llm.RequestOption) (string, error) <span class="cov9" title="7">{
+func runOnceWithOpts(ctx context.Context, client chatDoer, sys, user string, opts []llm.RequestOption) (string, error) <span class="cov8" title="7">{
msgs := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}}
txt, err := client.Chat(ctx, msgs, opts...)
if err != nil </span><span class="cov0" title="0">{
return "", err
}</span>
- <span class="cov9" title="7">return strings.TrimSpace(StripFences(txt)), nil</span>
+ <span class="cov8" title="7">return strings.TrimSpace(StripFences(txt)), nil</span>
}
// reqOptsFrom builds LLM request options similar to LSP behavior.
-func reqOptsFrom(cfg appconfig.App) []llm.RequestOption <span class="cov9" title="7">{
+func reqOptsFrom(cfg appconfig.App) []llm.RequestOption <span class="cov8" title="7">{
opts := []llm.RequestOption{llm.WithMaxTokens(cfg.MaxTokens)}
if cfg.CodingTemperature != nil </span><span class="cov5" title="3">{
opts = append(opts, llm.WithTemperature(*cfg.CodingTemperature))
}</span>
- <span class="cov9" title="7">return opts</span>
+ <span class="cov8" title="7">return opts</span>
}
// Timeout helpers to mirror LSP behavior.
-func timeout10s(parent context.Context) (context.Context, context.CancelFunc) <span class="cov4" title="2">{
+func timeout10s(parent context.Context) (context.Context, context.CancelFunc) <span class="cov3" title="2">{
return context.WithTimeout(parent, 10*time.Second)
}</span>
@@ -1220,11 +1444,11 @@ import (
)
// Run executes the hexai-action command flow.
-func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov0" title="0">{
+func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov1" title="1">{
logger := log.New(stderr, "hexai-action ", log.LstdFlags|log.Lmsgprefix)
cfg := appconfig.Load(logger)
client, err := llmutils.NewClientFromApp(cfg)
- if err != nil </span><span class="cov0" title="0">{
+ if err != nil </span><span class="cov1" title="1">{
fmt.Fprintf(stderr, logging.AnsiBase+"hexai-action: LLM disabled: %v"+logging.AnsiReset+"\n", err)
return err
}</span>
@@ -1298,9 +1522,9 @@ type item struct {
hotkey rune
}
-func (i item) Title() string <span class="cov0" title="0">{ return i.title }</span>
-func (i item) Description() string <span class="cov0" title="0">{ return i.desc }</span>
-func (i item) FilterValue() string <span class="cov1" title="1">{ return i.title }</span>
+func (i item) Title() string <span class="cov1" title="1">{ return i.title }</span>
+func (i item) Description() string <span class="cov1" title="1">{ return i.desc }</span>
+func (i item) FilterValue() string <span class="cov8" title="3">{ return i.title }</span>
type model struct {
list list.Model
@@ -1308,7 +1532,7 @@ type model struct {
done bool
}
-func newModel() model <span class="cov10" title="3">{
+func newModel() model <span class="cov10" title="4">{
items := []list.Item{
item{title: "Rewrite selection", desc: "", kind: ActionRewrite, hotkey: 'r'},
item{title: "Document code", desc: "", kind: ActionDocument, hotkey: 'c'},
@@ -1323,21 +1547,21 @@ func newModel() model <span class="cov10" title="3">{
return model{list: l}
}</span>
-func (m model) Init() tea.Cmd <span class="cov0" title="0">{ return nil }</span>
+func (m model) Init() tea.Cmd <span class="cov1" title="1">{ return nil }</span>
-func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) <span class="cov0" title="0">{
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) <span class="cov1" title="1">{
switch msg := msg.(type) </span>{
case tea.KeyMsg:<span class="cov0" title="0">
return handleKey(m, msg)</span>
- case tea.WindowSizeMsg:<span class="cov0" title="0">
+ case tea.WindowSizeMsg:<span class="cov1" title="1">
m.list.SetSize(msg.Width, msg.Height)</span>
}
- <span class="cov0" title="0">var cmd tea.Cmd
+ <span class="cov1" title="1">var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd</span>
}
-func handleKey(m model, msg tea.KeyMsg) (tea.Model, tea.Cmd) <span class="cov10" title="3">{
+func handleKey(m model, msg tea.KeyMsg) (tea.Model, tea.Cmd) <span class="cov8" title="3">{
raw := msg.String()
low := strings.ToLower(raw)
switch low </span>{
@@ -1377,11 +1601,11 @@ func handleKey(m model, msg tea.KeyMsg) (tea.Model, tea.Cmd) <span class="cov10"
<span class="cov1" title="1">return m, nil</span>
}
-func (m model) View() string <span class="cov0" title="0">{
+func (m model) View() string <span class="cov1" title="1">{
if m.done </span><span class="cov0" title="0">{
return ""
}</span>
- <span class="cov0" title="0">return m.list.View()</span>
+ <span class="cov1" title="1">return m.list.View()</span>
}
// RunTUI returns the chosen ActionKind.
@@ -1420,21 +1644,21 @@ var (
cursorStyle = lipgloss.NewStyle().Bold(true)
)
-func (oneLineDelegate) Height() int <span class="cov8" title="10">{ return 1 }</span>
-func (oneLineDelegate) Spacing() int <span class="cov10" title="16">{ return 0 }</span>
-func (oneLineDelegate) Update(tea.Msg, *list.Model) tea.Cmd <span class="cov0" title="0">{ return nil }</span>
-func (oneLineDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) <span class="cov1" title="1">{
+func (oneLineDelegate) Height() int <span class="cov8" title="14">{ return 1 }</span>
+func (oneLineDelegate) Spacing() int <span class="cov10" title="24">{ return 0 }</span>
+func (oneLineDelegate) Update(tea.Msg, *list.Model) tea.Cmd <span class="cov1" title="1">{ return nil }</span>
+func (oneLineDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) <span class="cov2" title="2">{
title := listItem.FilterValue()
hk := '?'
- if it, ok := listItem.(item); ok </span><span class="cov1" title="1">{
+ if it, ok := listItem.(item); ok </span><span class="cov2" title="2">{
hk = it.hotkey
}</span>
- <span class="cov1" title="1">hot := hotStyle.Render(fmt.Sprintf(" (%c)", hk))
+ <span class="cov2" title="2">hot := hotStyle.Render(fmt.Sprintf(" (%c)", hk))
cursor := " "
- if index == m.Index() </span><span class="cov1" title="1">{
+ if index == m.Index() </span><span class="cov2" title="2">{
cursor = cursorStyle.Render("&gt; ")
}</span>
- <span class="cov1" title="1">fmt.Fprintf(w, "%s%s%s", cursor, title, hot)</span>
+ <span class="cov2" title="2">fmt.Fprintf(w, "%s%s%s", cursor, title, hot)</span>
}
</pre>
@@ -2736,14 +2960,14 @@ type Config struct {
// NewFromConfig creates an LLM client using only the supplied configuration.
// The OpenAI API key is supplied separately and may be read from the environment
// by the caller; other environment-based configuration is not used.
-func NewFromConfig(cfg Config, openAIAPIKey, copilotAPIKey string) (Client, error) <span class="cov8" title="18">{
+func NewFromConfig(cfg Config, openAIAPIKey, copilotAPIKey string) (Client, error) <span class="cov8" title="19">{
p := strings.ToLower(strings.TrimSpace(cfg.Provider))
- if p == "" </span><span class="cov6" title="7">{
+ if p == "" </span><span class="cov6" title="8">{
p = "openai"
}</span>
- <span class="cov8" title="18">switch p </span>{
- case "openai":<span class="cov7" title="11">
- if strings.TrimSpace(openAIAPIKey) == "" </span><span class="cov4" title="4">{
+ <span class="cov8" title="19">switch p </span>{
+ case "openai":<span class="cov7" title="12">
+ if strings.TrimSpace(openAIAPIKey) == "" </span><span class="cov5" title="5">{
return nil, errors.New("missing OPENAI_API_KEY for provider openai")
}</span>
// Set coding-friendly default temperature if none provided
@@ -2792,7 +3016,7 @@ import (
)
// NewClientFromApp builds an llm.Client using app config and environment keys.
-func NewClientFromApp(cfg appconfig.App) (llm.Client, error) <span class="cov10" title="5">{
+func NewClientFromApp(cfg appconfig.App) (llm.Client, error) <span class="cov10" title="6">{
llmCfg := llm.Config{
Provider: cfg.Provider,
OpenAIBaseURL: cfg.OpenAIBaseURL,
@@ -2806,14 +3030,14 @@ func NewClientFromApp(cfg appconfig.App) (llm.Client, error) <span class="cov10"
CopilotTemperature: cfg.CopilotTemperature,
}
oaKey := os.Getenv("HEXAI_OPENAI_API_KEY")
- if strings.TrimSpace(oaKey) == "" </span><span class="cov8" title="4">{
+ if strings.TrimSpace(oaKey) == "" </span><span class="cov9" title="5">{
oaKey = os.Getenv("OPENAI_API_KEY")
}</span>
- <span class="cov10" title="5">cpKey := os.Getenv("HEXAI_COPILOT_API_KEY")
- if strings.TrimSpace(cpKey) == "" </span><span class="cov10" title="5">{
+ <span class="cov10" title="6">cpKey := os.Getenv("HEXAI_COPILOT_API_KEY")
+ if strings.TrimSpace(cpKey) == "" </span><span class="cov10" title="6">{
cpKey = os.Getenv("COPILOT_API_KEY")
}</span>
- <span class="cov10" title="5">return llm.NewFromConfig(llmCfg, oaKey, cpKey)</span>
+ <span class="cov10" title="6">return llm.NewFromConfig(llmCfg, oaKey, cpKey)</span>
}
</pre>
@@ -5752,26 +5976,26 @@ func RenderTemplate(t string, vars map[string]string) string <span class="cov8"
}
// StripCodeFences removes surrounding Markdown triple-backtick fences.
-func StripCodeFences(s string) string <span class="cov8" title="49">{
+func StripCodeFences(s string) string <span class="cov8" title="50">{
t := strings.TrimSpace(s)
if t == "" </span><span class="cov0" title="0">{
return t
}</span>
- <span class="cov8" title="49">lines := strings.Split(t, "\n")
+ <span class="cov8" title="50">lines := strings.Split(t, "\n")
start := 0
for start &lt; len(lines) &amp;&amp; strings.TrimSpace(lines[start]) == "" </span><span class="cov0" title="0">{
start++
}</span>
- <span class="cov8" title="49">end := len(lines) - 1
+ <span class="cov8" title="50">end := len(lines) - 1
for end &gt;= 0 &amp;&amp; strings.TrimSpace(lines[end]) == "" </span><span class="cov0" title="0">{
end--
}</span>
- <span class="cov8" title="49">if start &gt;= len(lines) || end &lt; 0 || start &gt; end </span><span class="cov0" title="0">{
+ <span class="cov8" title="50">if start &gt;= len(lines) || end &lt; 0 || start &gt; end </span><span class="cov0" title="0">{
return t
}</span>
- <span class="cov8" title="49">first := strings.TrimSpace(lines[start])
+ <span class="cov8" title="50">first := strings.TrimSpace(lines[start])
last := strings.TrimSpace(lines[end])
- if strings.HasPrefix(first, "```") &amp;&amp; last == "```" &amp;&amp; end &gt; start </span><span class="cov6" title="19">{
+ if strings.HasPrefix(first, "```") &amp;&amp; last == "```" &amp;&amp; end &gt; start </span><span class="cov6" title="20">{
inner := strings.Join(lines[start+1:end], "\n")
return inner
}</span>
@@ -5851,6 +6075,93 @@ func FindStrictInlineTag(line string) (text string, left, right int, ok bool) <s
</pre>
+ <pre class="file" id="file32" style="display: none">package tmux
+
+import (
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+)
+
+// Available reports whether tmux is available and we appear to be in a tmux session.
+func Available() bool <span class="cov2" title="2">{ return HasBinary() &amp;&amp; InSession() }</span>
+
+// HasBinary reports whether the tmux binary is on PATH.
+var lookPath = exec.LookPath
+var command = exec.Command
+
+func HasBinary() bool <span class="cov4" title="4">{ _, err := lookPath("tmux"); return err == nil }</span>
+
+// InSession reports whether we seem to be running inside a tmux session.
+func InSession() bool <span class="cov4" title="3">{ return strings.TrimSpace(os.Getenv("TMUX")) != "" }</span>
+
+// SplitOpts controls how a new pane is created for running a command.
+type SplitOpts struct {
+ Target string // optional pane target, e.g. ":."
+ Vertical bool // true =&gt; split vertically (-v); false =&gt; horizontally (-h)
+ Percent int // 1..100; 0 means use tmux default
+}
+
+// SplitRun splits the current tmux window and runs argv in the new pane.
+// It returns once tmux has launched the child process.
+func SplitRun(opts SplitOpts, argv []string) error <span class="cov1" title="1">{
+ if len(argv) == 0 </span><span class="cov0" title="0">{
+ return nil
+ }</span>
+ <span class="cov1" title="1">args := []string{"split-window"}
+ if opts.Vertical </span><span class="cov1" title="1">{
+ args = append(args, "-v")
+ }</span> else<span class="cov0" title="0"> {
+ args = append(args, "-h")
+ }</span>
+ <span class="cov1" title="1">if opts.Percent &gt; 0 &amp;&amp; opts.Percent &lt;= 100 </span><span class="cov1" title="1">{
+ args = append(args, "-p", strconv.Itoa(opts.Percent))
+ }</span>
+ <span class="cov1" title="1">if strings.TrimSpace(opts.Target) != "" </span><span class="cov1" title="1">{
+ args = append(args, "-t", opts.Target)
+ }</span>
+ // tmux takes a single command string. Use a conservative shell join.
+ <span class="cov1" title="1">cmdStr := shellJoin(argv)
+ args = append(args, cmdStr)
+ c := command("tmux", args...)
+ return c.Run()</span>
+}
+
+// shellJoin quotes argv elements for safe use in a single shell command string.
+// It avoids interpretation by wrapping in single quotes and escaping embedded single quotes.
+func shellJoin(argv []string) string <span class="cov1" title="1">{
+ out := make([]string, 0, len(argv))
+ for _, a := range argv </span><span class="cov4" title="4">{
+ if a == "" </span><span class="cov0" title="0">{
+ out = append(out, "''")
+ continue</span>
+ }
+ <span class="cov4" title="4">if isSafeBare(a) </span><span class="cov2" title="2">{
+ out = append(out, a)
+ continue</span>
+ }
+ // single-quote wrapping with escaped single quotes
+ // ' =&gt; '\'' (close, escaped quote, reopen)
+ <span class="cov2" title="2">esc := strings.ReplaceAll(a, "'", "'\\''")
+ out = append(out, "'"+esc+"'")</span>
+ }
+ <span class="cov1" title="1">return strings.Join(out, " ")</span>
+}
+
+// isSafeBare returns true if a contains only safe characters for bare words.
+func isSafeBare(s string) bool <span class="cov4" title="4">{
+ for i := 0; i &lt; len(s); i++ </span><span class="cov10" title="27">{
+ b := s[i]
+ if (b &gt;= 'a' &amp;&amp; b &lt;= 'z') || (b &gt;= 'A' &amp;&amp; b &lt;= 'Z') || (b &gt;= '0' &amp;&amp; b &lt;= '9') || b == '-' || b == '_' || b == '.' || b == '/' || b == ':' </span><span class="cov9" title="25">{
+ continue</span>
+ }
+ <span class="cov2" title="2">return false</span>
+ }
+ <span class="cov2" title="2">return true</span>
+}
+</pre>
+
</div>
</body>
<script>