11# LSP API
22
3- Use this API to register/ manage language servers used by Acode's CodeMirror LSP client .
3+ Use this API to register and manage language servers for Acode's CodeMirror LSP integration .
44
55## Import
66
77``` js
88const lsp = acode .require (" lsp" );
99```
1010
11- ## Important Transport Reality
11+ ## Current API Shape
1212
13- CodeMirror's LSP client in Acode communicates via ** WebSocket ** transport.
13+ The public API is intentionally small:
1414
15- - ` transport.kind: "websocket" ` is the recommended and practical mode.
16- - ` transport.kind: "stdio" ` is ** not a direct stdio pipe** to the editor.
17- - In this codebase, ` stdio ` mode still expects a ** WebSocket bridge URL** .
15+ - ` lsp.defineServer(...) `
16+ - ` lsp.defineBundle(...) `
17+ - ` lsp.register(entry, options?) `
18+ - ` lsp.upsert(entry) `
19+ - ` lsp.installers.* `
20+ - ` lsp.servers.* `
21+ - ` lsp.bundles.* `
1822
19- For local stdio language servers, use an ** AXS bridge** (` launcher.bridge ` ) and keep transport as websocket.
23+ ` bundle ` is the public name for what the internal runtime still calls a provider:
24+
25+ - a bundle can own one or more server definitions
26+ - a bundle can also provide install/check behavior hooks
27+ - most plugins only need a single server
28+ - use a bundle when you ship a family of related servers or custom install logic
29+
30+ ## Transport Reality
31+
32+ Acode's LSP client still speaks WebSocket to the transport layer.
33+
34+ - ` transport.kind: "websocket" ` is the normal and recommended setup
35+ - local stdio servers should usually be launched through ` launcher.bridge `
36+ - ` transport.kind: "stdio" ` still expects a WebSocket bridge URL
37+ - ` transport.kind: "external" ` is available for custom transport factories
38+
39+ For local servers, prefer ` transport.kind: "websocket" ` plus an AXS bridge.
2040
2141::: warning
22- ` stdio ` with only ` transport.command ` is not enough in Acode.
23- You still need a WebSocket bridge endpoint (AXS or equivalent) for the client connection .
42+ ` transport.kind: "stdio" ` is not a direct pipe from the editor to the server.
43+ It still resolves to the WebSocket transport layer and requires a bridge URL .
2444:::
2545
26- ## Recommended Setup (Local Server via AXS Bridge)
46+ ## Recommended Single-Server Setup
47+
48+ Use ` defineServer() ` and ` upsert() ` for idempotent registration.
49+
50+ ``` js
51+ const lsp = acode .require (" lsp" );
52+
53+ const typescriptServer = lsp .defineServer ({
54+ id: " typescript-custom" ,
55+ label: " TypeScript (Custom)" ,
56+ languages: [
57+ " javascript" ,
58+ " javascriptreact" ,
59+ " typescript" ,
60+ " typescriptreact" ,
61+ " tsx" ,
62+ " jsx" ,
63+ ],
64+ useWorkspaceFolders: true ,
65+ transport: {
66+ kind: " websocket" ,
67+ },
68+ command: " typescript-language-server" ,
69+ args: [" --stdio" ],
70+ checkCommand: " which typescript-language-server" ,
71+ installer: lsp .installers .npm ({
72+ executable: " typescript-language-server" ,
73+ packages: [" typescript-language-server" , " typescript" ],
74+ }),
75+ initializationOptions: {
76+ provideFormatter: true ,
77+ },
78+ });
79+
80+ lsp .upsert (typescriptServer);
81+ ```
82+
83+ ## Bundle Setup
84+
85+ Use a bundle when one plugin contributes multiple servers or needs custom install behavior.
86+
87+ ``` js
88+ const lsp = acode .require (" lsp" );
89+
90+ const htmlServer = lsp .defineServer ({
91+ id: " my-html" ,
92+ label: " My HTML Server" ,
93+ languages: [" html" ],
94+ transport: {
95+ kind: " websocket" ,
96+ },
97+ command: " vscode-html-language-server" ,
98+ args: [" --stdio" ],
99+ installer: lsp .installers .npm ({
100+ executable: " vscode-html-language-server" ,
101+ packages: [" vscode-langservers-extracted" ],
102+ }),
103+ });
104+
105+ const cssServer = lsp .defineServer ({
106+ id: " my-css" ,
107+ label: " My CSS Server" ,
108+ languages: [" css" , " scss" , " less" ],
109+ transport: {
110+ kind: " websocket" ,
111+ },
112+ command: " vscode-css-language-server" ,
113+ args: [" --stdio" ],
114+ installer: lsp .installers .npm ({
115+ executable: " vscode-css-language-server" ,
116+ packages: [" vscode-langservers-extracted" ],
117+ }),
118+ });
119+
120+ const webBundle = lsp .defineBundle ({
121+ id: " my-web-bundle" ,
122+ label: " My Web Bundle" ,
123+ servers: [htmlServer, cssServer],
124+ });
125+
126+ lsp .upsert (webBundle);
127+ ```
128+
129+ ## Bundle Hooks
130+
131+ Bundles can own behavior, not just server lists.
132+
133+ Available hooks:
134+
135+ - ` getExecutable(serverId, manifest) `
136+ - ` checkInstallation(serverId, manifest) `
137+ - ` installServer(serverId, manifest, mode, options?) `
138+
139+ Example:
27140
28141``` js
29- lsp .registerServer (
30- {
31- id: " typescript-custom" ,
32- label: " TypeScript (Custom)" ,
33- languages: [" javascript" , " javascriptreact" , " typescript" , " typescriptreact" , " tsx" , " jsx" ],
34- transport: {
35- kind: " websocket" ,
36- // url can be omitted when using launcher.bridge auto-port mode
142+ const bundle = lsp .defineBundle ({
143+ id: " my-bundle" ,
144+ label: " My Bundle" ,
145+ servers: [myServer],
146+ hooks: {
147+ getExecutable (serverId , manifest ) {
148+ return manifest .launcher ? .install ? .binaryPath || null ;
37149 },
38- launcher: {
39- bridge: {
40- kind: " axs" ,
41- command: " typescript-language-server" ,
42- args: [" --stdio" ],
43- },
44- checkCommand: " which typescript-language-server" ,
45- install: {
46- command:
47- " apk add --no-cache nodejs npm && npm install -g typescript-language-server typescript" ,
48- },
150+ async checkInstallation (serverId , manifest ) {
151+ return {
152+ status: " present" ,
153+ version: null ,
154+ canInstall: true ,
155+ canUpdate: true ,
156+ };
49157 },
50- enabled : true ,
51- initializationOptions : {
52- provideFormatter : true ,
158+ async installServer ( serverId , manifest , mode ) {
159+ console . log ( " install " , serverId, mode);
160+ return true ;
53161 },
54162 },
55- { replace: true },
56- );
163+ });
57164` ` `
58165
59- ## Remote/External WebSocket Server
166+ ## Structured Installers
167+
168+ Prefer structured installers over raw shell whenever possible.
169+
170+ Available installer builders:
171+
172+ - ` lsp .installers .apk (... )`
173+ - ` lsp .installers .npm (... )`
174+ - ` lsp .installers .pip (... )`
175+ - ` lsp .installers .cargo (... )`
176+ - ` lsp .installers .githubRelease (... )`
177+ - ` lsp .installers .manual (... )`
178+ - ` lsp .installers .shell (... )`
179+
180+ Example:
60181
61182` ` ` js
62- lsp .registerServer ({
63- id: " remote-lsp" ,
64- label: " Remote LSP" ,
183+ const server = lsp .defineServer ({
184+ id: " python-custom" ,
185+ label: " Python (pylsp)" ,
186+ languages: [" python" ],
187+ command: " pylsp" ,
188+ installer: lsp .installers .pip ({
189+ executable: " pylsp" ,
190+ packages: [" python-lsp-server[all]" ],
191+ }),
192+ });
193+ ` ` `
194+
195+ ### Installer Notes
196+
197+ - managed installers should declare the executable they provide
198+ - ` githubRelease ()` is intended for arch-aware downloaded binaries
199+ - ` manual ()` is useful when the binary already exists at a known path
200+ - ` shell ()` should be treated as the advanced fallback, not the default path
201+
202+ ## Remote WebSocket Server
203+
204+ ` ` ` js
205+ lsp .upsert ({
206+ id: " remote-json-lsp" ,
207+ label: " Remote JSON LSP" ,
65208 languages: [" json" ],
66209 transport: {
67210 kind: " websocket" ,
@@ -75,66 +218,89 @@ lsp.registerServer({
75218});
76219` ` `
77220
78- ## API Reference
79-
80- ### ` registerServer(definition, options?) `
81-
82- - ` options.replace?: boolean ` (default ` false ` )
83- - ` false ` : existing server with same id is kept
84- - ` true ` : existing server is replaced
85-
86- Definition fields commonly used:
87-
88- - ` id ` (required): normalized to lowercase
89- - ` label ` (optional)
90- - ` languages ` (required): non-empty array, normalized to lowercase
91- - ` enabled ` (optional, default ` true ` )
92- - ` transport ` (required)
93- - ` kind ` : ` "websocket" ` or ` "stdio" ` (see transport note above)
94- - ` url?: string `
95- - ` command?: string ` (required by registry validation when using ` stdio ` )
96- - ` args?: string[] `
97- - ` options?: { binary?, timeout?, reconnect?, maxReconnectAttempts? } `
98- - ` launcher ` (optional)
99- - ` startCommand?: string | string[] `
100- - ` command?: string `
101- - ` args?: string[] `
102- - ` checkCommand?: string `
103- - ` install?: { command?: string } `
104- - ` bridge?: { kind: "axs", command: string, args?: string[], port?: number, session?: string } `
105- - ` initializationOptions?: object `
106- - ` clientConfig?: object `
107- - ` startupTimeout?: number `
108- - ` capabilityOverrides?: object `
109- - ` rootUri?: (uri, context) => string | null `
110- - ` resolveLanguageId?: (context) => string | null `
111- - ` useWorkspaceFolders?: boolean `
112-
113- ### Other Registry Methods
114-
115- - ` unregisterServer(id) `
116- - ` updateServer(id, updater) `
117- - ` getServer(id) `
118- - ` listServers() `
119- - ` getServersForLanguage(languageId, options?) `
221+ ## Definition API
222+
223+ ### ` lsp .defineServer (options)`
224+
225+ Builds a normalized server manifest for later registration.
226+
227+ Common fields:
228+
229+ - ` id` : required, normalized to lowercase by the registry
230+ - ` label` : optional display label
231+ - ` languages` : required non-empty array
232+ - ` enabled` : defaults to ` true `
233+ - ` transport`
234+ - ` command` and ` args` : used to create an AXS launcher bridge
235+ - ` installer` : structured installer config
236+ - ` checkCommand`
237+ - ` versionCommand`
238+ - ` updateCommand`
239+ - ` initializationOptions`
240+ - ` clientConfig`
241+ - ` startupTimeout`
242+ - ` capabilityOverrides`
243+ - ` rootUri`
244+ - ` resolveLanguageId`
245+ - ` useWorkspaceFolders`
246+
247+ ### ` lsp .defineBundle (options)`
248+
249+ Creates a bundle record.
250+
251+ Fields:
252+
253+ - ` id` : required bundle id
254+ - ` label` : optional
255+ - ` servers` : array returned by ` lsp .defineServer (... )`
256+ - ` hooks? ` : optional behavioral hooks
257+
258+ ## Registration API
259+
260+ ### ` lsp .register (entry, options? )`
261+
262+ Registers either a server or bundle if the id is free.
263+
264+ - ` options .replace ?: boolean` defaults to ` false `
265+
266+ ### ` lsp .upsert (entry)`
267+
268+ Registers or replaces either a server or bundle. This is the preferred method for plugin startup code.
269+
270+ ## Server Inspection API
271+
272+ - ` lsp .servers .get (id)`
273+ - ` lsp .servers .list ()`
274+ - ` lsp .servers .listForLanguage (languageId, options? )`
275+ - ` lsp .servers .update (id, updater)`
276+ - ` lsp .servers .unregister (id)`
277+ - ` lsp .servers .onChange (listener)`
278+
279+ Example:
120280
121281` ` ` js
122- const jsServers = lsp .getServersForLanguage (" javascript" );
282+ const jsServers = lsp .servers . listForLanguage (" javascript" );
123283
124- lsp .updateServer (" typescript-custom" , (current ) => ({
284+ lsp .servers . update (" typescript-custom" , (current ) => ({
125285 ... current,
126286 enabled: false ,
127287}));
128288` ` `
129289
130- ` getServersForLanguage ` options:
290+ ` listForLanguage () ` options:
131291
132- - ` includeDisabled?: boolean ` (default ` false ` )
292+ - ` includeDisabled?: boolean` default ` false `
293+
294+ ## Bundle Inspection API
295+
296+ - ` lsp .bundles .list ()`
297+ - ` lsp .bundles .getForServer (serverId)`
298+ - ` lsp .bundles .unregister (id)`
133299
134300## Client Manager
135301
136- - ` clientManager.setOptions(options) `
137- - ` clientManager.getActiveClients() `
302+ - ` lsp . clientManager .setOptions (options)`
303+ - ` lsp . clientManager .getActiveClients ()`
138304
139305` ` ` js
140306lsp .clientManager .setOptions ({
@@ -144,3 +310,11 @@ lsp.clientManager.setOptions({
144310const activeClients = lsp .clientManager .getActiveClients ();
145311console .log (activeClients);
146312` ` `
313+
314+ ## Best Practices
315+
316+ - Prefer ` lsp .upsert (... )` during plugin init.
317+ - Prefer ` defineServer ()` and ` defineBundle ()` instead of hand-assembling objects everywhere.
318+ - Prefer structured installers over raw shell commands.
319+ - Use a bundle when your plugin owns a family of related servers or custom install logic.
320+ - Use ` useWorkspaceFolders: true ` for heavy workspace-aware servers like TypeScript or Rust.
0 commit comments