The server command is nimbus lsp. Your editor needs three things: that command, a filetype mapping for .cls and .trigger, and sfdx-project.json as the root marker. Everything else is editor-specific syntax.
Three things every LSP client needs
Language server setup looks different in every editor and is the same three decisions underneath. Getting them explicit makes the configs below readable rather than copy-paste magic:
- How to start the server. A command the editor launches and talks to over stdin/stdout. Here:
nimbus lsp. - Which files it owns. Apex lives in
.clsand.triggerfiles. No editor detects those as Apex out of the box — this is the step people skip, and then the server never starts. - Where the project root is.
sfdx-project.json. The server resolves classes, triggers, and the schema cache relative to it.
Install
The language server is not a separate download — it is a subcommand of the CLI:
# macOS / Linux
curl -sSL https://testnimbus.dev/install.sh | bash
# Windows (PowerShell)
iwr -useb https://testnimbus.dev/install.ps1 | iex
# Confirm it is on PATH
nimbus --versionNeovim 0.11+ (native LSP)
Neovim 0.11 and later has native LSP configuration. Drop this into your config:
-- ~/.config/nvim/init.lua (or any loaded file)
vim.lsp.config.nimbus = {
cmd = { 'nimbus', 'lsp' },
filetypes = { 'apex', 'apexcode' },
root_markers = { 'sfdx-project.json' },
-- Optional: write a protocol trace for debugging
-- cmd = { 'nimbus', 'lsp', '--log', '/tmp/nimbus-lsp.log' },
}
vim.lsp.enable('nimbus')
-- .cls and .trigger don't have a built-in filetype; teach Neovim:
vim.filetype.add({
extension = {
cls = 'apex',
trigger = 'apex',
},
})Neovim 0.8–0.10 (nvim-lspconfig)
On older Neovim, register the server as a custom lspconfig entry:
require('lspconfig.configs').nimbus = {
default_config = {
cmd = { 'nimbus', 'lsp' },
filetypes = { 'apex' },
root_dir = require('lspconfig.util').root_pattern('sfdx-project.json'),
settings = {},
},
}
require('lspconfig').nimbus.setup({})The vim.filetype.add block from the previous section is still required — lspconfig matches on filetype, and nothing maps .cls to apex for you.
Zed
Zed configures language servers per language in settings.json:
// ~/.config/zed/settings.json
{
"lsp": {
"nimbus": {
"binary": { "path": "nimbus", "arguments": ["lsp"] }
}
},
"languages": {
"Apex": { "language_servers": ["nimbus"] }
},
"file_types": {
"Apex": ["cls", "trigger"]
}
}Helix
Helix wires everything through languages.toml — server, file types, and roots in one place:
# ~/.config/helix/languages.toml
[language-server.nimbus-lsp]
command = "nimbus"
args = ["lsp"]
[[language]]
name = "apex"
scope = "source.apex"
file-types = ["cls", "trigger"]
roots = ["sfdx-project.json"]
language-servers = ["nimbus-lsp"]What you get once it connects
The server advertises its capabilities at initialize; how much of that renders depends on your client. The core is what you would expect — diagnostics on parse errors, completion, hover, signature help, go-to-definition, find references, rename, document symbols, folding, call hierarchy — plus two things specific to running Apex locally:
- Live SOQL column validation.
[SELECT Namee FROM Account]is a warning at the point you type it, with a quick fix offering the nearest real field names — including relationship fields once the chain resolves to a synced object. - Platform-data overlays. Coverage hit counts, surviving mutants, and flakiness rates from your last
nimbus testrun, surfaced as inlay hints and hover text.
All of it resolves against the local .nimbus/schemas/ cache rather than an org, so completion works offline and does not wait on a network round-trip. The full capability list is on the language server page.
When it does not work
- The server never starts. Confirm the binary resolves from the editor's environment (
nimbus --version), then runnimbus lsp --log /tmp/nimbus-lsp.logby hand. If the log stays empty when the editor should be talking to it, the editor is not finding the binary. - No diagnostics in a .cls file. Check the buffer's filetype. This is the filetype mapping step, and Neovim in particular does not auto-detect
.cls. - Custom objects missing from completion. Run
nimbus synconce to populate.nimbus/schemas/. Standard objects ship with the binary; yours do not. - Completion empty after a variable. The server needs the declared type. If the variable is declared in another file, use the fully-qualified name for now — cross-file type resolution is not fully wired.
Next steps
Once the server is answering, the rest of the loop is terminal-side and editor-agnostic: nimbus test "*" to run the suite, nimbus exec for a scratch snippet. Nothing about it assumes a GUI IDE.