@adecore/lsp 0.41.1 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/session.js CHANGED
@@ -38,7 +38,20 @@ const PROVIDERS = {
38
38
  'textDocument/prepareCallHierarchy': 'callHierarchyProvider',
39
39
  'callHierarchy/incomingCalls': 'callHierarchyProvider',
40
40
  'callHierarchy/outgoingCalls': 'callHierarchyProvider',
41
- 'textDocument/diagnostic': 'diagnosticProvider'
41
+ 'textDocument/diagnostic': 'diagnosticProvider',
42
+ 'workspace/diagnostic': 'diagnosticProvider',
43
+ 'textDocument/documentLink': 'documentLinkProvider',
44
+ 'documentLink/resolve': 'documentLinkProvider',
45
+ 'textDocument/documentColor': 'colorProvider',
46
+ 'textDocument/colorPresentation': 'colorProvider',
47
+ 'textDocument/linkedEditingRange': 'linkedEditingRangeProvider',
48
+ 'textDocument/moniker': 'monikerProvider',
49
+ 'textDocument/prepareTypeHierarchy': 'typeHierarchyProvider',
50
+ 'typeHierarchy/supertypes': 'typeHierarchyProvider',
51
+ 'typeHierarchy/subtypes': 'typeHierarchyProvider',
52
+ 'textDocument/inlineValue': 'inlineValueProvider',
53
+ 'textDocument/inlineCompletion': 'inlineCompletionProvider',
54
+ 'textDocument/rangesFormatting': 'documentRangeFormattingProvider'
42
55
  };
43
56
  /* A method whose dynamic registration is made under the name of another. */
