Language Server

An Apex language server
for every editor.

Nimbus ships an LSP 3.17 server for Apex. It provides schema-aware completion, live SOQL column validation, inline coverage hints, mutation score annotations, and one-click test runs in VSCode, Neovim, Zed, Helix, JetBrains, Emacs, or anything that speaks LSP. Everything runs against your local project, with no org and no setup other thannimbus lsp.

Compile errors appear as you write, before a deploy, including "Method does not exist or incorrect signature" and the rest of the Apex errors reference.

What it does today

Eight capabilities, all wired to the same parser, schema cache, and test data that powers the Nimbus CLI. The heavy lifting was already in the daemon; the LSP is a thin protocol layer over state we had.

Schema-aware completion

Type a.│ where a is an Account and 71 real fields appear, sourced from your local .nimbus/schemas/. Custom __c fields are included. There is no org round-trip.

Live SOQL column validation

Write [SELECT Namee FROM Account] and the typo gets a yellow squiggle before you reach the closing bracket. The same validator runs against Postgres when you execute the query.

apiVersion compatibility check

A class compiled at apiVersion=52.0 calling Database.getCursor (v66) gets a warning on the call site, not after a failed deploy. Reads the sibling *.cls-meta.xml; bump the apiVersion and the squiggle clears immediately. Suppress per line with // nimbus:ignore.

Coverage inlay hints

After a test run, each method displays its hit count inline: 3×, not covered, or the surviving mutant description (☠ survived: Shifted loop bound +1). Gaps show in the file itself.

Mutation score on class headers

Ghost text next to every class: ☣ 86.5% mutation. A quick glance tells you whether the tests in this file are actually testing anything.

Test flakiness warnings

Any @IsTest method that has failed intermittently across recent runs shows ⚠ flaky (12%) inline. Reads from .nimbus/history/, no separate tool to check.

Code lenses with one-click actions

▶ Run test / ◉ Debug / 🪄 Coverage above every @IsTest. ☣ Mutate on production classes. ⏱ Bench on test classes. Every LSP client renders them without an extension.

Document outline

Full tree of classes, methods, fields, inner classes, enums. Jump by symbol with Cmd+Shift+O in VSCode, :Outline in Helix, or whatever your editor's convention is.

Go-to-definition

F12 on a class name jumps to its .cls file. Works across force-app/, src/, and stubs/. Last-good-parse cache means it still works when the current file is transiently broken.

Example

Type a SOQL literal with a deliberate typo. The yellow squiggle appears before you finish the line, sourced from the same schema cache the test runner uses rather than from a remote org.

Click the bulb, apply "Fix: Name" (on the way), and the column name snaps to the nearest real match. Run the test inline with ▶ Run test. Editing, validating, and running never leave the editor and never touch the network.

No other Apex LSP does this, because no other Apex LSP has a local schema cache, a coverage collector, and a test runner behind it.

apex
public class OrderBook {
  public static void run(Id accountId) {
    // Type 'a.' → 71 Account fields auto-complete.
    Account a = [SELECT Id, Name FROM Account
                 WHERE Id = :accountId LIMIT 1];
    System.debug(a.Name);

    // Yellow squiggle on 'Namesaa' — live, from schema.
    List<Contact> cs = [
      SELECT Id, Namesaa
      FROM Contact WHERE AccountId = :accountId
    ];
  }
}

// Above every @IsTest method:
//   ▶ Run test  |  ◉ Debug  |  🪄 Coverage
// Ghost text beside tested lines:
//   3×   (hit count from last run)
//   ☠ survived: Changed integer 2 to 3
//   ⚠ flaky (12%) of recent runs failed

Editor setup

Copy the config that matches your editor. There is nothing to install beyond nimbus.

VSCodeAuto-starts via the Nimbus extension
bash
// .vscode/settings.json (extension handles everything)
{
  "nimbus.lsp.enabled": true
}
Neovim (0.11+)Native LSP config
bash
-- ~/.config/nvim/init.lua
vim.lsp.config.nimbus = {
  cmd = { 'nimbus', 'lsp' },
  filetypes = { 'apex' },
  root_markers = { 'sfdx-project.json' },
}
vim.lsp.enable('nimbus')
vim.filetype.add({
  extension = { cls = 'apex', trigger = 'apex' },
})
ZedSettings file only
bash
// ~/.config/zed/settings.json
{
  "lsp": {
    "nimbus": { "binary": { "path": "nimbus", "arguments": ["lsp"] } }
  },
  "languages": { "Apex": { "language_servers": ["nimbus"] } },
  "file_types": { "Apex": ["cls", "trigger"] }
}
Helixlanguages.toml
bash
# ~/.config/helix/languages.toml
[language-server.nimbus-lsp]
command = "nimbus"
args = ["lsp"]

