diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-17 18:52:51 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-17 18:52:51 +0300 |
| commit | 454451105ad3522d2ac3d22136eedee4a4d034af (patch) | |
| tree | aa4b5723809c4d45bfc9094a38c01c6415582f9c /internal/hexailsp/run.go | |
| parent | 498923e77c201ca90dc35c7934f4f7f1c9c3ccd2 (diff) | |
cli+lsp: refactor main packages into internal runners; add tests
- Move CLI logic to internal/hexaicli with Run/RunWithClient
- Move LSP logic to internal/hexailsp with Run/RunWithFactory
- Extract helpers; keep behavior identical for both binaries
- Add unit tests for hexaicli (input parsing, messages, streaming) and
hexailsp (factory wiring, client creation, logging settings)
- Add top-of-file summaries and 'Not yet reviewed by a human' comments to all Go files
- Update README with internal package docs
Diffstat (limited to 'internal/hexailsp/run.go')
| -rw-r--r-- | internal/hexailsp/run.go | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/internal/hexailsp/run.go b/internal/hexailsp/run.go new file mode 100644 index 0000000..2231fc2 --- /dev/null +++ b/internal/hexailsp/run.go @@ -0,0 +1,91 @@ +// Summary: Hexai LSP runner; configures logging, loads config, builds the LLM client, +// and constructs/runs the LSP server (with injectable factory for tests). +// Not yet reviewed by a human +package hexailsp + +import ( + "log" + "os" + "strings" + "io" + + "hexai/internal/appconfig" + "hexai/internal/llm" + "hexai/internal/logging" + "hexai/internal/lsp" +) + +// ServerRunner is the minimal interface satisfied by lsp.Server. +type ServerRunner interface{ Run() error } + +// ServerFactory creates a ServerRunner. Default uses lsp.NewServer. +type ServerFactory func(r io.Reader, w io.Writer, logger *log.Logger, opts lsp.ServerOptions) ServerRunner + +// Run configures logging, loads config, builds the LLM client and runs the LSP server. +// It is thin and delegates to RunWithFactory for testability. +func Run(logPath string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { + logger := log.New(stderr, "hexai-lsp ", log.LstdFlags|log.Lmsgprefix) + if strings.TrimSpace(logPath) != "" { + f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + logger.Fatalf("failed to open log file: %v", err) + } + defer f.Close() + logger.SetOutput(f) + } + logging.Bind(logger) + cfg := appconfig.Load(logger) + return RunWithFactory(logPath, stdin, stdout, logger, cfg, nil, nil) +} + +// RunWithFactory is the testable entrypoint. When client is nil, it is built from cfg+env. +// When factory is nil, lsp.NewServer is used. +func RunWithFactory(logPath string, stdin io.Reader, stdout io.Writer, logger *log.Logger, cfg appconfig.App, client llm.Client, factory ServerFactory) error { + // Normalize and apply logging config + cfg.ContextMode = strings.ToLower(strings.TrimSpace(cfg.ContextMode)) + if cfg.LogPreviewLimit >= 0 { + logging.SetLogPreviewLimit(cfg.LogPreviewLimit) + } + + // Build LLM client if not provided + if client == nil { + llmCfg := llm.Config{ + Provider: cfg.Provider, + OpenAIBaseURL: cfg.OpenAIBaseURL, + OpenAIModel: cfg.OpenAIModel, + OllamaBaseURL: cfg.OllamaBaseURL, + OllamaModel: cfg.OllamaModel, + CopilotBaseURL: cfg.CopilotBaseURL, + CopilotModel: cfg.CopilotModel, + } + oaKey := os.Getenv("OPENAI_API_KEY") + cpKey := os.Getenv("COPILOT_API_KEY") + if c, err := llm.NewFromConfig(llmCfg, oaKey, cpKey); err != nil { + logging.Logf("lsp ", "llm disabled: %v", err) + } else { + client = c + logging.Logf("lsp ", "llm enabled provider=%s model=%s", c.Name(), c.DefaultModel()) + } + } + + if factory == nil { + factory = func(r io.Reader, w io.Writer, logger *log.Logger, opts lsp.ServerOptions) ServerRunner { + return lsp.NewServer(r, w, logger, opts) + } + } + + server := factory(stdin, stdout, logger, lsp.ServerOptions{ + LogContext: strings.TrimSpace(logPath) != "", + MaxTokens: cfg.MaxTokens, + ContextMode: cfg.ContextMode, + WindowLines: cfg.ContextWindowLines, + MaxContextTokens: cfg.MaxContextTokens, + NoDiskIO: cfg.NoDiskIO, + Client: client, + TriggerCharacters: cfg.TriggerCharacters, + }) + if err := server.Run(); err != nil { + logger.Fatalf("server error: %v", err) + } + return nil +} |