44
57
  const REGISTRATION_METHODS = {
@@ -52,9 +65,23 @@ const REGISTRATION_METHODS = {
52
65
  'workspaceSymbol/resolve': 'workspace/symbol',
53
66
  'textDocument/semanticTokens/full': 'textDocument/semanticTokens',
54
67
  'textDocument/semanticTokens/full/delta': 'textDocument/semanticTokens',
55
- 'textDocument/semanticTokens/range': 'textDocument/semanticTokens'
68
+ 'textDocument/semanticTokens/range': 'textDocument/semanticTokens',
69
+ 'documentLink/resolve': 'textDocument/documentLink',
70
+ 'textDocument/colorPresentation': 'textDocument/documentColor',
71
+ 'typeHierarchy/supertypes': 'textDocument/prepareTypeHierarchy',
72
+ 'typeHierarchy/subtypes': 'textDocument/prepareTypeHierarchy',
73
+ 'textDocument/rangesFormatting': 'textDocument/rangeFormatting',
74
+ 'workspace/diagnostic': 'textDocument/diagnostic'
56
75
  };
57
- const REFRESH_REQUESTS = ['workspace/semanticTokens/refresh', 'workspace/inlayHint/refresh', 'workspace/diagnostic/refresh', 'workspace/codeLens/refresh'];
76
+ const REFRESH_REQUESTS = [
77
+ 'workspace/semanticTokens/refresh',
78
+ 'workspace/inlayHint/refresh',
79
+ 'workspace/diagnostic/refresh',
80
+ 'workspace/codeLens/refresh',
81
+ 'workspace/inlineValue/refresh',
82
+ 'workspace/foldingRange/refresh',
83
+ 'workspace/textDocumentContent/refresh'
84
+ ];
58
85
  function matches(options, document) {
59
86
  if (options.documentSelector == null) {
60
87
  return true;
@@ -84,6 +111,12 @@ function offersMethod(method, options) {
84
111
  if (method === 'textDocument/semanticTokens/range') {
85
112
  return !!options.range;
86
113
  }
114
+ if (method === 'textDocument/rangesFormatting') {
115
+ return options.rangesSupport === true;
116
+ }
117
+ if (method === 'workspace/diagnostic') {
118
+ return options.workspaceDiagnostics === true;
119
+ }
87
120
  return true;
88
121
  }
89
122
  /* One conversation with one language server: the handshake, what it can do, and the documents open in it. */
@@ -100,6 +133,7 @@ export class LspSession {
100
133
  diagnosticListeners = new Set();
101
134
  capabilityListeners = new Set();
102
135
  configuration;
136
+ folders;
103
137
  constructor(transport, options = {}) {
104
138
  this.options = options;
105
139
  this.connection = new JsonRpcConnection(transport, options.timeoutMs);
@@ -122,9 +156,15 @@ export class LspSession {
122
156
  const { items } = params;
123
157
  return Promise.all(items.map((item) => (options.onConfiguration ? options.onConfiguration(item) : this.readConfiguration(item.section))));
124
158
  });
159
+ this.folders = options.workspaceFolders ?? (options.rootUri ? [{ uri: options.rootUri, name: 'Workspace' }] : null);
125
160
  this.connection.onRequest('workspace/workspaceFolders', () => this.workspaceFolders);
126
161
  this.connection.onRequest('workspace/applyEdit', (params) => options.onApplyEdit?.(params) ?? { applied: false, failureReason: 'No workspace edit handler is attached' });
127
162
  this.connection.onRequest('window/showMessageRequest', (params) => options.onShowMessage?.(params) ?? null);
163
+ this.connection.onRequest('window/showDocument', (params) => options.onShowDocument?.(params) ?? { success: false });
164
+ this.connection.onNotification('window/showMessage', (params) => options.onMessage?.(params));
165
+ this.connection.onNotification('window/logMessage', (params) => options.onLog?.(params));
166
+ this.connection.onNotification('$/logTrace', (params) => options.onTrace?.(params));
167
+ this.connection.onNotification('telemetry/event', (params) => options.onTelemetry?.(params));
128
168
  this.connection.onRequest('window/workDoneProgress/create', () => null);
129
169
  this.connection.onRequest('client/registerCapability', (params) => {
130
170
  const { registrations } = params;
@@ -157,7 +197,7 @@ export class LspSession {
157
197
  return this.serverCapabilities;
158
198
  }
159
199
  get workspaceFolders() {
160
- return this.options.workspaceFolders ?? (this.options.rootUri ? [{ uri: this.options.rootUri, name: 'Workspace' }] : null);
200
+ return this.folders === null ? null : [...this.folders];
161
201
  }
162
202
  get syncOptions() {
163
203
  const sync = this.serverCapabilities.textDocumentSync;
@@ -178,6 +218,8 @@ export class LspSession {
178
218
  rootUri: this.options.rootUri ?? null,
179
219
  workspaceFolders: this.workspaceFolders,
180
220
  clientInfo: this.options.clientInfo ?? { name: '@adecore/lsp' },
221
+ locale: this.options.locale,
222
+ trace: this.options.trace ?? 'off',
181
223
  capabilities: clientCapabilities(this.options),
182
224
  initializationOptions: this.options.initializationOptions
183
225
  });
@@ -222,7 +264,14 @@ export class LspSession {
222
264
  candidates.push(registration.registerOptions ?? {});
223
265
  }
224
266
  }
225
- const provider = this.serverCapabilities[PROVIDERS[method]];
267
+ if (method === 'textDocument/willSaveWaitUntil' && this.syncOptions.willSaveWaitUntil) {
268
+ candidates.push({});
269
+ }
270
+ const content = this.serverCapabilities.workspace?.textDocumentContent;
271
+ if (method === 'workspace/textDocumentContent' && content !== undefined) {
272
+ candidates.push({ schemes: content.schemes });
273
+ }
274
+ const provider = PROVIDERS[method] === undefined ? undefined : this.serverCapabilities[PROVIDERS[method]];
226
275
  if (provider === true) {
227
276
  candidates.push({});
228
277
  }
@@ -260,6 +309,87 @@ export class LspSession {
260
309
  }
261
310
  return this.request('workspaceSymbol/resolve', symbol, options);
262
311
  }
312
+ /*
313
+ * The diagnostics of every file of the project, for a server that offers them. `previousResultIds` are what the
314
+ * client last heard per document, so the server can answer `unchanged`; `onPartialResult` streams reports while it works.
315
+ */
316
+ workspaceDiagnostics(previousResultIds = [], identifier, options) {
317
+ if (!this.supports('workspace/diagnostic')) {
318
+ return Promise.reject(new LspError('Server does not support workspace diagnostics', ErrorCodes.MethodNotFound));
319
+ }
320
+ return this.request('workspace/diagnostic', { identifier, previousResultIds: [...previousResultIds] }, options);
321
+ }
322
+ /* The text of a document of a scheme the server owns, such as a decompiled class, which is shown read-only. */
323
+ textDocumentContent(uri, options) {
324
+ const scheme = uri.slice(0, Math.max(0, uri.indexOf(':')));
325
+ if (!this.providerOptions('workspace/textDocumentContent')?.schemes?.includes(scheme)) {
326
+ return Promise.reject(new LspError(`Server does not serve documents of ${scheme}:`, ErrorCodes.MethodNotFound));
327
+ }
328
+ return this.request('workspace/textDocumentContent', { uri }, options);
329
+ }
330
+ /* Changes the trace level the server sends `$/logTrace` at. */
331
+ setTrace(value) {
332
+ return this.notify('$/setTrace', { value });
333
+ }
334
+ /* Asks the server to stop work it reported as cancellable under `token`. */
335
+ cancelWorkDoneProgress(token) {
336
+ return this.notify('window/workDoneProgress/cancel', { token });
337
+ }
338
+ /* Adds and removes folders of the workspace, and tells a server that asked to hear about it. */
339
+ async changeWorkspaceFolders(event) {
340
+ const removed = new Set(event.removed.map((folder) => fileUriKey(folder.uri)));
341
+ const kept = (this.folders ?? []).filter((folder) => !removed.has(fileUriKey(folder.uri)));
342
+ const known = new Set(kept.map((folder) => fileUriKey(folder.uri)));
343
+ this.folders = [...kept, ...event.added.filter((folder) => !known.has(fileUriKey(folder.uri)))];
344
+ const notifications = this.serverCapabilities.workspace?.workspaceFolders?.changeNotifications;
345
+ const registered = [...this.registrations.values()].some((registration) => registration.method === 'workspace/didChangeWorkspaceFolders');
346
+ if (this.phase === 'ready' && (notifications === true || typeof notifications === 'string' || registered)) {
347
+ await this.notify('workspace/didChangeWorkspaceFolders', { event: { added: [...event.added], removed: [...event.removed] } });
348
+ }
349
+ }
350
+ /* Whether the server synchronizes notebooks of this type and scheme; the `notebook*` methods do nothing for one it does not. */
351
+ syncsNotebook(notebook) {
352
+ const selectors = this.serverCapabilities.notebookDocumentSync?.notebookSelector ?? [];
353
+ const scheme = notebook.uri.slice(0, Math.max(0, notebook.uri.indexOf(':')));
354
+ return selectors.some(({ notebook: filter }) => {
355
+ if (filter === undefined) {
356
+ return true;
357
+ }
358
+ if (typeof filter === 'string') {
359
+ return filter === '*' || filter === notebook.notebookType;
360
+ }
361
+ return ((!filter.notebookType || filter.notebookType === notebook.notebookType) &&
362
+ (!filter.scheme || filter.scheme === scheme) &&
363
+ (!filter.pattern || globMatch(decodeURIComponent(new URL(notebook.uri).pathname), filter.pattern)));
364
+ });
365
+ }
366
+ /* Opens a notebook with the documents of its cells, which do not go through `openDocument`. */
367
+ async openNotebook(notebook, cellTextDocuments) {
368
+ if (this.syncsNotebook(notebook)) {
369
+ await this.notify('notebookDocument/didOpen', {
370
+ notebookDocument: notebook,
371
+ cellTextDocuments: cellTextDocuments.map((item) => ({ ...item, version: item.version ?? 0 }))
372
+ });
373
+ }
374
+ }
375
+ async changeNotebook(notebook, change) {
376
+ if (this.syncsNotebook(notebook)) {
377
+ await this.notify('notebookDocument/didChange', { notebookDocument: { uri: notebook.uri, version: notebook.version }, change });
378
+ }
379
+ }
380
+ async saveNotebook(notebook) {
381
+ if (this.syncsNotebook(notebook) && this.serverCapabilities.notebookDocumentSync?.save === true) {
382
+ await this.notify('notebookDocument/didSave', { notebookDocument: { uri: notebook.uri } });
383
+ }
384
+ }
385
+ async closeNotebook(notebook, cellUris) {
386
+ if (this.syncsNotebook(notebook)) {
387
+ await this.notify('notebookDocument/didClose', {
388
+ notebookDocument: { uri: notebook.uri },
389
+ cellTextDocuments: cellUris.map((uri) => ({ uri }))
390
+ });
391
+ }
392
+ }
263
393
  async setConfiguration(settings) {
264
394
  this.configuration = settings;
265
395
  await this.notify('workspace/didChangeConfiguration', { settings });
@@ -418,7 +548,13 @@ function clientCapabilities(options) {
418
548
  const dynamic = { dynamicRegistration: true };
419
549
  const symbolKind = { valueSet: Array.from({ length: 26 }, (_, index) => index + 1) };
420
550
  return {
421
- general: { positionEncodings: ['utf-16'] },
551
+ general: {
552
+ positionEncodings: ['utf-16'],
553
+ staleRequestSupport: { cancel: true, retryOnContentModified: [] },
554
+ regularExpressions: { engine: 'ECMAScript', version: 'ES2020' },
555
+ markdown: { parser: 'remark', version: '15' }
556
+ },
557
+ notebookDocument: { synchronization: { dynamicRegistration: false, executionSummarySupport: true } },
422
558
  workspace: {
423
559
  applyEdit: !!options.onApplyEdit,
424
560
  workspaceEdit: { documentChanges: true, resourceOperations: [...(options.resourceOperations ?? ['create', 'rename'])], failureHandling: 'abort' },
@@ -439,11 +575,18 @@ function clientCapabilities(options) {
439
575
  semanticTokens: { refreshSupport: true },
440
576
  inlayHint: { refreshSupport: true },
441
577
  codeLens: { refreshSupport: true },
442
- diagnostics: { refreshSupport: true }
578
+ diagnostics: { refreshSupport: true },
579
+ inlineValue: { refreshSupport: true },
580
+ foldingRange: { refreshSupport: true },
581
+ textDocumentContent: { dynamicRegistration: false }
582
+ },
583
+ window: {
584
+ workDoneProgress: true,
585
+ showMessage: { messageActionItem: { additionalPropertiesSupport: false } },
586
+ showDocument: { support: !!options.onShowDocument }
443
587
  },
444
- window: { workDoneProgress: true, showMessage: { messageActionItem: { additionalPropertiesSupport: false } } },
445
588
  textDocument: {
446
- synchronization: { dynamicRegistration: false, didSave: true },
589
+ synchronization: { dynamicRegistration: false, willSave: true, willSaveWaitUntil: true, didSave: true },
447
590
  publishDiagnostics: {
448
591
  versionSupport: true,
449
592
  relatedInformation: true,
@@ -455,6 +598,7 @@ function clientCapabilities(options) {
455
598
  ...dynamic,
456
599
  contextSupport: true,
457
600
  insertTextMode: 1,
601
+ completionItemKind: { valueSet: Array.from({ length: 25 }, (_, index) => index + 1) },
458
602
  ...(options.completionItemDefaults?.length ? { completionList: { itemDefaults: [...options.completionItemDefaults] } } : {}),
459
603
  completionItem: {
460
604
  snippetSupport: options.snippetSupport ?? false,
@@ -462,6 +606,7 @@ function clientCapabilities(options) {
462
606
  commitCharactersSupport: true,
463
607
  documentationFormat: ['markdown', 'plaintext'],
464
608
  deprecatedSupport: true,
609
+ tagSupport: { valueSet: [1] },
465
610
  preselectSupport: true,
466
611
  insertReplaceSupport: true,
467
612
  labelDetailsSupport: true,
@@ -509,11 +654,23 @@ function clientCapabilities(options) {
509
654
  },
510
655
  resolveSupport: { properties: ['edit', 'command'] }
511
656
  },
512
- codeLens: dynamic,
657
+ codeLens: { ...dynamic, resolveSupport: { properties: ['command'] } },
513
658
  formatting: dynamic,
514
- rangeFormatting: dynamic,
659
+ rangeFormatting: { ...dynamic, rangesSupport: true },
515
660
  onTypeFormatting: dynamic,
516
- foldingRange: { ...dynamic, lineFoldingOnly: false, foldingRange: { collapsedText: false } },
661
+ foldingRange: {
662
+ ...dynamic,
663
+ lineFoldingOnly: false,
664
+ foldingRangeKind: { valueSet: ['comment', 'imports', 'region'] },
665
+ foldingRange: { collapsedText: false }
666
+ },
667
+ documentLink: { ...dynamic, tooltipSupport: true },
668
+ colorProvider: dynamic,
669
+ linkedEditingRange: dynamic,
670
+ moniker: dynamic,
671
+ typeHierarchy: dynamic,
672
+ inlineValue: dynamic,
673
+ inlineCompletion: dynamic,
517
674
  selectionRange: dynamic,
518
675
  semanticTokens: {
519
676
  ...dynamic,
@@ -0,0 +1,2 @@
1
+ import type { LanguageService } from './service.ts';
2
+ export declare function unservedLanguageService(): LanguageService;
@@ -0,0 +1,53 @@
1
+ import { ErrorCodes, LspError } from './connection.js';
2
+ const REQUESTS = [
3
+ 'completion',
4
+ 'resolveCompletion',
5
+ 'hover',
6
+ 'signatureHelp',
7
+ 'definition',
8
+ 'declaration',
9
+ 'typeDefinition',
10
+ 'implementation',
11
+ 'references',
12
+ 'documentHighlights',
13
+ 'documentSymbols',
14
+ 'prepareRename',
15
+ 'rename',
16
+ 'codeActions',
17
+ 'resolveCodeAction',
18
+ 'executeCommand',
19
+ 'formatting',
20
+ 'rangeFormatting',
21
+ 'semanticTokens',
22
+ 'semanticTokensDelta',
23
+ 'semanticTokensRange',
24
+ 'inlayHints',
25
+ 'resolveInlayHint',
26
+ 'workspaceSymbols',
27
+ 'foldingRanges',
28
+ 'selectionRanges',
29
+ 'codeLenses',
30
+ 'resolveCodeLens'
31
+ ];
32
+ const NOTHING = { dispose: () => undefined };
33
+ function refuse() {
34
+ return Promise.reject(new LspError('No language server serves this document', ErrorCodes.MethodNotFound));
35
+ }
36
+ /*
37
+ * A service for documents no server serves: it takes documents and answers `supports` with false, so a
38
+ * feature that checks first never asks, and rejects every request with an `LspError` of code -32601.
39
+ */
40
+ export function unservedLanguageService() {
41
+ // Every request has the same refusal whatever its parameters, which the record type cannot say.
42
+ const requests = Object.fromEntries(REQUESTS.map((name) => [name, refuse]));
43
+ return {
44
+ ...requests,
45
+ openDocument: () => Promise.resolve(),
46
+ changeDocument: () => Promise.resolve(),
47
+ closeDocument: () => Promise.resolve(),
48
+ supports: () => false,
49
+ providerOptions: () => undefined,
50
+ onProvidersChanged: () => NOTHING,
51
+ onDiagnostics: () => NOTHING
52
+ };
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/lsp",
3
- "version": "0.41.1",
3
+ "version": "0.43.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/connection.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { asError } from './as-error.ts';
2
- import type { Disposable, LspTransport, RequestId, RequestOptions, RpcMessage, RpcRequest } from './protocol.ts';
2
+ import type { Disposable, LspTransport, RequestId, RequestOptions, RpcMessage, RpcRequest, WorkDoneProgress } from './protocol.ts';
3
3
 
4
4
  /* The JSON-RPC and LSP codes this package answers with. */
5
5
  export const ErrorCodes = {
@@ -89,7 +89,7 @@ export class JsonRpcConnection {
89
89
  }
90
90
 
91
91
  const id = ++this.nextId;
92
-
92
+ const progress = this.progressOf(id, params, options);
93
93
  return new Promise<T>((resolve, reject) => {
94
94
  let timer: ReturnType<typeof setTimeout> | undefined;
95
95
 
@@ -110,6 +110,7 @@ export class JsonRpcConnection {
110
110
  cleanup: () => {
111
111
  clearTimeout(timer);
112
112
  options.signal?.removeEventListener('abort', abort);
113
+ progress.listening?.dispose();
113
114
  }
114
115
  });
115
116
 
@@ -120,8 +121,34 @@ export class JsonRpcConnection {
120
121
  timer = setTimeout(() => cancel(new LspError(`LSP request timed out: ${method}`, ErrorCodes.RequestCancelled)), timeout);
121
122
  }
122
123
 
123
- void this.send({ jsonrpc: '2.0', id, method, params }).catch((error) => this.settle(id, undefined, error));
124
+ void this.send({ jsonrpc: '2.0', id, method, params: progress.params }).catch((error) => this.settle(id, undefined, error));
125
+ });
126
+ }
127
+
128
+ /* Adds the tokens of partial results and work-done progress to the params of a request, and hears `$/progress` for them. */
129
+ private progressOf(id: RequestId, params: unknown, options: RequestOptions): { params: unknown; listening?: Disposable } {
130
+ const { onPartialResult, onWorkDoneProgress } = options;
131
+ if ((onPartialResult === undefined && onWorkDoneProgress === undefined) || params === null || typeof params !== 'object' || Array.isArray(params)) {
132
+ return { params };
133
+ }
134
+ const partialResultToken = onPartialResult === undefined ? undefined : `adecore-partial-${id}`;
135
+ const workDoneToken = onWorkDoneProgress === undefined ? undefined : `adecore-work-${id}`;
136
+ const listening = this.onNotification('$/progress', (value) => {
137
+ const { token, value: report } = value as { token: unknown; value: unknown };
138
+ if (token === partialResultToken) {
139
+ onPartialResult?.(report);
140
+ } else if (token === workDoneToken) {
141
+ onWorkDoneProgress?.(report as WorkDoneProgress);
142
+ }
124
143
  });
144
+ return {
145
+ params: {
146
+ ...params,
147
+ ...(partialResultToken === undefined ? {} : { partialResultToken }),
148
+ ...(workDoneToken === undefined ? {} : { workDoneToken })
149
+ },
150
+ listening
151
+ };
125
152
  }
126
153
 
127
154
  notify(method: string, params?: unknown): Promise<void> {
package/src/document.ts CHANGED
@@ -9,6 +9,9 @@ import type {
9
9
  CodeAction,
10
10
  CodeActionContext,
11
11
  CodeLens,
12
+ Color,
13
+ ColorInformation,
14
+ ColorPresentation,
12
15
  Command,
13
16
  CompletionContext,
14
17
  CompletionItem,
@@ -17,13 +20,20 @@ import type {
17
20
  DocumentDiagnosticReport,
18
21
  DocumentHighlight,
19
22
  DocumentItem,
23
+ DocumentLink,
20
24
  DocumentRequestOptions,
21
25
  DocumentSymbolResult,
22
26
  FoldingRange,
23
27
  FormattingOptions,
24
28
  Hover,
25
29
  InlayHint,
30
+ InlineCompletionContext,
31
+ InlineCompletionResult,
32
+ InlineValue,
33
+ InlineValueContext,
34
+ LinkedEditingRanges,
26
35
  Location,
36
+ Moniker,
27
37
  NavigationResult,
28
38
  Position,
29
39
  PrepareRenameResult,
@@ -33,7 +43,9 @@ import type {
33
43
  SemanticTokensDelta,
34
44
  SignatureHelp,
35
45
  SignatureHelpContext,
46
+ TextDocumentSaveReason,
36
47
  TextEdit,
48
+ TypeHierarchyItem,
37
49
  WorkspaceEdit
38
50
  } from './protocol.ts';
39
51
 
@@ -286,6 +298,72 @@ export class LspDocument {
286
298
  return this.feature('textDocument/diagnostic', { previousResultId, identifier }, options);
287
299
  }
288
300
 
301
+ documentLinks(options?: DocumentRequestOptions): Promise<DocumentLink[] | null> {
302
+ return this.feature('textDocument/documentLink', {}, options);
303
+ }
304
+
305
+ resolveDocumentLink(link: DocumentLink, options?: DocumentRequestOptions): Promise<DocumentLink> {
306
+ return this.resolve('documentLink/resolve', link, options);
307
+ }
308
+
309
+ documentColors(options?: DocumentRequestOptions): Promise<ColorInformation[] | null> {
310
+ return this.feature('textDocument/documentColor', {}, options);
311
+ }
312
+
313
+ colorPresentations(color: Color, range: Range, options?: DocumentRequestOptions): Promise<ColorPresentation[] | null> {
314
+ return this.feature('textDocument/colorPresentation', { color, range }, options);
315
+ }
316
+
317
+ linkedEditingRanges(position: Position, options?: DocumentRequestOptions): Promise<LinkedEditingRanges | null> {
318
+ return this.feature('textDocument/linkedEditingRange', { position }, options);
319
+ }
320
+
321
+ monikers(position: Position, options?: DocumentRequestOptions): Promise<Moniker[] | null> {
322
+ return this.feature('textDocument/moniker', { position }, options);
323
+ }
324
+
325
+ prepareTypeHierarchy(position: Position, options?: DocumentRequestOptions): Promise<TypeHierarchyItem[] | null> {
326
+ return this.feature('textDocument/prepareTypeHierarchy', { position }, options);
327
+ }
328
+
329
+ /* Like the calls of a call hierarchy, expansions of the tree run beside each other unless `cancelPrevious` says otherwise. */
330
+ supertypes(item: TypeHierarchyItem, options?: DocumentRequestOptions): Promise<TypeHierarchyItem[] | null> {
331
+ return this.perform('typeHierarchy/supertypes', { item }, { cancelPrevious: false, ...options });
332
+ }
333
+
334
+ subtypes(item: TypeHierarchyItem, options?: DocumentRequestOptions): Promise<TypeHierarchyItem[] | null> {
335
+ return this.perform('typeHierarchy/subtypes', { item }, { cancelPrevious: false, ...options });
336
+ }
337
+
338
+ inlineValues(range: Range, context: InlineValueContext, options?: DocumentRequestOptions): Promise<InlineValue[] | null> {
339
+ return this.feature('textDocument/inlineValue', { range, context }, options);
340
+ }
341
+
342
+ inlineCompletions(position: Position, context: InlineCompletionContext, options?: DocumentRequestOptions): Promise<InlineCompletionResult> {
343
+ return this.feature('textDocument/inlineCompletion', { position, context }, options);
344
+ }
345
+
346
+ rangesFormatting(ranges: readonly Range[], formatting: FormattingOptions, options?: DocumentRequestOptions): Promise<TextEdit[] | null> {
347
+ return this.feature('textDocument/rangesFormatting', { ranges: [...ranges], options: formatting }, options);
348
+ }
349
+
350
+ /* Tells the server a save is coming, when it asked to hear it. */
351
+ async willSave(reason: TextDocumentSaveReason = 1): Promise<void> {
352
+ this.assertOpen();
353
+ if (this.session.syncOptions.willSave) {
354
+ await this.session.notify('textDocument/willSave', { textDocument: { uri: this.uri }, reason });
355
+ }
356
+ }
357
+
358
+ /* The edits the server wants in the text before it is saved, or null when it did not ask to be told. Apply them, then save. */
359
+ async willSaveWaitUntil(reason: TextDocumentSaveReason = 1, options?: DocumentRequestOptions): Promise<TextEdit[] | null> {
360
+ this.assertOpen();
361
+ if (!this.session.syncOptions.willSaveWaitUntil) {
362
+ return null;
363
+ }
364
+ return this.perform('textDocument/willSaveWaitUntil', { textDocument: { uri: this.uri }, reason }, options);
365
+ }
366
+
289
367
  /* Any feature by its LSP method, for a caller that gets the method from elsewhere. `params` leave out `textDocument`; a resolve method takes the item. */
290
368
  request<T = unknown>(method: string, params: object, options?: DocumentRequestOptions): Promise<T> {
291
369
  return method.endsWith('/resolve') ? (this.resolve(method, params, options) as Promise<T>) : this.feature<T>(method, params, options);
@@ -367,7 +445,14 @@ export class LspDocument {
367
445
  this.sendFullText?.();
368
446
  await this.synced;
369
447
  this.assertCurrent(version);
370
- const result = await this.session.request<T>(method, params, { signal: controller.signal, timeoutMs: options.timeoutMs });
448
+ const { onPartialResult, onWorkDoneProgress } = options;
449
+ const result = await this.session.request<T>(method, params, {
450
+ signal: controller.signal,
451
+ timeoutMs: options.timeoutMs,
452
+ // A part for a text that moved on is as stale as the answer would be.
453
+ onPartialResult: onPartialResult && ((value) => this.version === version && !this.closed && onPartialResult(value)),
454
+ onWorkDoneProgress
455
+ });
371
456
  this.assertCurrent(version);
372
457
  this.remember(result, version);
373
458
 
package/src/index.ts CHANGED
@@ -18,7 +18,8 @@ export { type CreatedFile, type DeletedFile, type RenamedFile } from './file-ope
18
18
  export { watchesFile, type FileChangeType } from './watched-files.ts';
19
19
  export { LspSession, type LspSessionOptions, type SessionState } from './session.ts';
20
20
  export { TextMirror } from './text-mirror.ts';
21
- export type { DiagnosticsReport, LanguageDocument, LanguageRequestOptions, LanguageService } from './service.ts';
21
+ export type { DiagnosticsReport, LanguageDocument, LanguageRequestOptions, LanguageService, ServerMessage } from './service.ts';
22
+ export { unservedLanguageService } from './unserved.ts';
22
23
  export { connectWebSocket, createStreamTransport, type ByteStream, type WebSocketTransportOptions } from './transport.ts';
23
24
  export { fileUriToPath, pathToFileUri } from './uris.ts';
24
25
  export { bridgeVueTypeScript, vueServerOrder, type VueServer } from './vue.ts';