LSP API
Use this API to register and manage language servers for Acode's CodeMirror LSP integration.
Import
const lsp = acode.require("lsp");Current API Shape
The public API is intentionally small:
lsp.defineServer(...)lsp.defineBundle(...)lsp.register(entry, options?)lsp.upsert(entry)lsp.installers.*lsp.servers.*lsp.bundles.*
bundle is the public name for what the internal runtime still calls a provider:
- a bundle can own one or more server definitions
- a bundle can also provide install/check behavior hooks
- most plugins only need a single server
- use a bundle when you ship a family of related servers or custom install logic
Transport Reality
Acode's LSP client still speaks WebSocket to the transport layer.
transport.kind: "websocket"is the normal and recommended setup- local stdio servers should usually be launched through
launcher.bridge transport.kind: "stdio"still expects a WebSocket bridge URLtransport.kind: "external"is available for custom transport factories
For local servers, prefer transport.kind: "websocket" plus an AXS bridge.
WARNING
transport.kind: "stdio" is not a direct pipe from the editor to the server. It still resolves to the WebSocket transport layer and requires a bridge URL.
Recommended Single-Server Setup
Use defineServer() and upsert() for idempotent registration.
const lsp = acode.require("lsp");
const typescriptServer = lsp.defineServer({
id: "typescript-custom",
label: "TypeScript (Custom)",
languages: [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"tsx",
"jsx",
],
useWorkspaceFolders: true,
transport: {
kind: "websocket",
},
command: "typescript-language-server",
args: ["--stdio"],
checkCommand: "which typescript-language-server",
installer: lsp.installers.npm({
executable: "typescript-language-server",
packages: ["typescript-language-server", "typescript"],
}),
initializationOptions: {
provideFormatter: true,
},
});
lsp.upsert(typescriptServer);Bundle Setup
Use a bundle when one plugin contributes multiple servers or needs custom install behavior.
const lsp = acode.require("lsp");
const htmlServer = lsp.defineServer({
id: "my-html",
label: "My HTML Server",
languages: ["html"],
transport: {
kind: "websocket",
},
command: "vscode-html-language-server",
args: ["--stdio"],
installer: lsp.installers.npm({
executable: "vscode-html-language-server",
packages: ["vscode-langservers-extracted"],
}),
});
const cssServer = lsp.defineServer({
id: "my-css",
label: "My CSS Server",
languages: ["css", "scss", "less"],
transport: {
kind: "websocket",
},
command: "vscode-css-language-server",
args: ["--stdio"],
installer: lsp.installers.npm({
executable: "vscode-css-language-server",
packages: ["vscode-langservers-extracted"],
}),
});
const webBundle = lsp.defineBundle({
id: "my-web-bundle",
label: "My Web Bundle",
servers: [htmlServer, cssServer],
});
lsp.upsert(webBundle);Bundle Hooks
Bundles can own behavior, not just server lists.
Available hooks:
getExecutable(serverId, manifest)checkInstallation(serverId, manifest)installServer(serverId, manifest, mode, options?)
Example:
const bundle = lsp.defineBundle({
id: "my-bundle",
label: "My Bundle",
servers: [myServer],
hooks: {
getExecutable(serverId, manifest) {
return manifest.launcher?.install?.binaryPath || null;
},
async checkInstallation(serverId, manifest) {
return {
status: "present",
version: null,
canInstall: true,
canUpdate: true,
};
},
async installServer(serverId, manifest, mode) {
console.log("install", serverId, mode);
return true;
},
},
});Structured Installers
Prefer structured installers over raw shell whenever possible.
Available installer builders:
lsp.installers.apk(...)lsp.installers.npm(...)lsp.installers.pip(...)lsp.installers.cargo(...)lsp.installers.githubRelease(...)lsp.installers.manual(...)lsp.installers.shell(...)
Example:
const server = lsp.defineServer({
id: "python-custom",
label: "Python (pylsp)",
languages: ["python"],
command: "pylsp",
installer: lsp.installers.pip({
executable: "pylsp",
packages: ["python-lsp-server[all]"],
}),
});Installer Notes
- managed installers should declare the executable they provide
githubRelease()is intended for arch-aware downloaded binariesmanual()is useful when the binary already exists at a known pathshell()should be treated as the advanced fallback, not the default path
Remote WebSocket Server
lsp.upsert({
id: "remote-json-lsp",
label: "Remote JSON LSP",
languages: ["json"],
transport: {
kind: "websocket",
url: "ws://127.0.0.1:2087/",
options: {
binary: true,
timeout: 5000,
},
},
enabled: true,
});Definition API
lsp.defineServer(options)
Builds a normalized server manifest for later registration.
Common fields:
id: required, normalized to lowercase by the registrylabel: optional display labellanguages: required non-empty arrayenabled: defaults totruetransportcommandandargs: used to create an AXS launcher bridgeinstaller: structured installer configcheckCommandversionCommandupdateCommandinitializationOptionsclientConfigstartupTimeoutcapabilityOverridesrootUriresolveLanguageIduseWorkspaceFolders
lsp.defineBundle(options)
Creates a bundle record.
Fields:
id: required bundle idlabel: optionalservers: array returned bylsp.defineServer(...)hooks?: optional behavioral hooks
Registration API
lsp.register(entry, options?)
Registers either a server or bundle if the id is free.
options.replace?: booleandefaults tofalse
lsp.upsert(entry)
Registers or replaces either a server or bundle. This is the preferred method for plugin startup code.
Server Inspection API
lsp.servers.get(id)lsp.servers.list()lsp.servers.listForLanguage(languageId, options?)lsp.servers.update(id, updater)lsp.servers.unregister(id)lsp.servers.onChange(listener)
Example:
const jsServers = lsp.servers.listForLanguage("javascript");
lsp.servers.update("typescript-custom", (current) => ({
...current,
enabled: false,
}));listForLanguage() options:
includeDisabled?: booleandefaultfalse
Bundle Inspection API
lsp.bundles.list()lsp.bundles.getForServer(serverId)lsp.bundles.unregister(id)
Client Manager
lsp.clientManager.setOptions(options)lsp.clientManager.getActiveClients()
lsp.clientManager.setOptions({
diagnosticsUiExtension: [],
});
const activeClients = lsp.clientManager.getActiveClients();
console.log(activeClients);Best Practices
- Prefer
lsp.upsert(...)during plugin init. - Prefer
defineServer()anddefineBundle()instead of hand-assembling objects everywhere. - Prefer structured installers over raw shell commands.
- Use a bundle when your plugin owns a family of related servers or custom install logic.
- Use
useWorkspaceFolders: truefor heavy workspace-aware servers like TypeScript or Rust.
