Skip to content

Commit 2cc29cc

Browse files
committed
feat(lsp): LSP 3.18
- inlineCompletion Closes #5071
1 parent 283b781 commit 2cc29cc

38 files changed

+887
-429
lines changed

package-lock.json

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

package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
"globals": "^16.0.0",
8686
"jest": "29.7.0",
8787
"typescript": "^5.5.4",
88-
"vscode-languageserver": "^9.0.1"
88+
"vscode-languageserver": "^10.0.0-next.12"
8989
},
9090
"dependencies": {
9191
"@chemzqm/neovim": "^6.1.7",
@@ -112,10 +112,10 @@
112112
"unidecode": "^1.0.1",
113113
"unzip-stream": "^0.3.4",
114114
"uuid": "^9.0.1",
115-
"vscode-languageserver-protocol": "^3.17.5",
116-
"vscode-languageserver-textdocument": "^1.0.11",
117-
"vscode-languageserver-types": "^3.17.5",
118-
"vscode-uri": "^3.0.8",
115+
"vscode-languageserver-protocol": "^3.17.6-next.12",
116+
"vscode-languageserver-textdocument": "^1.0.12",
117+
"vscode-languageserver-types": "^3.17.6-next.6",
118+
"vscode-uri": "^3.1.0",
119119
"which": "^4.0.0"
120120
}
121121
}

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')
@@ -537,6 +543,40 @@ describe('Client integration', () => {
537543
)
538544
})
539545

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

822862
const referenceFileUri = URI.parse('/dummy-edit')
823863
function ensureReferenceEdit(edits: WorkspaceEdit, type: string, expectedLines: string[]) {
824-
// // Ensure the edits are as expected.
864+
// Ensure the edits are as expected.
825865
assert.strictEqual(edits.documentChanges?.length, 1)
826866
const edit = edits.documentChanges[0] as TextDocumentEdit
827867
assert.strictEqual(edit.edits.length, 1)
828868
assert.strictEqual(edit.textDocument.uri, referenceFileUri.path)
829-
assert.strictEqual(edit.edits[0].newText.trim(), `${type}:\n${expectedLines.join('\n')}`.trim())
869+
const expectedTextEdit = edit.edits[0] as TextEdit
870+
assert.strictEqual(expectedTextEdit.newText.trim(), `${type}:\n${expectedLines.join('\n')}`.trim())
830871
}
831872
async function ensureNotificationReceived(type: string, params: any) {
832873
const result = await client.sendRequest(
@@ -1350,6 +1391,27 @@ describe('Client integration', () => {
13501391
}, true)
13511392
})
13521393

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+
13531415
test('Workspace symbols', async () => {
13541416
const providers = client.getFeature(WorkspaceSymbolRequest.method).getProviders()
13551417
isDefined(providers)
@@ -1364,6 +1426,59 @@ describe('Client integration', () => {
13641426
isDefined(symbol)
13651427
rangeEqual(symbol.location['range'], 1, 2, 3, 4)
13661428
})
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.strictEqual(middlewareEvents.length, 1)
1480+
assert.strictEqual(middlewareEvents[0].label, 'Apply Edit')
1481+
})
13671482
})
13681483

13691484
namespace CrashNotification {
@@ -1382,8 +1497,8 @@ class CrashClient extends LanguageClient {
13821497
})
13831498
}
13841499

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

0 commit comments

Comments
 (0)