diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-26 07:46:14 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-26 07:46:14 +0300 |
| commit | d0330d02ff040326216ab940a767490cb2de09ce (patch) | |
| tree | 36c73d4562630c7f56f6f3c8ae6f8f3d7e36f102 /internal/lsp | |
| parent | cff326a7e562eb1acc8027f129b3fd0035eb7d22 (diff) | |
Allow slash commands without prefix
Diffstat (limited to 'internal/lsp')
| -rw-r--r-- | internal/lsp/chat_commands_test.go | 39 | ||||
| -rw-r--r-- | internal/lsp/handlers_document.go | 42 |
2 files changed, 61 insertions, 20 deletions
diff --git a/internal/lsp/chat_commands_test.go b/internal/lsp/chat_commands_test.go index bedfaed..f9bd6a0 100644 --- a/internal/lsp/chat_commands_test.go +++ b/internal/lsp/chat_commands_test.go @@ -80,3 +80,42 @@ func TestHandleReloadCommandReloadsStore(t *testing.T) { t.Fatalf("expected summary logged, got %q", logBuf.String()) } } + +func TestDetectAndHandleChatExecutesSlashCommand(t *testing.T) { + tmp := t.TempDir() + configDir := filepath.Join(tmp, "hexai") + if err := os.MkdirAll(configDir, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + configPath := filepath.Join(configDir, "config.toml") + if err := os.WriteFile(configPath, []byte("[general]\nmax_tokens = 128\n"), 0o644); err != nil { + t.Fatalf("write config: %v", err) + } + t.Setenv("XDG_CONFIG_HOME", tmp) + t.Setenv("HEXAI_MAX_TOKENS", "") + + var logBuf bytes.Buffer + logger := log.New(&logBuf, "", 0) + + initial := appconfig.Load(logger) + store := runtimeconfig.New(initial) + + s := newTestServer() + s.logger = logger + s.configStore = store + var out bytes.Buffer + s.out = &out + + uri := "file:///cmd.go" + s.setDocument(uri, "/reload>\n") + + s.detectAndHandleChat(uri) + + outStr := out.String() + if !strings.Contains(outStr, "Reloaded config") { + t.Fatalf("expected reload summary in applyEdit payload, got %q", outStr) + } + if !strings.Contains(logBuf.String(), "Reloaded config") { + t.Fatalf("expected reload summary logged, got %q", logBuf.String()) + } +} diff --git a/internal/lsp/handlers_document.go b/internal/lsp/handlers_document.go index e82e683..69f482f 100644 --- a/internal/lsp/handlers_document.go +++ b/internal/lsp/handlers_document.go @@ -104,28 +104,37 @@ func (s *Server) detectAndHandleChat(uri string) { if j < 0 { continue } - // Check suffix/prefix according to configuration + // Check suffix and derive the prompt text before validating prefixes if suffix == "" { continue } - // Last non-space must equal suffix if string(raw[j]) != suffix { continue } - // Require at least one char before suffix and that char must be in chatPrefixes - if j < 1 { + removeCount := len(suffix) + base := raw[:j+1-removeCount] + prompt := strings.TrimSpace(base) + if prompt == "" { continue } - prev := string(raw[j-1]) - isTrigger := false - for _, pfx := range prefixes { - if prev == pfx { - isTrigger = true - break + // Slash commands (`/foo>`) do not require a prefix trigger. + isCommand := strings.HasPrefix(prompt, "/") + if !isCommand { + // Require at least one char before suffix and that char must be in chatPrefixes + if j < 1 { + continue + } + prev := string(raw[j-1]) + match := false + for _, pfx := range prefixes { + if prev == pfx { + match = true + break + } + } + if !match { + continue } - } - if !isTrigger { - continue } // Avoid double-answering: if the next non-empty line starts with '>' we skip. k := i + 1 @@ -135,13 +144,6 @@ func (s *Server) detectAndHandleChat(uri string) { if k < len(d.lines) && strings.HasPrefix(strings.TrimSpace(d.lines[k]), ">") { continue } - // Derive prompt by removing only the trailing '>' - removeCount := len(suffix) - base := raw[:j+1-removeCount] - prompt := strings.TrimSpace(base) - if prompt == "" { - continue - } lineIdx := i lastIdx := j if resp, ok := s.chatCommandResponse(uri, lineIdx, prompt); ok { |
