From 5e966f50111adf6e2cb2683fe588f6fe033fa931 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 6 Sep 2025 13:18:21 +0300 Subject: fix unit test coverage --- Magefile.go | 59 +- README.md | 58 +- docs/coverage.html | 2585 +++-- docs/coverage.out | 14688 ++++++++++++++++++++----- go.mod | 27 +- go.sum | 47 + internal/appconfig/config.go | 802 +- internal/appconfig/config_test.go | 164 +- internal/hexaicli/run.go | 116 +- internal/hexailsp/run.go | 68 +- internal/logging/logging_test.go | 58 +- internal/lsp/build_prompts_table_test.go | 36 +- internal/lsp/chat_prompt_test.go | 58 +- internal/lsp/codeaction_prompts_test.go | 171 +- internal/lsp/completion_messages_test.go | 19 +- internal/lsp/document_test.go | 52 +- internal/lsp/handlers.go | 112 +- internal/lsp/handlers_codeaction.go | 88 +- internal/lsp/handlers_completion.go | 70 +- internal/lsp/handlers_document.go | 6 +- internal/lsp/handlers_utils.go | 39 +- internal/lsp/helpers_more_test.go | 20 +- internal/lsp/provider_native_success_test.go | 44 +- internal/lsp/server.go | 126 +- internal/lsp/testhelper_capture_llm_test.go | 9 +- 25 files changed, 14524 insertions(+), 4998 deletions(-) diff --git a/Magefile.go b/Magefile.go index 6acc882..dedb72c 100644 --- a/Magefile.go +++ b/Magefile.go @@ -17,16 +17,16 @@ import ( ) var ( - Default = Build // Default target: build both binaries. - coverageThreshold float64 = 85 - coveragePrinted = make(chan struct{}, 1) + Default = Build // Default target: build all binaries. + coverageThreshold float64 = 85 + coveragePrinted = make(chan struct{}, 1) ) // Build builds the Hexai LSP and CLI binaries. func Build() error { - mg.Deps(BuildHexaiLSP, BuildHexaiCLI) - printCoverage() - return nil + mg.Deps(BuildHexaiLSP, BuildHexaiCLI, BuildHexaiAction) + printCoverage() + return nil } // BuildHexaiLSP builds the LSP server binary. @@ -37,18 +37,27 @@ func BuildHexaiLSP() error { // BuildHexaiCLI builds the CLI binary. func BuildHexaiCLI() error { - printCoverage() - return sh.RunV("go", "build", "-o", "hexai", "cmd/hexai/main.go") + printCoverage() + return sh.RunV("go", "build", "-o", "hexai", "cmd/hexai/main.go") +} + +// BuildHexaiAction builds the hexai-action TUI binary. +func BuildHexaiAction() error { + printCoverage() + return sh.RunV("go", "build", "-o", "hexai-action", "cmd/internal/hexai-action/main.go") } // Dev runs tests, vet, lint, then builds with race for both binaries. func Dev() error { - printCoverage() - mg.Deps(Test, Vet, Lint) - if err := sh.RunV("go", "build", "-race", "-o", "hexai-lsp", "cmd/hexai-lsp/main.go"); err != nil { - return err - } - return sh.RunV("go", "build", "-race", "-o", "hexai", "cmd/hexai/main.go") + printCoverage() + mg.Deps(Test, Vet, Lint) + if err := sh.RunV("go", "build", "-race", "-o", "hexai-lsp", "cmd/hexai-lsp/main.go"); err != nil { + return err + } + if err := sh.RunV("go", "build", "-race", "-o", "hexai", "cmd/hexai/main.go"); err != nil { + return err + } + return sh.RunV("go", "build", "-race", "-o", "hexai-action", "cmd/internal/hexai-action/main.go") } // Run launches the LSP server via go run (useful during development). @@ -68,8 +77,8 @@ func RunCLI() error { // Install copies built binaries to GOPATH/bin (defaults to ~/go/bin when GOPATH is unset). func Install() error { - printCoverage() - mg.Deps(Build) + printCoverage() + mg.Deps(Build) gopath := os.Getenv("GOPATH") if gopath == "" { home, err := os.UserHomeDir() @@ -82,10 +91,20 @@ func Install() error { if err := os.MkdirAll(bin, 0o755); err != nil { return err } - if err := sh.RunV("cp", "-v", "./hexai-lsp", bin+"/"); err != nil { - return err - } - return sh.RunV("cp", "-v", "./hexai", bin+"/") + if err := sh.RunV("cp", "-v", "./hexai-lsp", bin+"/"); err != nil { + return err + } + if err := sh.RunV("cp", "-v", "./hexai", bin+"/"); err != nil { + return err + } + return sh.RunV("cp", "-v", "./hexai-action", bin+"/") +} + +// RunAction runs the hexai-action TUI via go run (reads stdin). +func RunAction() error { + printCoverage() + mg.Deps(Dev) + return sh.RunV("go", "run", "cmd/internal/hexai-action/main.go") } // printCoverage prints a warning if an existing coverage profile shows total < coverateThreshold. diff --git a/README.md b/README.md index f9b9864..1486f4a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ It has got improved capabilities for Go code understanding (for example, create * LSP Code actions * LSP in-editor chat with the LLM * Stand-alone command line tool for LLM interaction +* TUI code-action runner (`hexai-action`) with Bubble Tea * Support for OpenAI, GitHub Copilot, and Ollama ## Documentation @@ -24,7 +25,7 @@ It has got improved capabilities for Go code understanding (for example, create Hexai uses Mage for developer tasks. Install Mage, then run targets like build, dev, test, and install. - Install Mage: `go install github.com/magefile/mage@latest` -- Build binaries: `mage build` (produces `hexai` and `hexai-lsp`) +- Build binaries: `mage build` (produces `hexai`, `hexai-lsp`, and `hexai-action`) - Dev build (+ tests, vet, lint): `mage dev` - Run tests: `mage test` - Run tests with coverage: `go test ./... -cover` @@ -40,3 +41,58 @@ Either use the Mage method as mentioned above, or install directly with: - CLI: `go install codeberg.org/snonux/hexai/cmd/hexai@latest` - LSP: `go install codeberg.org/snonux/hexai/cmd/hexai-lsp@latest` + +For `hexai-action`, use Mage or a local build: + +- Build locally: `go build -o hexai-action cmd/internal/hexai-action/main.go` +- Or via Mage: `mage buildHexaiAction` (or `mage build`) +- Install: `mage install` (copies `hexai-action` to `GOPATH/bin` together with other binaries) + +## Hexai Action (TUI) + +`hexai-action` is a small TUI to run Hexai code actions from stdin. It loads the same `config.toml` as `hexai` and `hexai-lsp` (XDG path: `~/.config/hexai/config.toml`), and respects the same environment overrides. + +- Pipe code (and optionally diagnostics) into the tool. +- Select an action with arrow keys, vi keys (`j/k`, `g/G`), Enter, or hotkeys `[s] [r] [d] [c] [t]`. +- The tool prints the transformed text to stdout. + +Input formats + +- Rewrite: include an inline instruction near the top of the selection using one of: + - `;do something;` + - `/* do something */` + - `` + - `// do something` (or `#`, `--`) + +- Diagnostics (optional block): + - Begin with a header line `Diagnostics:` (case-insensitive), one diagnostic per line, blank line, then the code selection. + +Examples + +- Rewrite selection: + +``` +;replace fmt.Println with log.Println; +package main + +import "fmt" + +func main() { fmt.Println("hi") } +``` + +- Diagnostics + selection: + +``` +Diagnostics: +missing return at end of function +use of undefined: foo + +func f() int { + foo() +} +``` + +Run: + +- `cat input.go | ./hexai-action` +- or `./hexai-action < input.go` diff --git a/docs/coverage.html b/docs/coverage.html index d22ef74..6b80630 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -55,53 +55,69 @@ @@ -136,11 +152,11 @@ import ( "codeberg.org/snonux/hexai/internal/hexailsp" ) -func main() { +func main() { logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)") showVersion := flag.Bool("version", false, "print version and exit") flag.Parse() - if *showVersion { + if *showVersion { log.Println(internal.Version) return } @@ -164,10 +180,10 @@ import ( "codeberg.org/snonux/hexai/internal/hexaicli" ) -func main() { +func main() { showVersion := flag.Bool("version", false, "print version and exit") flag.Parse() - if *showVersion { + if *showVersion { fmt.Fprintln(os.Stdout, internal.Version) return } @@ -178,7 +194,25 @@ func main() { } -