Skip to content

Commit 169779d

Browse files
committed
feat(lsp): LSP 3.18
- inlineCompletion Closes #5071
1 parent dce5ae5 commit 169779d

37 files changed

+866
-474
lines changed

package-lock.json

+27-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"eslint-plugin-jsdoc": "^48.11.0",
8282
"jest": "29.7.0",
8383
"typescript": "^5.5.4",
84-
"vscode-languageserver": "^9.0.1"
84+
"vscode-languageserver": "^10.0.0-next.11"
8585
},
8686
"dependencies": {
8787
"@chemzqm/msgpack-lite": "^0.1.29",
@@ -108,9 +108,9 @@
108108
"unidecode": "^1.0.1",
109109
"unzip-stream": "^0.3.4",
110110
"uuid": "^9.0.1",
111-
"vscode-languageserver-protocol": "^3.17.5",
112-
"vscode-languageserver-textdocument": "^1.0.11",
113-
"vscode-languageserver-types": "^3.17.5",
111+
"vscode-languageserver-protocol": "^3.17.6-next.11",
112+
"vscode-languageserver-textdocument": "^1.0.12",
113+
"vscode-languageserver-types": "^3.17.6-next.5",
114114
"vscode-uri": "^3.0.8",
115115
"which": "^4.0.0"
116116
}

src/__tests__/client/features.test.ts

+120-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as assert from 'assert'
22
import path from 'path'
3-
import { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyPrepareRequest, CancellationToken, CancellationTokenSource, CodeAction, CodeActionRequest, CodeLensRequest, Color, ColorInformation, ColorPresentation, CompletionItem, CompletionRequest, CompletionTriggerKind, ConfigurationRequest, DeclarationRequest, DefinitionRequest, DidChangeConfigurationNotification, DidChangeTextDocumentNotification, DidChangeWatchedFilesNotification, DidCloseTextDocumentNotification, DidCreateFilesNotification, DidDeleteFilesNotification, DidOpenTextDocumentNotification, DidRenameFilesNotification, DidSaveTextDocumentNotification, Disposable, DocumentColorRequest, DocumentDiagnosticReport, DocumentDiagnosticReportKind, DocumentDiagnosticRequest, DocumentFormattingRequest, DocumentHighlight, DocumentHighlightKind, DocumentHighlightRequest, DocumentLink, DocumentLinkRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest, DocumentSelector, DocumentSymbolRequest, FoldingRange, FoldingRangeRequest, FullDocumentDiagnosticReport, Hover, HoverRequest, ImplementationRequest, InlayHintKind, InlayHintLabelPart, InlayHintRequest, InlineValueEvaluatableExpression, InlineValueRequest, InlineValueText, InlineValueVariableLookup, LinkedEditingRangeRequest, Location, NotificationType0, ParameterInformation, Position, ProgressToken, ProtocolRequestType, Range, ReferencesRequest, RenameRequest, SelectionRange, SelectionRangeRequest, SemanticTokensRegistrationType, SignatureHelpRequest, SignatureHelpTriggerKind, SignatureInformation, TextDocumentEdit, TextDocumentSyncKind, TextEdit, TypeDefinitionRequest, TypeHierarchyPrepareRequest, WillCreateFilesRequest, WillDeleteFilesRequest, WillRenameFilesRequest, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest, WorkDoneProgressBegin, WorkDoneProgressCreateRequest, WorkDoneProgressEnd, WorkDoneProgressReport, WorkspaceEdit, WorkspaceSymbolRequest } from 'vscode-languageserver-protocol'
3+
import { ApplyWorkspaceEditParams, CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyPrepareRequest, CancellationToken, CancellationTokenSource, CodeAction, CodeActionRequest, CodeLensRequest, Color, ColorInformation, ColorPresentation, CompletionItem, CompletionRequest, CompletionTriggerKind, ConfigurationRequest, DeclarationRequest, DefinitionRequest, DidChangeConfigurationNotification, DidChangeTextDocumentNotification, DidChangeWatchedFilesNotification, DidCloseTextDocumentNotification, DidCreateFilesNotification, DidDeleteFilesNotification, DidOpenTextDocumentNotification, DidRenameFilesNotification, DidSaveTextDocumentNotification, Disposable, DocumentColorRequest, DocumentDiagnosticReport, DocumentDiagnosticReportKind, DocumentDiagnosticRequest, DocumentFormattingRequest, DocumentHighlight, DocumentHighlightKind, DocumentHighlightRequest, DocumentLink, DocumentLinkRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest, DocumentSelector, DocumentSymbolRequest, FoldingRange, FoldingRangeRequest, FullDocumentDiagnosticReport, Hover, HoverRequest, ImplementationRequest, InlayHintKind, InlayHintLabelPart, InlayHintRequest, InlineCompletionItem, InlineCompletionRequest, InlineValueEvaluatableExpression, InlineValueRequest, InlineValueText, InlineValueVariableLookup, LinkedEditingRangeRequest, Location, NotificationType0, ParameterInformation, Position, ProgressToken, ProtocolRequestType, Range, ReferencesRequest, RenameRequest, SelectionRange, SelectionRangeRequest, SemanticTokensRegistrationType, SignatureHelpRequest, SignatureHelpTriggerKind, SignatureInformation, TextDocumentEdit, TextDocumentSyncKind, TextEdit, TypeDefinitionRequest, TypeHierarchyPrepareRequest, WillCreateFilesRequest, WillDeleteFilesRequest, WillRenameFilesRequest, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest, WorkDoneProgressBegin, WorkDoneProgressCreateRequest, WorkDoneProgressEnd, WorkDoneProgressReport, WorkspaceEdit, WorkspaceSymbolRequest } from 'vscode-languageserver-protocol'
44
import { TextDocument } from 'vscode-languageserver-textdocument'
55
import { URI } from 'vscode-uri'
66
import commands from '../../commands'
@@ -97,11 +97,12 @@ describe('Client integration', () => {
9797
run: { module: serverModule, transport: TransportKind.ipc },
9898
debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }
9999
}
100-
const documentSelector: DocumentSelector = [{ scheme: 'lsptests' }]
100+
const documentSelector: DocumentSelector = [{ scheme: 'lsptests', language: 'bat' }]
101101

