summaryrefslogtreecommitdiff
path: root/docs/coverage.html
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-07 18:00:10 +0300
committerPaul Buetow <paul@buetow.org>2025-09-07 18:00:10 +0300
commitf57d63831d604d726685fe31494788e81f17900a (patch)
tree5ba8f0af9b47a7a5201b9b3f622e706c5c0cf546 /docs/coverage.html
parent3246ebcc5246ed357f45ac32234d5cd34922b9f3 (diff)
editor: remove prefilled text in temp files for custom prompts (TUI and CLI)
Diffstat (limited to 'docs/coverage.html')
-rw-r--r--docs/coverage.html642
1 files changed, 405 insertions, 237 deletions
diff --git a/docs/coverage.html b/docs/coverage.html
index fb5d655..8560a58 100644
--- a/docs/coverage.html
+++ b/docs/coverage.html
@@ -61,67 +61,69 @@
<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="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (90.6%)</option>
- <option value="file4">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (84.5%)</option>
+ <option value="file4">codeberg.org/snonux/hexai/internal/editor/editor.go (58.3%)</option>
- <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option>
+ <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (84.5%)</option>
- <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (91.9%)</option>
+ <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option>
- <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (71.8%)</option>
+ <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (85.0%)</option>
- <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option>
+ <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (67.3%)</option>
- <option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option>
+ <option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option>
- <option value="file10">codeberg.org/snonux/hexai/internal/hexaicli/run.go (78.8%)</option>
+ <option value="file10">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option>
- <option value="file11">codeberg.org/snonux/hexai/internal/hexailsp/run.go (92.5%)</option>
+ <option value="file11">codeberg.org/snonux/hexai/internal/hexaicli/run.go (88.4%)</option>
- <option value="file12">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option>
+ <option value="file12">codeberg.org/snonux/hexai/internal/hexailsp/run.go (92.5%)</option>
- <option value="file13">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option>
+ <option value="file13">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option>
- <option value="file14">codeberg.org/snonux/hexai/internal/llm/openai.go (85.5%)</option>
+ <option value="file14">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option>
- <option value="file15">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option>
+ <option value="file15">codeberg.org/snonux/hexai/internal/llm/openai.go (85.5%)</option>
- <option value="file16">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option>
+ <option value="file16">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option>
- <option value="file17">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option>
+ <option value="file17">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option>
- <option value="file18">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option>
+ <option value="file18">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option>
- <option value="file19">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option>
+ <option value="file19">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option>
- <option value="file20">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
+ <option value="file20">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option>
- <option value="file21">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option>
+ <option value="file21">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
- <option value="file22">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option>
+ <option value="file22">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option>
- <option value="file23">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (81.9%)</option>
+ <option value="file23">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option>
- <option value="file24">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.6%)</option>
+ <option value="file24">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (78.8%)</option>
- <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option>
+ <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.6%)</option>
- <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
+ <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option>
- <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option>
+ <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
- <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.0%)</option>
+ <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option>
- <option value="file29">codeberg.org/snonux/hexai/internal/lsp/server.go (82.1%)</option>
+ <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.0%)</option>
- <option value="file30">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option>
+ <option value="file30">codeberg.org/snonux/hexai/internal/lsp/server.go (82.6%)</option>
- <option value="file31">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option>
+ <option value="file31">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option>
- <option value="file32">codeberg.org/snonux/hexai/internal/textutil/textutil.go (89.0%)</option>
+ <option value="file32">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option>
- <option value="file33">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
+ <option value="file33">codeberg.org/snonux/hexai/internal/textutil/textutil.go (89.0%)</option>
+
+ <option value="file34">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
</select>
</div>
@@ -306,19 +308,21 @@ type App struct {
// Code actions
PromptCodeActionRewriteSystem string `json:"-" toml:"-"`
PromptCodeActionDiagnosticsSystem string `json:"-" toml:"-"`
- PromptCodeActionDocumentSystem string `json:"-" toml:"-"`
- PromptCodeActionRewriteUser string `json:"-" toml:"-"`
- PromptCodeActionDiagnosticsUser string `json:"-" toml:"-"`
- PromptCodeActionDocumentUser string `json:"-" toml:"-"`
- PromptCodeActionGoTestSystem string `json:"-" toml:"-"`
- PromptCodeActionGoTestUser string `json:"-" toml:"-"`
+ PromptCodeActionDocumentSystem string `json:"-" toml:"-"`
+ PromptCodeActionRewriteUser string `json:"-" toml:"-"`
+ PromptCodeActionDiagnosticsUser string `json:"-" toml:"-"`
+ PromptCodeActionDocumentUser string `json:"-" toml:"-"`
+ PromptCodeActionGoTestSystem string `json:"-" toml:"-"`
+ PromptCodeActionGoTestUser string `json:"-" toml:"-"`
+ PromptCodeActionSimplifySystem string `json:"-" toml:"-"`
+ PromptCodeActionSimplifyUser string `json:"-" toml:"-"`
// CLI
PromptCLIDefaultSystem string `json:"-" toml:"-"`
PromptCLIExplainSystem string `json:"-" toml:"-"`
}
// Constructor: defaults for App (kept first among functions)
-func newDefaultConfig() App <span class="cov5" title="16">{
+func newDefaultConfig() App <span class="cov5" title="19">{
// Coding-friendly default temperature across providers
// Users can override per provider in config.toml (including 0.0).
t := 0.2
@@ -359,8 +363,10 @@ func newDefaultConfig() App <span class="cov5" title="16">{
PromptCodeActionRewriteUser: "Instruction: {{instruction}}\n\nSelected code to transform:\n{{selection}}",
PromptCodeActionDiagnosticsUser: "Diagnostics to resolve (selection only):\n{{diagnostics}}\n\nSelected code:\n{{selection}}",
PromptCodeActionDocumentUser: "Add documentation comments to this code:\n{{selection}}",
- PromptCodeActionGoTestSystem: "You are a precise Go unit test generator. Given a Go function, write one or more Test* functions using the testing package. Do NOT include package or imports, only the test function(s). Prefer table-driven tests. Keep it minimal and idiomatic.",
- PromptCodeActionGoTestUser: "Function under test:\n{{function}}",
+ PromptCodeActionGoTestSystem: "You are a precise Go unit test generator. Given a Go function, write one or more Test* functions using the testing package. Do NOT include package or imports, only the test function(s). Prefer table-driven tests. Keep it minimal and idiomatic.",
+ PromptCodeActionGoTestUser: "Function under test:\n{{function}}",
+ PromptCodeActionSimplifySystem: "You are a precise code improvement engine. Simplify and improve the given code while preserving behavior. Return only the improved code with no prose or backticks.",
+ PromptCodeActionSimplifyUser: "Improve this code:\n{{selection}}",
PromptCLIDefaultSystem: "You are Hexai CLI. Default to very short, concise answers. If the user asks for commands, output only the commands (one per line) with no commentary or explanation. Only when the word 'explain' appears in the prompt, produce a verbose explanation.",
PromptCLIExplainSystem: "You are Hexai CLI. The user requested an explanation. Provide a clear, verbose explanation with reasoning and details. If commands are needed, include them with brief context.",
@@ -369,17 +375,17 @@ func newDefaultConfig() App <span class="cov5" title="16">{
// 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="15">{
+func Load(logger *log.Logger) App <span class="cov5" title="18">{
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="11">configPath, err := getConfigPath()
+ <span class="cov5" title="14">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="11"> {
+ }</span> else<span class="cov5" title="14"> {
if fileCfg, err := loadFromFile(configPath, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov3" title="4">{
cfg.mergeWith(fileCfg)
}</span>
@@ -388,10 +394,10 @@ func Load(logger *log.Logger) App <span class="cov5" title="15">{
}
// Environment overrides (take precedence over file)
- <span class="cov4" title="11">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{
cfg.mergeWith(envCfg)
}</span>
- <span class="cov4" title="11">return cfg</span>
+ <span class="cov5" title="14">return cfg</span>
}
// Private helpers
@@ -488,14 +494,16 @@ type sectionPromptsChat struct {
}
type sectionPromptsCodeAction struct {
- RewriteSystem string `toml:"rewrite_system"`
- DiagnosticsSystem string `toml:"diagnostics_system"`
- DocumentSystem string `toml:"document_system"`
- RewriteUser string `toml:"rewrite_user"`
- DiagnosticsUser string `toml:"diagnostics_user"`
- DocumentUser string `toml:"document_user"`
- GoTestSystem string `toml:"go_test_system"`
- GoTestUser string `toml:"go_test_user"`
+ RewriteSystem string `toml:"rewrite_system"`
+ DiagnosticsSystem string `toml:"diagnostics_system"`
+ DocumentSystem string `toml:"document_system"`
+ RewriteUser string `toml:"rewrite_user"`
+ DiagnosticsUser string `toml:"diagnostics_user"`
+ DocumentUser string `toml:"document_user"`
+ GoTestSystem string `toml:"go_test_system"`
+ GoTestUser string `toml:"go_test_user"`
+ SimplifySystem string `toml:"simplify_system"`
+ SimplifyUser string `toml:"simplify_user"`
}
type sectionPromptsCLI struct {
@@ -619,7 +627,7 @@ func (fc *fileConfig) toApp() App <span class="cov3" title="4">{
out.PromptChatSystem = fc.Prompts.Chat.System
}</span>
// code action
- <span class="cov3" title="4">if (fc.Prompts.CodeAction != sectionPromptsCodeAction{}) </span><span class="cov1" title="1">{
+ <span class="cov3" title="4">if (fc.Prompts.CodeAction != sectionPromptsCodeAction{}) </span><span class="cov1" title="1">{
if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionRewriteSystem = fc.Prompts.CodeAction.RewriteSystem
}</span>
@@ -641,10 +649,16 @@ func (fc *fileConfig) toApp() App <span class="cov3" title="4">{
<span class="cov1" title="1">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionGoTestSystem = fc.Prompts.CodeAction.GoTestSystem
}</span>
- <span class="cov1" title="1">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{
- out.PromptCodeActionGoTestUser = fc.Prompts.CodeAction.GoTestUser
- }</span>
- }
+ <span class="cov1" title="1">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{
+ out.PromptCodeActionGoTestUser = fc.Prompts.CodeAction.GoTestUser
+ }</span>
+ <span class="cov1" title="1">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{
+ out.PromptCodeActionSimplifySystem = fc.Prompts.CodeAction.SimplifySystem
+ }</span>
+ <span class="cov1" title="1">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{
+ out.PromptCodeActionSimplifyUser = fc.Prompts.CodeAction.SimplifyUser
+ }</span>
+ }
// cli
<span class="cov3" title="4">if (fc.Prompts.CLI != sectionPromptsCLI{}) </span><span class="cov1" title="1">{
if strings.TrimSpace(fc.Prompts.CLI.DefaultSystem) != "" </span><span class="cov1" title="1">{
@@ -662,13 +676,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="cov5" title="12">{
+func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov5" title="15">{
b, err := os.ReadFile(path)
- if err != nil </span><span class="cov3" title="6">{
+ if err != nil </span><span class="cov4" title="9">{
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="cov3" title="6">return nil, err</span>
+ <span class="cov4" title="9">return nil, err</span>
}
<span class="cov3" title="6">var tables fileConfig
@@ -843,9 +857,15 @@ func (a *App) mergePrompts(other *App) <span class="cov3" title="5">{
<span class="cov3" title="5">if strings.TrimSpace(other.PromptCodeActionGoTestSystem) != "" </span><span class="cov1" title="1">{
a.PromptCodeActionGoTestSystem = other.PromptCodeActionGoTestSystem
}</span>
- <span class="cov3" title="5">if strings.TrimSpace(other.PromptCodeActionGoTestUser) != "" </span><span class="cov1" title="1">{
- a.PromptCodeActionGoTestUser = other.PromptCodeActionGoTestUser
- }</span>
+ <span class="cov3" title="5">if strings.TrimSpace(other.PromptCodeActionGoTestUser) != "" </span><span class="cov1" title="1">{
+ a.PromptCodeActionGoTestUser = other.PromptCodeActionGoTestUser
+ }</span>
+ <span class="cov3" title="5">if strings.TrimSpace(other.PromptCodeActionSimplifySystem) != "" </span><span class="cov0" title="0">{
+ a.PromptCodeActionSimplifySystem = other.PromptCodeActionSimplifySystem
+ }</span>
+ <span class="cov3" title="5">if strings.TrimSpace(other.PromptCodeActionSimplifyUser) != "" </span><span class="cov0" title="0">{
+ a.PromptCodeActionSimplifyUser = other.PromptCodeActionSimplifyUser
+ }</span>
// CLI
<span class="cov3" title="5">if strings.TrimSpace(other.PromptCLIDefaultSystem) != "" </span><span class="cov1" title="1">{
a.PromptCLIDefaultSystem = other.PromptCLIDefaultSystem
@@ -886,33 +906,33 @@ func (a *App) mergeProviderFields(other *App) <span class="cov5" title="14">{
}</span>
}
-func getConfigPath() (string, error) <span class="cov5" title="12">{
+func getConfigPath() (string, error) <span class="cov5" title="15">{
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="cov3" title="5"> {
+ }</span> else<span class="cov4" title="8"> {
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="cov3" title="5">configPath = filepath.Join(home, ".config", "hexai", "config.toml")</span>
+ <span class="cov4" title="8">configPath = filepath.Join(home, ".config", "hexai", "config.toml")</span>
}
- <span class="cov5" title="12">return configPath, nil</span>
+ <span class="cov5" title="15">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="11">{
+func loadFromEnv(logger *log.Logger) *App <span class="cov5" title="14">{
var out App
var any bool
// helpers
- getenv := func(k string) string </span><span class="cov10" title="264">{ return strings.TrimSpace(os.Getenv(k)) }</span>
- <span class="cov4" title="11">parseInt := func(k string) (int, bool) </span><span class="cov8" title="77">{
+ getenv := func(k string) string </span><span class="cov10" title="336">{ return strings.TrimSpace(os.Getenv(k)) }</span>
+ <span class="cov5" title="14">parseInt := func(k string) (int, bool) </span><span class="cov8" title="98">{
v := getenv(k)
- if v == "" </span><span class="cov7" title="70">{
+ if v == "" </span><span class="cov7" title="91">{
return 0, false
}</span>
<span class="cov4" title="7">n, err := strconv.Atoi(v)
@@ -924,9 +944,9 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="11">{
}
<span class="cov4" title="7">return n, true</span>
}
- <span class="cov4" title="11">parseFloatPtr := func(k string) (*float64, bool) </span><span class="cov7" title="44">{
+ <span class="cov5" title="14">parseFloatPtr := func(k string) (*float64, bool) </span><span class="cov7" title="56">{
v := getenv(k)
- if v == "" </span><span class="cov6" title="40">{
+ if v == "" </span><span class="cov7" title="52">{
return nil, false
}</span>
<span class="cov3" title="4">f, err := strconv.ParseFloat(v, 64)
@@ -939,43 +959,43 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="11">{
<span class="cov3" title="4">return &amp;f, true</span>
}
- <span class="cov4" title="11">if n, ok := parseInt("HEXAI_MAX_TOKENS"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if n, ok := parseInt("HEXAI_MAX_TOKENS"); ok </span><span class="cov1" title="1">{
out.MaxTokens = n
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_CONTEXT_MODE"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_CONTEXT_MODE"); s != "" </span><span class="cov1" title="1">{
out.ContextMode = s
any = true
}</span>
- <span class="cov4" title="11">if n, ok := parseInt("HEXAI_CONTEXT_WINDOW_LINES"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">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="11">if n, ok := parseInt("HEXAI_MAX_CONTEXT_TOKENS"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">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="11">if n, ok := parseInt("HEXAI_LOG_PREVIEW_LIMIT"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">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="11">if n, ok := parseInt("HEXAI_MANUAL_INVOKE_MIN_PREFIX"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">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="11">if n, ok := parseInt("HEXAI_COMPLETION_DEBOUNCE_MS"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">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="11">if n, ok := parseInt("HEXAI_COMPLETION_THROTTLE_MS"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">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="11">if f, ok := parseFloatPtr("HEXAI_CODING_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if f, ok := parseFloatPtr("HEXAI_CODING_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.CodingTemperature = f
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_TRIGGER_CHARACTERS"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">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">{
@@ -985,19 +1005,19 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="11">{
}
<span class="cov1" title="1">any = true</span>
}
- <span class="cov4" title="11">if s := getenv("HEXAI_INLINE_OPEN"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_INLINE_OPEN"); s != "" </span><span class="cov0" title="0">{
out.InlineOpen = s
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_INLINE_CLOSE"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_INLINE_CLOSE"); s != "" </span><span class="cov0" title="0">{
out.InlineClose = s
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_CHAT_SUFFIX"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_CHAT_SUFFIX"); s != "" </span><span class="cov0" title="0">{
out.ChatSuffix = s
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_CHAT_PREFIXES"); s != "" </span><span class="cov0" title="0">{
+ <span class="cov5" title="14">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">{
@@ -1007,59 +1027,131 @@ func loadFromEnv(logger *log.Logger) *App <span class="cov4" title="11">{
}
<span class="cov0" title="0">any = true</span>
}
- <span class="cov4" title="11">if s := getenv("HEXAI_PROVIDER"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_PROVIDER"); s != "" </span><span class="cov1" title="1">{
out.Provider = s
any = true
}</span>
// Provider-specific
- <span class="cov4" title="11">if s := getenv("HEXAI_OPENAI_BASE_URL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_OPENAI_BASE_URL"); s != "" </span><span class="cov1" title="1">{
out.OpenAIBaseURL = s
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_OPENAI_MODEL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_OPENAI_MODEL"); s != "" </span><span class="cov1" title="1">{
out.OpenAIModel = s
any = true
}</span>
- <span class="cov4" title="11">if f, ok := parseFloatPtr("HEXAI_OPENAI_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if f, ok := parseFloatPtr("HEXAI_OPENAI_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.OpenAITemperature = f
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_OLLAMA_BASE_URL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_OLLAMA_BASE_URL"); s != "" </span><span class="cov1" title="1">{
out.OllamaBaseURL = s
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_OLLAMA_MODEL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_OLLAMA_MODEL"); s != "" </span><span class="cov1" title="1">{
out.OllamaModel = s
any = true
}</span>
- <span class="cov4" title="11">if f, ok := parseFloatPtr("HEXAI_OLLAMA_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if f, ok := parseFloatPtr("HEXAI_OLLAMA_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.OllamaTemperature = f
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_COPILOT_BASE_URL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_COPILOT_BASE_URL"); s != "" </span><span class="cov1" title="1">{
out.CopilotBaseURL = s
any = true
}</span>
- <span class="cov4" title="11">if s := getenv("HEXAI_COPILOT_MODEL"); s != "" </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if s := getenv("HEXAI_COPILOT_MODEL"); s != "" </span><span class="cov1" title="1">{
out.CopilotModel = s
any = true
}</span>
- <span class="cov4" title="11">if f, ok := parseFloatPtr("HEXAI_COPILOT_TEMPERATURE"); ok </span><span class="cov1" title="1">{
+ <span class="cov5" title="14">if f, ok := parseFloatPtr("HEXAI_COPILOT_TEMPERATURE"); ok </span><span class="cov1" title="1">{
out.CopilotTemperature = f
any = true
}</span>
- <span class="cov4" title="11">if !any </span><span class="cov4" title="10">{
+ <span class="cov5" title="14">if !any </span><span class="cov4" title="13">{
return nil
}</span>
<span class="cov1" title="1">return &amp;out</span>
}
</pre>
- <pre class="file" id="file4" style="display: none">package hexaiaction
+ <pre class="file" id="file4" style="display: none">package editor
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+// Resolve returns the editor command from HEXAI_EDITOR or EDITOR.
+func Resolve() (string, error) <span class="cov10" title="5">{
+ ed := strings.TrimSpace(os.Getenv("HEXAI_EDITOR"))
+ if ed == "" </span><span class="cov1" title="1">{
+ ed = strings.TrimSpace(os.Getenv("EDITOR"))
+ }</span>
+ <span class="cov10" title="5">if ed == "" </span><span class="cov0" title="0">{
+ return "", errors.New("no editor configured (set HEXAI_EDITOR or EDITOR)")
+ }</span>
+ <span class="cov10" title="5">return ed, nil</span>
+}
+
+// RunEditor is the seam that invokes the editor on the given file path.
+// Override in tests to avoid launching a real editor.
+var RunEditor = func(editor, path string) error <span class="cov0" title="0">{
+ cmd := exec.Command(editor, path)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}</span>
+
+// OpenTempAndEdit creates a temporary .md file, writes initial content if provided,
+// opens it in the resolved editor, then reads the final content and removes the file.
+// Returns the trimmed content.
+func OpenTempAndEdit(initial []byte) (string, error) <span class="cov7" title="3">{
+ ed, err := Resolve()
+ if err != nil </span><span class="cov0" title="0">{
+ return "", err
+ }</span>
+ // Create temp file under system temp dir; ensure .md suffix
+ <span class="cov7" title="3">dir := os.TempDir()
+ f, err := os.CreateTemp(dir, "hexai-*.md")
+ if err != nil </span><span class="cov0" title="0">{
+ return "", err
+ }</span>
+ <span class="cov7" title="3">path := f.Name()
+ defer func() </span><span class="cov7" title="3">{ _ = os.Remove(path) }</span>()
+ <span class="cov7" title="3">if len(initial) &gt; 0 </span><span class="cov1" title="1">{
+ if _, err := f.Write(initial); err != nil </span><span class="cov0" title="0">{
+ _ = f.Close()
+ return "", err
+ }</span>
+ }
+ <span class="cov7" title="3">if err := f.Sync(); err != nil </span><span class="cov0" title="0">{
+ _ = f.Close()
+ return "", err
+ }</span>
+ <span class="cov7" title="3">if err := f.Close(); err != nil </span><span class="cov0" title="0">{
+ return "", err
+ }</span>
+ <span class="cov7" title="3">if err := RunEditor(ed, path); err != nil </span><span class="cov0" title="0">{
+ return "", err
+ }</span>
+ <span class="cov7" title="3">b, err := os.ReadFile(filepath.Clean(path))
+ if err != nil </span><span class="cov0" title="0">{
+ return "", err
+ }</span>
+ <span class="cov7" title="3">return strings.TrimSpace(string(b)), nil</span>
+}
+</pre>
+
+ <pre class="file" id="file5" style="display: none">package hexaiaction
import (
"context"
@@ -1210,7 +1302,7 @@ func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) erro
}
</pre>
- <pre class="file" id="file5" style="display: none">package hexaiaction
+ <pre class="file" id="file6" style="display: none">package hexaiaction
import (
"bufio"
@@ -1229,26 +1321,26 @@ import (
// &lt;rest is selection/code&gt;
//
// If the header is absent, the entire input is treated as selection.
-func ParseInput(r io.Reader) (InputParts, error) <span class="cov7" title="4">{
+func ParseInput(r io.Reader) (InputParts, error) <span class="cov7" title="5">{
b, err := io.ReadAll(bufio.NewReader(r))
if err != nil </span><span class="cov0" title="0">{
return InputParts{}, err
}</span>
- <span class="cov7" title="4">raw := strings.TrimSpace(string(b))
+ <span class="cov7" title="5">raw := strings.TrimSpace(string(b))
if raw == "" </span><span class="cov0" title="0">{
return InputParts{Selection: ""}, nil
}</span>
- <span class="cov7" title="4">lines := strings.Split(raw, "\n")
+ <span class="cov7" title="5">lines := strings.Split(raw, "\n")
// find a case-insensitive line equal to "diagnostics:"
diagsIdx := -1
- for i, ln := range lines </span><span class="cov7" title="5">{
+ for i, ln := range lines </span><span class="cov8" title="6">{
t := strings.TrimSpace(strings.ToLower(ln))
if t == "diagnostics:" </span><span class="cov1" title="1">{
diagsIdx = i
break</span>
}
}
- <span class="cov7" title="4">if diagsIdx &lt; 0 </span><span class="cov5" title="3">{
+ <span class="cov7" title="5">if diagsIdx &lt; 0 </span><span class="cov7" title="4">{
return InputParts{Selection: raw}, nil
}</span>
// collect diagnostics until a blank line or EOF
@@ -1281,7 +1373,7 @@ func ExtractInstruction(sel string) (string, string) <span class="cov10" title="
// helpers moved to textutil
</pre>
- <pre class="file" id="file6" style="display: none">package hexaiaction
+ <pre class="file" id="file7" style="display: none">package hexaiaction
import (
"context"
@@ -1294,16 +1386,16 @@ import (
)
// Render performs simple {{var}} replacement like LSP.
-func Render(t string, vars map[string]string) string <span class="cov9" title="9">{ return textutil.RenderTemplate(t, vars) }</span>
+func Render(t string, vars map[string]string) string <span class="cov9" title="10">{ return textutil.RenderTemplate(t, vars) }</span>
// StripFences removes surrounding markdown code fences.
-func StripFences(s string) string <span class="cov10" title="10">{ return textutil.StripCodeFences(s) }</span>
+func StripFences(s string) string <span class="cov10" title="11">{ 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="cov5" title="3">{
+func runRewrite(ctx context.Context, cfg appconfig.App, client chatDoer, instruction, selection string) (string, error) <span class="cov6" title="4">{
sys := cfg.PromptCodeActionRewriteSystem
user := Render(cfg.PromptCodeActionRewriteUser, map[string]string{"instruction": instruction, "selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
@@ -1326,8 +1418,14 @@ func runDiagnostics(ctx context.Context, cfg appconfig.App, client chatDoer, dia
}
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})
+ sys := cfg.PromptCodeActionDocumentSystem
+ user := Render(cfg.PromptCodeActionDocumentUser, map[string]string{"selection": selection})
+ return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
+}</span>
+
+func runSimplify(ctx context.Context, cfg appconfig.App, client chatDoer, selection string) (string, error) <span class="cov0" title="0">{
+ sys := cfg.PromptCodeActionSimplifySystem
+ user := Render(cfg.PromptCodeActionSimplifyUser, map[string]string{"selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}</span>
@@ -1346,26 +1444,26 @@ func runOnce(ctx context.Context, client chatDoer, sys, user string) (string, er
<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="8">{
+func runOnceWithOpts(ctx context.Context, client chatDoer, sys, user string, opts []llm.RequestOption) (string, error) <span class="cov9" title="9">{
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="8">return strings.TrimSpace(StripFences(txt)), nil</span>
+ <span class="cov9" title="9">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="8">{
+func reqOptsFrom(cfg appconfig.App) []llm.RequestOption <span class="cov9" title="9">{
opts := []llm.RequestOption{llm.WithMaxTokens(cfg.MaxTokens)}
- if cfg.CodingTemperature != nil </span><span class="cov6" title="4">{
+ if cfg.CodingTemperature != nil </span><span class="cov7" title="5">{
opts = append(opts, llm.WithTemperature(*cfg.CodingTemperature))
}</span>
- <span class="cov9" title="8">return opts</span>
+ <span class="cov9" title="9">return opts</span>
}
// Timeout helpers to mirror LSP behavior.
-func timeout10s(parent context.Context) (context.Context, context.CancelFunc) <span class="cov5" title="3">{
+func timeout10s(parent context.Context) (context.Context, context.CancelFunc) <span class="cov6" title="4">{
return context.WithTimeout(parent, 10*time.Second)
}</span>
@@ -1374,7 +1472,7 @@ func timeout8s(parent context.Context) (context.Context, context.CancelFunc) <sp
}</span>
</pre>
- <pre class="file" id="file7" style="display: none">package hexaiaction
+ <pre class="file" id="file8" style="display: none">package hexaiaction
import (
"context"
@@ -1384,6 +1482,7 @@ import (
"strings"
"codeberg.org/snonux/hexai/internal/appconfig"
+ "codeberg.org/snonux/hexai/internal/editor"
"codeberg.org/snonux/hexai/internal/logging"
"codeberg.org/snonux/hexai/internal/llmutils"
)
@@ -1393,7 +1492,7 @@ import (
var chooseActionFn = RunTUI
var newClientFromApp = llmutils.NewClientFromApp
-func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov6" title="3">{
+func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov7" title="4">{
logger := log.New(stderr, "hexai-tmux-action ", log.LstdFlags|log.Lmsgprefix)
cfg := appconfig.Load(logger)
client, err := newClientFromApp(cfg)
@@ -1401,31 +1500,31 @@ func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <
fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err)
return err
}</span>
- <span class="cov4" title="2">parts, err := ParseInput(stdin)
+ <span class="cov6" title="3">parts, err := ParseInput(stdin)
if err != nil </span><span class="cov0" title="0">{
fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset)
return err
}</span>
- <span class="cov4" title="2">if strings.TrimSpace(parts.Selection) == "" </span><span class="cov0" title="0">{
+ <span class="cov6" title="3">if strings.TrimSpace(parts.Selection) == "" </span><span class="cov0" title="0">{
return fmt.Errorf("hexai-tmux-action: no input provided on stdin")
}</span>
- <span class="cov4" title="2">kind, err := chooseActionFn()
+ <span class="cov6" title="3">kind, err := chooseActionFn()
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
- <span class="cov4" title="2">out, err := executeAction(ctx, kind, parts, cfg, client, stderr)
+ <span class="cov6" title="3">out, err := executeAction(ctx, kind, parts, cfg, client, stderr)
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
- <span class="cov4" title="2">io.WriteString(stdout, out)
+ <span class="cov6" title="3">io.WriteString(stdout, out)
return nil</span>
}
-func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) <span class="cov10" title="6">{
- switch kind </span>{
- case ActionSkip:<span class="cov4" title="2">
- return parts.Selection, nil</span>
- case ActionRewrite:<span class="cov4" title="2">
+func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) <span class="cov10" title="7">{
+ switch kind </span>{
+ case ActionSkip:<span class="cov4" title="2">
+ return parts.Selection, nil</span>
+ case ActionRewrite:<span class="cov4" title="2">
instr, cleaned := ExtractInstruction(parts.Selection)
if strings.TrimSpace(instr) == "" </span><span class="cov0" title="0">{
fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset)
@@ -1438,23 +1537,37 @@ func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg a
cctx, cancel := timeout10s(ctx)
defer cancel()
return runDiagnostics(cctx, cfg, client, parts.Diagnostics, parts.Selection)</span>
- case ActionDocument:<span class="cov1" title="1">
- cctx, cancel := timeout10s(ctx)
- defer cancel()
- return runDocument(cctx, cfg, client, parts.Selection)</span>
- case ActionGoTest:<span class="cov1" title="1">
- cctx, cancel := timeout8s(ctx)
- defer cancel()
- return runGoTest(cctx, cfg, client, parts.Selection)</span>
- default:<span class="cov0" title="0">
- return parts.Selection, nil</span>
- }
+ case ActionDocument:<span class="cov1" title="1">
+ cctx, cancel := timeout10s(ctx)
+ defer cancel()
+ return runDocument(cctx, cfg, client, parts.Selection)</span>
+ case ActionGoTest:<span class="cov1" title="1">
+ cctx, cancel := timeout8s(ctx)
+ defer cancel()
+ return runGoTest(cctx, cfg, client, parts.Selection)</span>
+ case ActionSimplify:<span class="cov0" title="0">
+ cctx, cancel := timeout10s(ctx)
+ defer cancel()
+ return runSimplify(cctx, cfg, client, parts.Selection)</span>
+ case ActionCustom:<span class="cov1" title="1">
+ cctx, cancel := timeout10s(ctx)
+ defer cancel()
+ // Open editor for free-form instruction
+ prompt, err := editor.OpenTempAndEdit(nil)
+ if err != nil || strings.TrimSpace(prompt) == "" </span><span class="cov0" title="0">{
+ fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: custom prompt canceled or empty; echoing input"+logging.AnsiReset)
+ return parts.Selection, nil
+ }</span>
+ <span class="cov1" title="1">return runRewrite(cctx, cfg, client, prompt, parts.Selection)</span>
+ default:<span class="cov0" title="0">
+ return parts.Selection, nil</span>
+ }
}
// client construction is shared via internal/llmutils
</pre>
- <pre class="file" id="file8" style="display: none">package hexaiaction
+ <pre class="file" id="file9" style="display: none">package hexaiaction
import (
"fmt"
@@ -1484,12 +1597,14 @@ type model struct {
func newModel() model <span class="cov10" title="4">{
items := []list.Item{
item{title: "Rewrite selection", desc: "", kind: ActionRewrite, hotkey: 'r'},
+ item{title: "Simplify and improve", desc: "", kind: ActionSimplify, hotkey: 'i'},
item{title: "Document code", desc: "", kind: ActionDocument, hotkey: 'c'},
item{title: "Generate Go unit test(s)", desc: "", kind: ActionGoTest, hotkey: 't'},
+ item{title: "Custom prompt", desc: "", kind: ActionCustom, hotkey: 'p'},
item{title: "Skip", desc: "", kind: ActionSkip, hotkey: 's'},
}
l := list.New(items, oneLineDelegate{}, 0, 0)
- l.Title = "Select Hexai Action"
+ l.SetShowTitle(false)
l.SetShowHelp(false)
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
@@ -1533,7 +1648,7 @@ func handleKey(m model, msg tea.KeyMsg) (tea.Model, tea.Cmd) <span class="cov8"
m.list.Select(0)</span>
case "end":<span class="cov0" title="0">
if n := len(m.list.Items()); n &gt; 0 </span><span class="cov0" title="0">{ m.list.Select(n - 1) }</span>
- case "s", "r", "c", "t":<span class="cov1" title="1">
+ case "s", "r", "c", "t", "i", "p":<span class="cov1" title="1">
items := m.list.Items()
for i := 0; i &lt; len(items); i++ </span><span class="cov1" title="1">{
if it, ok := items[i].(item); ok &amp;&amp; strings.ToLower(string(it.hotkey)) == low </span><span class="cov1" title="1">{
@@ -1574,7 +1689,7 @@ func RunTUI() (ActionKind, error) <span class="cov0" title="0">{
}
</pre>
- <pre class="file" id="file9" style="display: none">package hexaiaction
+ <pre class="file" id="file10" style="display: none">package hexaiaction
import (
"fmt"
@@ -1593,8 +1708,8 @@ var (
cursorStyle = lipgloss.NewStyle().Bold(true)
)
-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) Height() int <span class="cov8" title="18">{ return 1 }</span>
+func (oneLineDelegate) Spacing() int <span class="cov10" title="32">{ 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()
@@ -1611,7 +1726,7 @@ func (oneLineDelegate) Render(w io.Writer, m list.Model, index int, listItem lis
}
</pre>
- <pre class="file" id="file10" style="display: none">// Summary: Hexai CLI runner; reads input, creates an LLM client, builds messages,
+ <pre class="file" id="file11" 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
@@ -1626,6 +1741,7 @@ import (
"time"
"codeberg.org/snonux/hexai/internal/appconfig"
+ "codeberg.org/snonux/hexai/internal/editor"
"codeberg.org/snonux/hexai/internal/logging"
"codeberg.org/snonux/hexai/internal/llm"
"codeberg.org/snonux/hexai/internal/llmutils"
@@ -1633,28 +1749,36 @@ import (
// Run executes the Hexai CLI behavior given arguments and I/O streams.
// It assumes flags have already been parsed by the caller.
-func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov1" title="1">{
- // Load configuration with a logger so file-based config is respected.
- logger := log.New(stderr, "hexai ", log.LstdFlags|log.Lmsgprefix)
- cfg := appconfig.Load(logger)
- client, err := llmutils.NewClientFromApp(cfg)
+func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov6" title="3">{
+ // Load configuration with a logger so file-based config is respected.
+ logger := log.New(stderr, "hexai ", log.LstdFlags|log.Lmsgprefix)
+ cfg := appconfig.Load(logger)
+ client, err := newClientFromApp(cfg)
if err != nil </span><span class="cov1" title="1">{
fmt.Fprintf(stderr, logging.AnsiBase+"hexai: LLM disabled: %v"+logging.AnsiReset+"\n", err)
return err
}</span>
- // Inline the flow here to use configured CLI prompts.
- <span class="cov0" title="0">input, rerr := readInput(stdin, args)
+ // No args: open editor to capture a prompt, then combine with stdin as usual.
+ <span class="cov4" title="2">if len(args) == 0 </span><span class="cov1" title="1">{
+ if prompt, eerr := editor.OpenTempAndEdit(nil); eerr == nil &amp;&amp; strings.TrimSpace(prompt) != "" </span><span class="cov1" title="1">{
+ args = []string{prompt}
+ }</span> else <span class="cov0" title="0">{
+ // If editor fails or empty, continue; readInput will likely error if no stdin either.
+ }</span>
+ }
+ // Inline the flow here to use configured CLI prompts.
+ <span class="cov4" title="2">input, rerr := readInput(stdin, args)
if rerr != nil </span><span class="cov0" title="0">{
fmt.Fprintln(stderr, logging.AnsiBase+rerr.Error()+logging.AnsiReset)
return rerr
}</span>
- <span class="cov0" title="0">printProviderInfo(stderr, client)
+ <span class="cov4" title="2">printProviderInfo(stderr, client)
msgs := buildMessagesFromConfig(cfg, input)
if err := runChat(ctx, client, msgs, input, stdout, stderr); err != nil </span><span class="cov0" title="0">{
fmt.Fprintf(stderr, logging.AnsiBase+"hexai: error: %v"+logging.AnsiReset+"\n", err)
return err
}</span>
- <span class="cov0" title="0">return nil</span>
+ <span class="cov4" title="2">return nil</span>
}
// RunWithClient executes the CLI flow using an already-constructed client.
@@ -1675,19 +1799,19 @@ func RunWithClient(ctx context.Context, args []string, stdin io.Reader, stdout,
}
// readInput reads from stdin and args, then combines them per CLI rules.
-func readInput(stdin io.Reader, args []string) (string, error) <span class="cov9" title="5">{
+func readInput(stdin io.Reader, args []string) (string, error) <span class="cov10" title="7">{
var stdinData string
if fi, err := os.Stdin.Stat(); err == nil &amp;&amp; (fi.Mode()&amp;os.ModeCharDevice) == 0 </span><span class="cov7" title="4">{
b, _ := io.ReadAll(bufio.NewReader(stdin))
stdinData = strings.TrimSpace(string(b))
}</span>
- <span class="cov9" title="5">argData := strings.TrimSpace(strings.Join(args, " "))
+ <span class="cov10" title="7">argData := strings.TrimSpace(strings.Join(args, " "))
switch </span>{
case stdinData != "" &amp;&amp; argData != "":<span class="cov1" title="1">
return fmt.Sprintf("%s:\n\n%s", argData, stdinData), nil</span>
case stdinData != "":<span class="cov1" title="1">
return stdinData, nil</span>
- case argData != "":<span class="cov4" title="2">
+ case argData != "":<span class="cov7" title="4">
return argData, nil</span>
default:<span class="cov1" title="1">
return "", fmt.Errorf("hexai: no input provided; pass text as an argument or via stdin")</span>
@@ -1698,20 +1822,20 @@ func readInput(stdin io.Reader, args []string) (string, error) <span class="cov9
// client construction moved to internal/llmutils
// buildMessages creates system and user messages based on input content.
-func buildMessages(input string) []llm.Message <span class="cov10" title="6">{
+func buildMessages(input string) []llm.Message <span class="cov9" title="6">{
lower := strings.ToLower(input)
system := "You are Hexai CLI. Default to very short, concise answers. If the user asks for commands, output only the commands (one per line) with no commentary or explanation. Only when the word 'explain' appears in the prompt, produce a verbose explanation."
if strings.Contains(lower, "explain") </span><span class="cov1" title="1">{
system = "You are Hexai CLI. The user requested an explanation. Provide a clear, verbose explanation with reasoning and details. If commands are needed, include them with brief context."
}</span>
- <span class="cov10" title="6">return []llm.Message{
+ <span class="cov9" title="6">return []llm.Message{
{Role: "system", Content: system},
{Role: "user", Content: input},
}</span>
}
// buildMessagesFromConfig uses configured CLI system prompts.
-func buildMessagesFromConfig(cfg appconfig.App, input string) []llm.Message <span class="cov4" title="2">{
+func buildMessagesFromConfig(cfg appconfig.App, input string) []llm.Message <span class="cov7" title="4">{
lower := strings.ToLower(input)
system := cfg.PromptCLIDefaultSystem
if strings.Contains(lower, "explain") </span><span class="cov1" title="1">{
@@ -1719,51 +1843,52 @@ func buildMessagesFromConfig(cfg appconfig.App, input string) []llm.Message <spa
system = cfg.PromptCLIExplainSystem
}</span>
}
- <span class="cov4" title="2">return []llm.Message{
+ <span class="cov7" title="4">return []llm.Message{
{Role: "system", Content: system},
{Role: "user", Content: input},
}</span>
}
// runChat executes the chat request, handling streaming and summary output.
-func runChat(ctx context.Context, client llm.Client, msgs []llm.Message, input string, out io.Writer, errw io.Writer) error <span class="cov9" title="5">{
+func runChat(ctx context.Context, client llm.Client, msgs []llm.Message, input string, out io.Writer, errw io.Writer) error <span class="cov10" title="7">{
start := time.Now()
var output string
if s, ok := client.(llm.Streamer); ok </span><span class="cov4" title="2">{
var b strings.Builder
- if err := s.ChatStream(ctx, msgs, func(chunk string) </span><span class="cov9" title="5">{
+ if err := s.ChatStream(ctx, msgs, func(chunk string) </span><span class="cov8" title="5">{
b.WriteString(chunk)
fmt.Fprint(out, chunk)
}</span>); err != nil <span class="cov0" title="0">{
return err
}</span>
<span class="cov4" title="2">output = b.String()</span>
- } else<span class="cov6" title="3"> {
+ } else<span class="cov8" title="5"> {
txt, err := client.Chat(ctx, msgs)
if err != nil </span><span class="cov4" title="2">{
return err
}</span>
- <span class="cov1" title="1">output = txt
+ <span class="cov6" title="3">output = txt
fmt.Fprint(out, output)</span>
}
- <span class="cov6" title="3">dur := time.Since(start)
+ <span class="cov8" title="5">dur := time.Since(start)
fmt.Fprintf(errw, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d"+logging.AnsiReset+"\n",
client.Name(), client.DefaultModel(), dur.Round(time.Millisecond), len(input), len(output))
return nil</span>
}
// printProviderInfo writes the provider/model line to stderr.
-func printProviderInfo(errw io.Writer, client llm.Client) <span class="cov4" title="2">{
+func printProviderInfo(errw io.Writer, client llm.Client) <span class="cov7" title="4">{
fmt.Fprintf(errw, logging.AnsiBase+"provider=%s model=%s"+logging.AnsiReset+"\n", client.Name(), client.DefaultModel())
}</span>
// newClientFromConfig is kept for tests; delegates to llmutils.
-func newClientFromConfig(cfg appconfig.App) (llm.Client, error) <span class="cov4" title="2">{
- return llmutils.NewClientFromApp(cfg)
-}</span>
+var newClientFromApp = llmutils.NewClientFromApp
+
+// Backcompat for tests referencing the older helper name.
+func newClientFromConfig(cfg appconfig.App) (llm.Client, error) <span class="cov4" title="2">{ return newClientFromApp(cfg) }</span>
</pre>
- <pre class="file" id="file11" style="display: none">// Summary: Hexai LSP runner; configures logging, loads config, builds the LLM client,
+ <pre class="file" id="file12" 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
@@ -1905,11 +2030,13 @@ func makeServerOptions(cfg appconfig.App, logContext bool, client llm.Client) ls
PromptDocumentUser: cfg.PromptCodeActionDocumentUser,
PromptGoTestSystem: cfg.PromptCodeActionGoTestSystem,
PromptGoTestUser: cfg.PromptCodeActionGoTestUser,
+ PromptSimplifySystem: cfg.PromptCodeActionSimplifySystem,
+ PromptSimplifyUser: cfg.PromptCodeActionSimplifyUser,
}
}</span>
</pre>
- <pre class="file" id="file12" style="display: none">// Summary: GitHub Copilot client for chat and Codex-style code completion.
+ <pre class="file" id="file13" style="display: none">// Summary: GitHub Copilot client for chat and Codex-style code completion.
package llm
import (
@@ -2304,7 +2431,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="file13" style="display: none">// Summary: Ollama client against a local server; supports chat responses and streaming via /api/chat.
+ <pre class="file" id="file14" style="display: none">// Summary: Ollama client against a local server; supports chat responses and streaming via /api/chat.
package llm
import (
@@ -2522,7 +2649,7 @@ func handleOllamaNon2xx(resp *http.Response, start time.Time) error <span class=
}
</pre>
- <pre class="file" id="file14" style="display: none">// Summary: OpenAI client implementation for chat completions with optional streaming and detailed logging.
+ <pre class="file" id="file15" style="display: none">// Summary: OpenAI client implementation for chat completions with optional streaming and detailed logging.
package llm
import (
@@ -2825,7 +2952,7 @@ func parseOpenAIStream(resp *http.Response, start time.Time, onDelta func(string
}
</pre>
- <pre class="file" id="file15" style="display: none">// Summary: LLM provider interfaces, request options, configuration, and factory to build a client from config.
+ <pre class="file" id="file16" style="display: none">// Summary: LLM provider interfaces, request options, configuration, and factory to build a client from config.
package llm
import (
@@ -2883,8 +3010,8 @@ type Options struct {
type RequestOption func(*Options)
func WithModel(model string) RequestOption <span class="cov1" title="1">{ return func(o *Options) </span><span class="cov1" title="1">{ o.Model = model }</span> }
-func WithTemperature(t float64) RequestOption <span class="cov5" title="5">{ return func(o *Options) </span><span class="cov1" title="1">{ o.Temperature = t }</span> }
-func WithMaxTokens(n int) RequestOption <span class="cov10" title="33">{ return func(o *Options) </span><span class="cov1" title="1">{ o.MaxTokens = n }</span> }
+func WithTemperature(t float64) RequestOption <span class="cov5" title="6">{ return func(o *Options) </span><span class="cov1" title="1">{ o.Temperature = t }</span> }
+func WithMaxTokens(n int) RequestOption <span class="cov10" title="34">{ return func(o *Options) </span><span class="cov1" title="1">{ o.MaxTokens = n }</span> }
func WithStop(stop ...string) RequestOption <span class="cov1" title="1">{
return func(o *Options) </span><span class="cov1" title="1">{ o.Stop = append([]string{}, stop...) }</span>
}
@@ -2920,11 +3047,11 @@ func NewFromConfig(cfg Config, openAIAPIKey, copilotAPIKey string) (Client, erro
return nil, errors.New("missing OPENAI_API_KEY for provider openai")
}</span>
// Set coding-friendly default temperature if none provided
- <span class="cov6" title="7">if cfg.OpenAITemperature == nil </span><span class="cov5" title="5">{
+ <span class="cov5" title="7">if cfg.OpenAITemperature == nil </span><span class="cov5" title="5">{
t := 0.2
cfg.OpenAITemperature = &amp;t
}</span>
- <span class="cov6" title="7">return newOpenAI(cfg.OpenAIBaseURL, cfg.OpenAIModel, openAIAPIKey, cfg.OpenAITemperature), nil</span>
+ <span class="cov5" title="7">return newOpenAI(cfg.OpenAIBaseURL, cfg.OpenAIModel, openAIAPIKey, cfg.OpenAITemperature), nil</span>
case "ollama":<span class="cov3" title="3">
if cfg.OllamaTemperature == nil </span><span class="cov2" title="2">{
t := 0.2
@@ -2946,7 +3073,7 @@ func NewFromConfig(cfg Config, openAIAPIKey, copilotAPIKey string) (Client, erro
}
</pre>
- <pre class="file" id="file16" style="display: none">package llm
+ <pre class="file" id="file17" style="display: none">package llm
import "errors"
@@ -2954,7 +3081,7 @@ import "errors"
func nilStringErr(msg string) (string, error) <span class="cov10" title="2">{ return "", errors.New(msg) }</span>
</pre>
- <pre class="file" id="file17" style="display: none">package llmutils
+ <pre class="file" id="file18" style="display: none">package llmutils
import (
"os"
@@ -2991,7 +3118,7 @@ func NewClientFromApp(cfg appconfig.App) (llm.Client, error) <span class="cov10"
</pre>
- <pre class="file" id="file18" style="display: none">package logging
+ <pre class="file" id="file19" style="display: none">package logging
// ChatLogger provides a structured way to log chat interactions.
type ChatLogger struct {
@@ -3022,7 +3149,7 @@ func (cl ChatLogger) LogStart(stream bool, model string, temp float64, maxTokens
}
</pre>
- <pre class="file" id="file19" style="display: none">// Summary: ANSI-styled logging utilities with a bound standard logger and configurable preview truncation.
+ <pre class="file" id="file20" style="display: none">// Summary: ANSI-styled logging utilities with a bound standard logger and configurable preview truncation.
package logging
import (
@@ -3078,7 +3205,7 @@ func PreviewForLog(s string) string <span class="cov7" title="32">{
}
</pre>
- <pre class="file" id="file20" style="display: none">// Summary: Builds additional context snippets based on configured mode and truncates text by token heuristic.
+ <pre class="file" id="file21" style="display: none">// Summary: Builds additional context snippets based on configured mode and truncates text by token heuristic.
package lsp
import (
@@ -3164,7 +3291,7 @@ func truncateToApproxTokens(text string, maxTokens int) string <span class="cov8
}
</pre>
- <pre class="file" id="file21" style="display: none">// Summary: In-memory document model for the LSP; tracks text, lines, and applies edits.
+ <pre class="file" id="file22" style="display: none">// Summary: In-memory document model for the LSP; tracks text, lines, and applies edits.
package lsp
import (
@@ -3311,7 +3438,7 @@ func firstLine(s string) string <span class="cov8" title="25">{
}
</pre>
- <pre class="file" id="file22" style="display: none">// Summary: LSP JSON-RPC handlers; implements core methods and integrates with the LLM client when enabled.
+ <pre class="file" id="file23" style="display: none">// Summary: LSP JSON-RPC handlers; implements core methods and integrates with the LLM client when enabled.
package lsp
import (
@@ -3758,7 +3885,7 @@ func (s *Server) fallbackCompletionItems(docStr string) []CompletionItem <span c
}</span>
</pre>
- <pre class="file" id="file23" style="display: none">// Summary: Code Action handlers and helpers split from handlers.go for clarity.
+ <pre class="file" id="file24" style="display: none">// Summary: Code Action handlers and helpers split from handlers.go for clarity.
package lsp
import (
@@ -3791,24 +3918,42 @@ func (s *Server) handleCodeAction(req Request) <span class="cov3" title="3">{
}
<span class="cov1" title="1">sel := extractRangeText(d, p.Range)
- actions := make([]CodeAction, 0, 4)
+ actions := make([]CodeAction, 0, 5)
if a := s.buildRewriteCodeAction(p, sel); a != nil </span><span class="cov0" title="0">{
actions = append(actions, *a)
}</span>
<span class="cov1" title="1">if a := s.buildDiagnosticsCodeAction(p, sel); a != nil </span><span class="cov0" title="0">{
actions = append(actions, *a)
}</span>
- <span class="cov1" title="1">if a := s.buildDocumentCodeAction(p, sel); a != nil </span><span class="cov1" title="1">{
- actions = append(actions, *a)
- }</span>
- <span class="cov1" title="1">if a := s.buildGoUnitTestCodeAction(p); a != nil </span><span class="cov1" title="1">{
- actions = append(actions, *a)
- }</span>
+ <span class="cov1" title="1">if a := s.buildDocumentCodeAction(p, sel); a != nil </span><span class="cov1" title="1">{
+ actions = append(actions, *a)
+ }</span>
+ <span class="cov1" title="1">if a := s.buildGoUnitTestCodeAction(p); a != nil </span><span class="cov1" title="1">{
+ actions = append(actions, *a)
+ }</span>
+ <span class="cov1" title="1">if a := s.buildSimplifyCodeAction(p, sel); a != nil </span><span class="cov1" title="1">{
+ actions = append(actions, *a)
+ }</span>
<span class="cov1" title="1">if len(req.ID) != 0 </span><span class="cov1" title="1">{
s.reply(req.ID, actions, nil)
}</span>
}
+func (s *Server) buildSimplifyCodeAction(p CodeActionParams, sel string) *CodeAction <span class="cov1" title="1">{
+ if strings.TrimSpace(sel) == "" </span><span class="cov0" title="0">{
+ return nil
+ }</span>
+ <span class="cov1" title="1">payload := struct {
+ Type string `json:"type"`
+ URI string `json:"uri"`
+ Range Range `json:"range"`
+ Selection string `json:"selection"`
+ }{Type: "simplify", URI: p.TextDocument.URI, Range: p.Range, Selection: sel}
+ raw, _ := json.Marshal(payload)
+ ca := CodeAction{Title: "Hexai: simplify and improve", Kind: "refactor", Data: raw}
+ return &amp;ca</span>
+}
+
func (s *Server) buildRewriteCodeAction(p CodeActionParams, sel string) *CodeAction <span class="cov3" title="3">{
if instr, cleaned := instructionFromSelection(sel); strings.TrimSpace(instr) != "" </span><span class="cov1" title="1">{
payload := struct {
@@ -3857,7 +4002,7 @@ func (s *Server) resolveCodeAction(ca CodeAction) (CodeAction, bool) <span class
if err := json.Unmarshal(ca.Data, &amp;payload); err != nil </span><span class="cov0" title="0">{
return ca, false
}</span>
- <span class="cov5" title="12">switch payload.Type </span>{
+ <span class="cov5" title="12">switch payload.Type </span>{
case "rewrite":<span class="cov3" title="4">
sys := s.promptRewriteSystem
user := renderTemplate(s.promptRewriteUser, map[string]string{"instruction": payload.Instruction, "selection": payload.Selection})
@@ -3915,17 +4060,34 @@ func (s *Server) resolveCodeAction(ca CodeAction) (CodeAction, bool) <span class
} else<span class="cov0" title="0"> {
logging.Logf("lsp ", "codeAction document llm error: %v", err)
}</span>
- case "go_test":<span class="cov0" title="0">
- if edit, jumpURI, jumpRange, ok := s.resolveGoTest(payload.URI, payload.Range.Start); ok </span><span class="cov0" title="0">{
- ca.Edit = &amp;edit
- // After edit is applied, ask client to jump to new test function
- ca.Command = &amp;Command{Title: "Jump to generated test", Command: "hexai.showDocument", Arguments: []any{jumpURI, jumpRange}}
- // Also send a server-initiated showDocument shortly after resolve to cover
- // clients that do not execute commands from code actions.
- s.deferShowDocument(jumpURI, jumpRange)
- return ca, true
- }</span>
- }
+ case "go_test":<span class="cov0" title="0">
+ if edit, jumpURI, jumpRange, ok := s.resolveGoTest(payload.URI, payload.Range.Start); ok </span><span class="cov0" title="0">{
+ ca.Edit = &amp;edit
+ // After edit is applied, ask client to jump to new test function
+ ca.Command = &amp;Command{Title: "Jump to generated test", Command: "hexai.showDocument", Arguments: []any{jumpURI, jumpRange}}
+ // Also send a server-initiated showDocument shortly after resolve to cover
+ // clients that do not execute commands from code actions.
+ s.deferShowDocument(jumpURI, jumpRange)
+ return ca, true
+ }</span>
+ case "simplify":<span class="cov0" title="0">
+ sys := s.promptRewriteSystem
+ // Reuse rewrite user template with a fixed instruction
+ user := renderTemplate(s.promptRewriteUser, map[string]string{"instruction": "Simplify and improve the code while preserving behavior. Return only the improved code.", "selection": payload.Selection})
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ messages := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}}
+ opts := s.llmRequestOpts()
+ if text, err := s.llmClient.Chat(ctx, messages, opts...); err == nil </span><span class="cov0" title="0">{
+ if out := stripCodeFences(strings.TrimSpace(text)); out != "" </span><span class="cov0" title="0">{
+ edit := WorkspaceEdit{Changes: map[string][]TextEdit{payload.URI: {{Range: payload.Range, NewText: out}}}}
+ ca.Edit = &amp;edit
+ return ca, true
+ }</span>
+ } else<span class="cov0" title="0"> {
+ logging.Logf("lsp ", "codeAction simplify llm error: %v", err)
+ }</span>
+ }
<span class="cov0" title="0">return ca, false</span>
}
@@ -4285,7 +4447,7 @@ func exportName(name string) string <span class="cov2" title="2">{
}
</pre>
- <pre class="file" id="file24" style="display: none">// Summary: Completion handlers split from handlers.go to reduce file size and isolate feature logic.
+ <pre class="file" id="file25" style="display: none">// Summary: Completion handlers split from handlers.go to reduce file size and isolate feature logic.
package lsp
import (
@@ -4680,7 +4842,7 @@ func (s *Server) postProcessCompletion(text string, leftOfCursor string, current
}
</pre>
- <pre class="file" id="file25" style="display: none">// Summary: Document open/change/close and in-editor chat handlers split out of handlers.go.
+ <pre class="file" id="file26" style="display: none">// Summary: Document open/change/close and in-editor chat handlers split out of handlers.go.
package lsp
import (
@@ -5009,7 +5171,7 @@ func (s *Server) deferShowDocument(uri string, sel Range) <span class="cov1" tit
}
</pre>
- <pre class="file" id="file26" style="display: none">// Summary: ExecuteCommand handler to support post-edit navigation (jump to generated test).
+ <pre class="file" id="file27" style="display: none">// Summary: ExecuteCommand handler to support post-edit navigation (jump to generated test).
package lsp
import (
@@ -5045,7 +5207,7 @@ func (s *Server) handleExecuteCommand(req Request) <span class="cov8" title="1">
}
</pre>
- <pre class="file" id="file27" style="display: none">// Summary: Initialization and lifecycle handlers split from handlers.go.
+ <pre class="file" id="file28" style="display: none">// Summary: Initialization and lifecycle handlers split from handlers.go.
package lsp
import (
@@ -5088,7 +5250,7 @@ func (s *Server) handleExit() <span class="cov0" title="0">{
}</span>
</pre>
- <pre class="file" id="file28" style="display: none">// Summary: Generic LSP helpers shared across handlers (LLM opts, prompts, text utils, counters).
+ <pre class="file" id="file29" style="display: none">// Summary: Generic LSP helpers shared across handlers (LLM opts, prompts, text utils, counters).
package lsp
import (
@@ -5551,7 +5713,7 @@ func collectSemicolonMarkers(line string, lineNum int) []TextEdit <span class="c
}
</pre>
- <pre class="file" id="file29" style="display: none">// Summary: Minimal LSP server over stdio; manages documents, dispatches requests, and tracks stats.
+ <pre class="file" id="file30" style="display: none">// Summary: Minimal LSP server over stdio; manages documents, dispatches requests, and tracks stats.
package lsp
import (
@@ -5631,9 +5793,11 @@ type Server struct {
promptDocumentSystem string
promptRewriteUser string
promptDiagnosticsUser string
- promptDocumentUser string
- promptGoTestSystem string
- promptGoTestUser string
+ promptDocumentUser string
+ promptGoTestSystem string
+ promptGoTestUser string
+ promptSimplifySystem string
+ promptSimplifyUser string
}
// ServerOptions collects configuration for NewServer to avoid long parameter lists.
@@ -5672,8 +5836,10 @@ type ServerOptions struct {
PromptRewriteUser string
PromptDiagnosticsUser string
PromptDocumentUser string
- PromptGoTestSystem string
- PromptGoTestUser string
+ PromptGoTestSystem string
+ PromptGoTestUser string
+ PromptSimplifySystem string
+ PromptSimplifyUser string
}
func NewServer(r io.Reader, w io.Writer, logger *log.Logger, opts ServerOptions) *Server <span class="cov10" title="7">{
@@ -5752,9 +5918,11 @@ func NewServer(r io.Reader, w io.Writer, logger *log.Logger, opts ServerOptions)
s.promptDocumentSystem = opts.PromptDocumentSystem
s.promptRewriteUser = opts.PromptRewriteUser
s.promptDiagnosticsUser = opts.PromptDiagnosticsUser
- s.promptDocumentUser = opts.PromptDocumentUser
- s.promptGoTestSystem = opts.PromptGoTestSystem
- s.promptGoTestUser = opts.PromptGoTestUser
+ s.promptDocumentUser = opts.PromptDocumentUser
+ s.promptGoTestSystem = opts.PromptGoTestSystem
+ s.promptGoTestUser = opts.PromptGoTestUser
+ s.promptSimplifySystem = opts.PromptSimplifySystem
+ s.promptSimplifyUser = opts.PromptSimplifyUser
// Assign package-level inline trigger chars for free helper functions
if s.inlineOpen != "" </span><span class="cov10" title="7">{
@@ -5812,7 +5980,7 @@ func (s *Server) Run() error <span class="cov1" title="1">{
}
</pre>
- <pre class="file" id="file30" style="display: none">// Summary: LSP transport utilities to read and write JSON-RPC messages with Content-Length framing.
+ <pre class="file" id="file31" style="display: none">// Summary: LSP transport utilities to read and write JSON-RPC messages with Content-Length framing.
package lsp
import (
@@ -5880,7 +6048,7 @@ func (s *Server) writeMessage(v any) <span class="cov10" title="18">{
}
</pre>
- <pre class="file" id="file31" style="display: none">package testutil
+ <pre class="file" id="file32" style="display: none">package testutil
// MultilineDocBlock returns a realistic multi-line documentation block.
func MultilineDocBlock() string <span class="cov8" title="1">{
@@ -5908,47 +6076,47 @@ func MalformedJSON() string <span class="cov8" title="1">{
}</span>
</pre>
- <pre class="file" id="file32" style="display: none">package textutil
+ <pre class="file" id="file33" style="display: none">package textutil
import "strings"
// RenderTemplate performs simple {{var}} replacement in a template string.
-func RenderTemplate(t string, vars map[string]string) string <span class="cov8" title="45">{
+func RenderTemplate(t string, vars map[string]string) string <span class="cov8" title="46">{
if t == "" || len(vars) == 0 </span><span class="cov5" title="11">{
return t
}</span>
- <span class="cov7" title="34">out := t
- for k, v := range vars </span><span class="cov9" title="93">{
+ <span class="cov7" title="35">out := t
+ for k, v := range vars </span><span class="cov9" title="95">{
out = strings.ReplaceAll(out, "{{"+k+"}}", v)
}</span>
- <span class="cov7" title="34">return out</span>
+ <span class="cov7" title="35">return out</span>
}
// StripCodeFences removes surrounding Markdown triple-backtick fences.
-func StripCodeFences(s string) string <span class="cov8" title="51">{
+func StripCodeFences(s string) string <span class="cov8" title="52">{
t := strings.TrimSpace(s)
if t == "" </span><span class="cov0" title="0">{
return t
}</span>
- <span class="cov8" title="51">lines := strings.Split(t, "\n")
+ <span class="cov8" title="52">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="51">end := len(lines) - 1
+ <span class="cov8" title="52">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="51">if start &gt;= len(lines) || end &lt; 0 || start &gt; end </span><span class="cov0" title="0">{
+ <span class="cov8" title="52">if start &gt;= len(lines) || end &lt; 0 || start &gt; end </span><span class="cov0" title="0">{
return t
}</span>
- <span class="cov8" title="51">first := strings.TrimSpace(lines[start])
+ <span class="cov8" title="52">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="20">{
inner := strings.Join(lines[start+1:end], "\n")
return inner
}</span>
- <span class="cov7" title="31">return t</span>
+ <span class="cov7" title="32">return t</span>
}
// InstructionFromSelection extracts the first inline instruction and returns
@@ -6024,7 +6192,7 @@ func FindStrictInlineTag(line string) (text string, left, right int, ok bool) <s
</pre>
- <pre class="file" id="file33" style="display: none">package tmux
+ <pre class="file" id="file34" style="display: none">package tmux
import (
"os"