Nimbus ships an LSP 3.17 server for Apex. 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. All running against your local project. No org, no latency, no setup other thannimbus lsp.
See compile errors at write time, not deploy time — like "Method does not exist or incorrect signature" and the rest of the Apex errors reference.
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.
Type a.│ where a is an Account — 71 real fields pop up, sourced from your local .nimbus/schemas/. Custom __c fields included. No org round-trip. No 2-second lag.
Write [SELECT Namee FROM Account] — the typo gets a yellow squiggle before you reach the closing bracket. The same validator runs against Postgres when you execute the query.
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.
After a test run, each method displays its hit count inline: 3×, not covered, or the surviving mutant description (☠ survived: Shifted loop bound +1). Makes gaps impossible to miss.
Ghost text next to every class: ☣ 86.5% mutation. A quick glance tells you whether the tests in this file are actually testing anything.
Any @IsTest method that has failed intermittently across recent runs shows ⚠ flaky (12%) inline. Reads from .nimbus/history/, no separate tool to check.
▶ Run test / ◉ Debug / 🪄 Coverage above every @IsTest. ☣ Mutate on production classes. ⏱ Bench on test classes. No extension required — every LSP client renders them.
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.
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.
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, not 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. The whole loop — edit, validate, run — never leaves the editor and never touches the network.
No other Apex LSP does any of this, because no other Apex LSP has a local schema cache, a coverage collector, and a test runner sitting behind it. Nimbus does.
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 failedOne binary, one protocol, every editor. Copy the config that matches yours. Nothing to install beyond nimbus.
// .vscode/settings.json (extension handles everything)
{
"nimbus.lsp.enabled": true
}-- ~/.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' },
})// ~/.config/zed/settings.json
{
"lsp": {
"nimbus": { "binary": { "path": "nimbus", "arguments": ["lsp"] } }
},
"languages": { "Apex": { "language_servers": ["nimbus"] } },
"file_types": { "Apex": ["cls", "trigger"] }
}# ~/.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"]# Settings → Language Servers → +
Name: Nimbus Apex
Command: nimbus lsp
Mapping: *.cls, *.trigger;; eglot (Emacs 29+, built-in)
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(apex-mode . ("nimbus" "lsp"))))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.
| Nimbus | Salesforce Apex LSP | |
|---|---|---|
| Zero-latency completion | Reads .nimbus/schemas/Account.json. Instant. | Round-trip to the org describe API. 200–2000ms. |
| Works offline | Every feature runs against local files. | Requires an active org connection. |
| Coverage inlay hints | Ghost text per method, every test run. | None. |
| Mutation annotations | Per-class score + surviving mutants on the exact line. | None. |
| Live SOQL validation | Every column checked as you type. | Basic; relies on org metadata sync. |
| Test flakiness inline | Reads .nimbus/history/; warns on unstable tests. | None. |
| Editor-neutral | Works in VSCode, Neovim, Zed, Helix, JetBrains, Emacs. | VSCode and Code Builder only. |
Beyond completion and coverage hints, Nimbus LSP covers the workspace-wide refactoring capabilities developers rely on daily. All of it offline, all of it powered by the same parser + schema the test runner uses.
Shift+F12 on any symbol — every caller across the repo. Powered by a workspace symbol index that rebuilds incrementally on every keystroke.
F2 on a class, method, or field — workspace-wide edit applied atomically. 22 edits across 2 files when renamingCalculator on the testdata project.
Floating parameter tooltip while you type a method call, with the active parameter bolded. Covers stdlib, user classes, and stays accurate even mid-keystroke.
Cmd+T finds any class, method, or field across the repo. Ranks exact matches over prefix matches over substrings.
Yellow bulb with SOQL auto-fix (Namee → Name), "Generate test stub" with schema-aware record construction, "Extract to @TestSetup", "Add @IsTest annotation".
Type test → full @IsTest scaffold with Arrange/Act/Assert Tab-through placeholders. 12 hand-curated Apex idioms: debug, soql, trycatch, runAs, trigger, classTest…
Ctrl+Shift+H on any method — incoming and outgoing calls, resolved through the workspace symbol index.
Theme-aware syntax highlighting: annotations, keywords, stdlib namespaces, types, numbers, comments, strings. Customrequired modifier reserved for custom fields marked <required>true</required>.
Conservative by design: normalises indentation, trims trailing whitespace, collapses excess blank lines. Preserves strings, SOQL, and comments verbatim. Idempotent on clean input.
Faded Hint diagnostics on methods that have zero workspace references AND zero coverage hits. Skipsglobal/virtual to avoid false positives on public APIs.
Brace-based code folding, Ctrl+W expand-selection, same-symbol highlighting in the active file.
Separate protocol verbs for "type of this variable", "implementations of this interface", and "declaration (opposed to definition)".
Pragmatic follow-ups. Most valuable on long-lived codebases. All optional for the daily loop.
Ghost-text variable values beside running lines during a debug session. Blocked on LSP ↔ DAP co-hosting — they're separate processes today.
"Which tests actually exercise this method?" filter on incoming calls, using the coverage overlay.
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.
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 everyone — you get identical capability, regardless of which editor you write your Apex in.
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.
# 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