[[language]]
name = "apex"
file-types = ["cls", "trigger"]
roots = ["sfdx-project.json"]
language-servers = ["nimbus-lsp"]
JetBrainsVia LSP4IJ plugin
bash
# Settings → Language Servers → +
Name:    Nimbus Apex
Command: nimbus lsp
Mapping: *.cls, *.trigger
Emacseglot or lsp-mode
bash
;; eglot (Emacs 29+, built-in)
(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs
               '(apex-mode . ("nimbus" "lsp"))))

Compared with the Salesforce Apex Language Server

Salesforce ships their own Apex Language Server. It works, but every capability is bottlenecked on a live org connection and none of the test-data overlays exist there.

NimbusSalesforce Apex LSP
Zero-latency completionReads .nimbus/schemas/Account.json locally.Round-trip to the org describe API. 200–2000ms.
Works offlineEvery feature runs against local files.Requires an active org connection.
Coverage inlay hintsGhost text per method, every test run.None.
Mutation annotationsPer-class score + surviving mutants on the exact line.None.
Live SOQL validationEvery column checked as you type.Basic; relies on org metadata sync.
Test flakiness inlineReads .nimbus/history/; warns on unstable tests.None.
Editor-neutralWorks in VSCode, Neovim, Zed, Helix, JetBrains, Emacs.VSCode and Code Builder only.

Refactoring

Beyond completion and coverage hints, the server covers workspace-wide refactoring. All of it runs offline, on the same parser and schema the test runner uses.

Find all references

Shift+F12 on any symbol lists every caller across the repo, from a workspace symbol index that rebuilds incrementally on every keystroke.

Rename symbol

F2 on a class, method, or field applies a workspace-wide edit atomically. 22 edits across 2 files when renamingCalculator on the testdata project.

Signature help

Floating parameter tooltip while you type a method call, with the active parameter bolded. Covers stdlib, user classes, and stays accurate even mid-keystroke.

Workspace symbol search

Cmd+T finds any class, method, or field across the repo. Ranks exact matches over prefix matches over substrings.

Code actions

Yellow bulb with SOQL auto-fix (NameeName), "Generate test stub" with schema-aware record construction, "Extract to @TestSetup", "Add @IsTest annotation".

Snippets

Type test → full @IsTest scaffold with Arrange/Act/Assert Tab-through placeholders. 12 hand-curated Apex idioms: debug, soql, trycatch, runAs, trigger, classTest…

Call hierarchy

Ctrl+Shift+H on any method shows incoming and outgoing calls, resolved through the workspace symbol index.

Semantic tokens

Theme-aware syntax highlighting: annotations, keywords, stdlib namespaces, types, numbers, comments, strings. Customrequired modifier reserved for custom fields marked <required>true</required>.

Opinionated formatter

Conservative by design: normalises indentation, trims trailing whitespace, collapses excess blank lines. Preserves strings, SOQL, and comments verbatim. Idempotent on clean input.

Dead code detector

Faded Hint diagnostics on methods that have zero workspace references AND zero coverage hits. Skipsglobal/virtual to avoid false positives on public APIs.

Folding + selection + highlight

Brace-based code folding, Ctrl+W expand-selection, same-symbol highlighting in the active file.

Type / impl / declaration jumps

Separate protocol verbs for "type of this variable", "implementations of this interface", and "declaration (opposed to definition)".

Planned

Follow-ups, most valuable on long-lived codebases. None are needed for day-to-day use.

Inline debug values

Ghost-text variable values beside running lines during a debug session. Blocked on LSP and DAP co-hosting. They are separate processes today.

Coverage-filtered call hierarchy

"Which tests actually exercise this method?" filter on incoming calls, using the coverage overlay.

Refactor moves

Extract variable, inline variable, inline method, convert field to property with getter/setter. Each independent, ~1 day's work.

See LSP_ROADMAP.md in the source for the full plan.

Try it

If you have Nimbus installed, the LSP is already available. The VSCode extension auto-starts it; other editors need the 5-line config above. The server is the same process for every editor, so the capabilities are identical wherever you write your Apex.

Completion and diagnostics work on a fresh clone before the first nimbus sync; stock SObjects and the stdlib are built in. Custom object fields appear after your first sync. Coverage and mutation inlays appear after your first nimbus test --coverage run.

Full configuration and troubleshooting docs live in the reference.

bash
# Already installed Nimbus? LSP is ready.
nimbus lsp             # What editors launch
nimbus lsp --log /tmp/nimbus-lsp.log  # Debug mode

# Not yet installed:
brew install nimbus-solution/nimbus/nimbus
# or download from testnimbus.dev