Skip to content

Commit a41662e

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

38 files changed

+888
-480
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

+122-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'
@@ -101,7 +101,8 @@ describe('Client integration', () => {
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,12 +862,13 @@ 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)
829869
assert.strictEqual(edit.textDocument.uri, referenceFileUri.path)
830-
assert.strictEqual(edit.edits[0].newText.trim(), `${type}:\n${expectedLines.join('\n')}`.trim())
870+
const expectedTextEdit = edit.edits[0] as TextEdit
871+
assert.strictEqual(expectedTextEdit.newText.trim(), `${type}:\n${expectedLines.join('\n')}`.trim())
831872
}
832873
async function ensureNotificationReceived(type: string, params: any) {
833874
const result = await client.sendRequest(
@@ -1351,6 +1392,27 @@ describe('Client integration', () => {
13511392
}, true)
13521393
})
13531394

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

13701485
namespace CrashNotification {
@@ -1383,8 +1498,8 @@ class CrashClient extends LanguageClient {
13831498
})
13841499
}
13851500

1386-
protected handleConnectionClosed(): void {
1387-
super.handleConnectionClosed()
1501+
protected async handleConnectionClosed(): Promise<void> {
1502+
await super.handleConnectionClosed()
13881503
this.resolve!()
13891504
}
13901505
}

0 commit comments

Comments
 (0)