Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/lsp.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Sixteen servers ship in the registry today
| `typescript-language-server` | `typescript-language-server` | typescript, javascript | 5 |
| `pyright` | `pyright-langserver` | python | 5 |
| `rust-analyzer` | `rust-analyzer` | rust | 5 |
| `clangd` | `clangd --background-index` | c, c++, objc, objc++ | 5 |
| `clangd` | `clangd --background-index=false --clang-tidy=false --header-insertion=never -j=1` | c, c++, objc, objc++ | 5 |
| `jdtls` | `jdtls` | java | 6 |
| `kotlin-language-server` | `kotlin-language-server` | kotlin | 6 |
| `omnisharp` | `omnisharp -lsp` | csharp | 5 |
Expand All @@ -47,6 +47,12 @@ binary on `PATH`:
- `ruby-lsp` → falls back to `solargraph stdio`.
- `phpactor` → falls back to `intelephense --stdio`.

The built-in `clangd` spec disables background indexing and clang-tidy by
default because semantic enrichment needs request-driven graph evidence,
not lint diagnostics or a persistent project index. Repositories that want
those clangd features can override the `clangd` provider args in
`.gortex.yaml`.

Lower priority numbers win when more than one provider serves the same
language. `gopls` is `3` so it beats SCIP-based providers (`5`) for Go;
`jdtls` is `6` so it's lower-priority than the SCIP-java path that
Expand Down
17 changes: 12 additions & 5 deletions internal/semantic/lsp/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,18 @@ var Servers = []ServerSpec{
{
Name: "clangd",
Command: "clangd",
// `--background-index` keeps a project-wide symbol index hot in
// the daemon, which is essential for type-hierarchy precision in
// large C++ trees. `--header-insertion=never` avoids tactical
// edits when we only want graph signal.
Args: []string{"--background-index", "--header-insertion=never"},
// Gortex uses clangd for request-driven graph evidence during
// enrichment, not lint diagnostics or a persistent project index.
// Keep clang-tidy and background indexing off by default so broad
// repo .clang-tidy configs and large C++ trees do not dominate CPU
// or crash-loop the enrichment subprocess. Users can opt back in via
// a semantic.providers override in .gortex.yaml.
Args: []string{
"--background-index=false",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@darthghandi --background-index=false has caveats:

It can impact the C/C++ cross-file graph fidelity:

  • find_usages / get_callers on a C/C++ function -> only same-TU callers; call sites in other .cpp files are not found. Ambiguous call edges never get confirmed to the lsp_resolved tier, so min_tier filtering drops them → missing caller edges.
  • find_implementations -> misses implementers in other files.
  • Type hierarchy -> misses subtypes defined elsewhere.

I want to benchmark it before accepting it.

"--clang-tidy=false",
"--header-insertion=never",
"-j=1",
},
Languages: []string{"c", "cpp", "objc", "objcpp"},
Extensions: []string{
".c", ".h",
Expand Down
22 changes: 22 additions & 0 deletions internal/semantic/lsp/registry_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lsp

import (
"slices"
"strings"
"testing"

Expand Down Expand Up @@ -179,6 +180,27 @@ func TestPyreflyAndTsgoSpecs(t *testing.T) {
}
}

// TestClangdSpecUsesSafeEnrichmentDefaults pins clangd's built-in
// enrichment argv. Gortex needs request-driven graph signal from
// clangd, not repo clang-tidy diagnostics or a persistent background
// project index.
func TestClangdSpecUsesSafeEnrichmentDefaults(t *testing.T) {
clangd := SpecByName("clangd")
if clangd == nil {
t.Fatal("clangd spec not registered")
}

want := []string{
"--background-index=false",
"--clang-tidy=false",
"--header-insertion=never",
"-j=1",
}
if got := clangd.Args; !slices.Equal(got, want) {
t.Fatalf("clangd args = %v, want %v", got, want)
}
}

// TestSpecWithOverrides verifies .gortex.yaml command / args / env
// overrides are applied to a copy without mutating the built-in spec —
// the path by which jdtls gets a pinned JRE and launcher args.
Expand Down