102102
middleware = {}
103103
const clientOptions: LanguageClientOptions = {
104-
documentSelector, synchronize: {}, initializationOptions: {}, middleware
104+
documentSelector, synchronize: {}, initializationOptions: {}, middleware,
105+
workspaceFolder: { name: 'test_folder', uri: URI.parse('file-test:///').toString() },
105106
}
106107

107108
client = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)
@@ -137,7 +138,9 @@ describe('Client integration', () => {
137138
resolveProvider: true
138139
},
139140
documentFormattingProvider: true,
140-
documentRangeFormattingProvider: true,
141+
documentRangeFormattingProvider: {
142+
rangesSupport: true
143+
},
141144
documentOnTypeFormattingProvider: {
142145
firstTriggerCharacter: ':'
143146
},
@@ -147,13 +150,15 @@ describe('Client integration', () => {
147150
documentLinkProvider: {
148151
resolveProvider: true
149152
},
153+
documentSymbolProvider: true,
150154
colorProvider: true,
151155
declarationProvider: true,
152156
foldingRangeProvider: true,
153157
implementationProvider: {
154158
documentSelector: [{ language: '*' }]
155159
},
156160
selectionRangeProvider: true,
161+
inlineCompletionProvider: {},
157162
inlineValueProvider: {},
158163
inlayHintProvider: {
159164
resolveProvider: true
@@ -270,6 +275,7 @@ describe('Client integration', () => {
270275
testFeature(SemanticTokensRegistrationType.method, 'document')
271276
testFeature(LinkedEditingRangeRequest.method, 'document')
272277
testFeature(TypeHierarchyPrepareRequest.method, 'document')
278+
testFeature(InlineCompletionRequest.method, 'document')
273279
testFeature(InlineValueRequest.method, 'document')
274280
testFeature(InlayHintRequest.method, 'document')
275281
testFeature(WorkspaceSymbolRequest.method, 'workspace')
@@ -538,6 +544,40 @@ describe('Client integration', () => {
538544
)
539545
})
540546

547+
test('Progress percentage is an integer', async () => {
548+
const progressToken = 'TEST-PROGRESS-PERCENTAGE'
549+
const percentages: Array<number | undefined> = []
550+
let currentProgressResolver: (value: unknown) => void | undefined
551+
552+
// Set up middleware that calls the current resolve function when it gets its 'end' progress event.
553+
middleware.handleWorkDoneProgress = (token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, next) => {
554+
if (token === progressToken) {
555+
const percentage = params.kind === 'report' || params.kind === 'begin' ? params.percentage : undefined
556+
percentages.push(percentage)
557+
558+
if (params.kind === 'end') {
559+
setImmediate(currentProgressResolver)
560+
}
561+
}
562+
return next(token, params)
563+
}
564+
565+
// Trigger a progress event.
566+
await new Promise<unknown>(resolve => {
567+
currentProgressResolver = resolve
568+
void client.sendRequest(
569+
new ProtocolRequestType<any, null, never, any, any>('testing/sendPercentageProgress'),
570+
{},
571+
tokenSource.token,
572+
)
573+
})
574+
575+
middleware.handleWorkDoneProgress = undefined
576+
577+
// Ensure percentages are rounded according to the spec
578+
assert.deepStrictEqual(percentages, [0, 50, undefined])
579+
})
580+
541581
test('Document Formatting', async () => {
542582
const provider = client.getFeature(DocumentFormattingRequest.method).getProvider(document)
543583
isDefined(provider)
@@ -822,7 +862,7 @@ describe('Client integration', () => {
822862

823863
const referenceFileUri = URI.parse('/dummy-edit')
824864
function ensureReferenceEdit(edits: WorkspaceEdit, type: string, expectedLines: string[]) {
825-
// // Ensure the edits are as expected.
865+
// Ensure the edits are as expected.
826866
assert.strictEqual(edits.documentChanges?.length, 1)
827867
const edit = edits.documentChanges[0] as TextDocumentEdit
828868
assert.strictEqual(edit.edits.length, 1)
@@ -1351,6 +1391,27 @@ describe('Client integration', () => {
13511391
}, true)
13521392
})
13531393

1394+
test('Inline Completions', async () => {
1395+
const provider = client.getFeature(InlineCompletionRequest.method)?.getProvider(document)
1396+
isDefined(provider)
1397+
const results = (await provider.provideInlineCompletionItems(document, position, { triggerKind: 1, selectedCompletionInfo: { range, text: 'text' } }, tokenSource.token)) as InlineCompletionItem[]
1398+
1399+
isArray(results, InlineCompletionItem, 1)
1400+
1401+
rangeEqual(results[0].range!, 1, 2, 3, 4)
1402+
assert.strictEqual(results[0].filterText!, 'te')
1403+
assert.strictEqual(results[0].insertText, 'text inline')
1404+
1405+
let middlewareCalled = false
1406+
middleware.provideInlineCompletionItems = (d, r, c, t, n) => {
1407+
middlewareCalled = true
1408+
return n(d, r, c, t)
1409+
}
1410+
await provider.provideInlineCompletionItems(document, position, { triggerKind: 1, selectedCompletionInfo: undefined }, tokenSource.token)
1411+
middleware.provideInlineCompletionItems = undefined
1412+
assert.strictEqual(middlewareCalled, true)
1413+
})
1414+
13541415
test('Workspace symbols', async () => {
13551416
const providers = client.getFeature(WorkspaceSymbolRequest.method).getProviders()
13561417
isDefined(providers)
@@ -1365,6 +1426,58 @@ describe('Client integration', () => {
13651426
isDefined(symbol)
13661427
rangeEqual(symbol.location['range'], 1, 2, 3, 4)
13671428
})
1429+
1430+
test('General middleware', async () => {
1431+
let middlewareCallCount = 0
1432+
// Add a general middleware for both requests and notifications
1433+
middleware.sendRequest = (type, param, token, next) => {
1434+
middlewareCallCount++
1435+
return next(type, param, token)
1436+
}
1437+
middleware.sendNotification = (type, next, params) => {
1438+
middlewareCallCount++
1439+
return next(type, params)
1440+
}
1441+
// Send a request
1442+
const definitionProvider = client.getFeature(DefinitionRequest.method).getProvider(document)
1443+
isDefined(definitionProvider)
1444+
await definitionProvider.provideDefinition(document, position, tokenSource.token)
1445+
// Send a notification
1446+
const notificationProvider = client.getFeature(DidSaveTextDocumentNotification.method).getProvider(document)
1447+
isDefined(notificationProvider)
1448+
await notificationProvider.send(document)
1449+
// Verify that both the request and notification went through the middleware
1450+
middleware.sendRequest = undefined
1451+
middleware.sendNotification = undefined
1452+
assert.strictEqual(middlewareCallCount, 2)
1453+
})
1454+
1455+
test('applyEdit middleware', async () => {
1456+
const middlewareEvents: Array<ApplyWorkspaceEditParams> = []
1457+
let currentProgressResolver: (value: unknown) => void | undefined
1458+
1459+
middleware.workspace = middleware.workspace || {}
1460+
middleware.workspace.handleApplyEdit = async (params, next) => {
1461+
middlewareEvents.push(params)
1462+
setImmediate(currentProgressResolver)
1463+
return next(params, tokenSource.token)
1464+
}
1465+
1466+
// Trigger sample applyEdit event.
1467+
await new Promise<unknown>(resolve => {
1468+
currentProgressResolver = resolve
1469+
void client.sendRequest(
1470+
new ProtocolRequestType<any, null, never, any, any>('testing/sendApplyEdit'),
1471+
{},
1472+
tokenSource.token,
1473+
)
1474+
})
1475+
1476+
middleware.workspace.handleApplyEdit = undefined
1477+
1478+
// Ensure event was handled.
1479+
assert.deepStrictEqual(middlewareEvents, [{ label: 'Apply Edit', edit: {} }])
1480+
})
13681481
})
13691482

13701483
namespace CrashNotification {
@@ -1383,9 +1496,9 @@ class CrashClient extends LanguageClient {
13831496
})
13841497
}
13851498

1386-
protected handleConnectionClosed(): void {
1387-
super.handleConnectionClosed()
1499+
protected handleConnectionClosed(): Promise<void> {
13881500
this.resolve!()
1501+
return super.handleConnectionClosed()
13891502
}
13901503
}
13911504

src/__tests__/client/integration.test.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import os from 'os'
66
import { v4 as uuid } from 'uuid'
77
import { CancellationToken, CancellationTokenSource, DidCreateFilesNotification, Disposable, ErrorCodes, InlayHintRequest, LSPErrorCodes, MessageType, ResponseError, Trace, WorkDoneProgress } from 'vscode-languageserver-protocol'
88
import { IPCMessageReader, IPCMessageWriter } from 'vscode-languageserver-protocol/node'
9-
import { Diagnostic, MarkupKind, Range } from 'vscode-languageserver-types'
9+
import { MarkupKind, Range } from 'vscode-languageserver-types'
1010
import { URI } from 'vscode-uri'
1111
import * as lsclient from '../../language-client'
12-
import { CloseAction, ErrorAction, HandleDiagnosticsSignature } from '../../language-client'
12+
import { CloseAction, ErrorAction } from '../../language-client'
1313
import { LSPCancellationError } from '../../language-client/features'
1414
import { InitializationFailedHandler } from '../../language-client/utils/errorHandler'
1515
import { disposeAll } from '../../util'
@@ -213,11 +213,11 @@ describe('Client events', () => {
213213
synchronize: {},
214214
errorHandler: {
215215
error: () => {
216-
return ErrorAction.Shutdown
216+
return { action: ErrorAction.Shutdown }
217217
},
218218
closed: () => {
219219
called = true
220-
return CloseAction.DoNotRestart
220+
return { action: CloseAction.DoNotRestart }
221221
}
222222
},
223223
initializationOptions: { initEvent: true }
@@ -234,7 +234,7 @@ describe('Client events', () => {
234234
await helper.waitValue(() => {
235235
return called
236236
}, true)
237-
client.handleConnectionError(new Error('error'), { jsonrpc: '' }, 1)
237+
void client.handleConnectionError(new Error('error'), { jsonrpc: '' }, 1)
238238
})
239239

240240
it('should handle message events', async () => {
@@ -295,9 +295,9 @@ describe('Client events', () => {
295295
synchronize: {},
296296
middleware: {
297297
window: {
298-
showDocument: async (params, next) => {
298+
showDocument: async (params, token, next) => {
299299
called = true
300-
let res = await next(params, CancellationToken.None)
300+
let res = await next(params, token)
301301
return res as any
302302
}
303303
}
@@ -437,11 +437,11 @@ describe('Client integration', () => {
437437
},
438438
stdioEncoding: 'utf8',
439439
errorHandler: {
440-
error: (): lsclient.ErrorAction => {
441-
return lsclient.ErrorAction.Continue
440+
error: () => {
441+
return { action: lsclient.ErrorAction.Continue }
442442
},
443443
closed: () => {
444-
return lsclient.CloseAction.DoNotRestart
444+
return { action: lsclient.CloseAction.DoNotRestart }
445445
}
446446
},
447447
progressOnInitialization: true,

0 commit comments

Comments
 (0)