Skip to content

Commit 20b0269

Browse files
committed
feat(lsp): LSP 3.18
- inlineCompletion Closes #5071
1 parent 98fa66a commit 20b0269

36 files changed

+681
-397
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.10"
8585
},
8686
"dependencies": {
8787
"@chemzqm/neovim": "^6.1.2",
@@ -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.10",
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

+118-6
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
},
@@ -154,6 +157,7 @@ describe('Client integration', () => {
154157
documentSelector: [{ language: '*' }]
155158
},
156159
selectionRangeProvider: true,
160+
inlineCompletionProvider: true,
157161
inlineValueProvider: {},
158162
inlayHintProvider: {
159163
resolveProvider: true
@@ -270,6 +274,7 @@ describe('Client integration', () => {
270274
testFeature(SemanticTokensRegistrationType.method, 'document')
271275
testFeature(LinkedEditingRangeRequest.method, 'document')
272276
testFeature(TypeHierarchyPrepareRequest.method, 'document')
277+
testFeature(InlineCompletionRequest.method, 'document')
273278
testFeature(InlineValueRequest.method, 'document')
274279
testFeature(InlayHintRequest.method, 'document')
275280
testFeature(WorkspaceSymbolRequest.method, 'workspace')
@@ -538,6 +543,40 @@ describe('Client integration', () => {
538543
)
539544
})
540545

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+
541580
test('Document Formatting', async () => {
542581
const provider = client.getFeature(DocumentFormattingRequest.method).getProvider(document)
543582
isDefined(provider)
@@ -822,7 +861,7 @@ describe('Client integration', () => {
822861

823862
const referenceFileUri = URI.parse('/dummy-edit')
824863
function ensureReferenceEdit(edits: WorkspaceEdit, type: string, expectedLines: string[]) {
825-
// // Ensure the edits are as expected.
864+
// Ensure the edits are as expected.
826865
assert.strictEqual(edits.documentChanges?.length, 1)
827866
const edit = edits.documentChanges[0] as TextDocumentEdit
828867
assert.strictEqual(edit.edits.length, 1)
@@ -1351,6 +1390,27 @@ describe('Client integration', () => {
13511390
}, true)
13521391
})
13531392

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

13701482
namespace CrashNotification {
@@ -1383,9 +1495,9 @@ class CrashClient extends LanguageClient {
13831495
})
13841496
}
13851497

1386-
protected handleConnectionClosed(): void {
1387-
super.handleConnectionClosed()
1498+
protected handleConnectionClosed(): Promise<void> {
13881499
this.resolve!()
1500+
return super.handleConnectionClosed()
13891501
}
13901502
}
13911503

src/__tests__/client/integration.test.ts

+7-7
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 }
@@ -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,
+7-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
'use strict'
2-
const {createConnection, ConfigurationRequest, DidChangeConfigurationNotification} = require('vscode-languageserver')
3-
const {URI} = require('vscode-uri')
2+
const { createConnection, ConfigurationRequest, DidChangeConfigurationNotification } = require('vscode-languageserver/node')
3+
const { URI } = require('vscode-uri')
44

55
const connection = createConnection()
66
console.log = connection.console.log.bind(connection.console)
77
console.error = connection.console.error.bind(connection.console)
8-
connection.onInitialize((_params) => {
9-
return {capabilities: {}}
8+
connection.onInitialize(_params => {
9+
return { capabilities: {} }
1010
})
1111

1212
connection.onNotification('pull0', () => {
13-
connection.sendRequest(ConfigurationRequest.type, {
13+
void connection.sendRequest(ConfigurationRequest.type, {
1414
items: [{
1515
scopeUri: URI.file(__filename).toString()
1616
}]
1717
})
1818
})
1919

2020
connection.onNotification('pull1', () => {
21-
connection.sendRequest(ConfigurationRequest.type, {
21+
void connection.sendRequest(ConfigurationRequest.type, {
2222
items: [{
2323
section: 'http'
2424
}, {
@@ -30,7 +30,7 @@ connection.onNotification('pull1', () => {
3030
})
3131

3232
connection.onNotification(DidChangeConfigurationNotification.type, params => {
33-
connection.sendNotification('configurationChange', params)
33+
void connection.sendNotification('configurationChange', params)
3434
})
3535

3636
connection.listen()

0 commit comments

Comments
 (0)