Skip to content

Commit

Permalink
feat(lsp): LSP 3.18
Browse files Browse the repository at this point in the history
- inlineCompletion

Closes #5071
  • Loading branch information
fannheyward committed Nov 22, 2024
1 parent 61be196 commit e6f170e
Show file tree
Hide file tree
Showing 37 changed files with 863 additions and 472 deletions.
49 changes: 27 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"eslint-plugin-jsdoc": "^48.11.0",
"jest": "29.7.0",
"typescript": "^5.5.4",
"vscode-languageserver": "^9.0.1"
"vscode-languageserver": "^10.0.0-next.11"
},
"dependencies": {
"@chemzqm/msgpack-lite": "^0.1.29",
Expand All @@ -108,9 +108,9 @@
"unidecode": "^1.0.1",
"unzip-stream": "^0.3.4",
"uuid": "^9.0.1",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-languageserver-types": "^3.17.5",
"vscode-languageserver-protocol": "^3.17.6-next.11",
"vscode-languageserver-textdocument": "^1.0.12",
"vscode-languageserver-types": "^3.17.6-next.5",
"vscode-uri": "^3.0.8",
"which": "^4.0.0"
}
Expand Down
126 changes: 119 additions & 7 deletions src/__tests__/client/features.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as assert from 'assert'
import path from 'path'
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'
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'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { URI } from 'vscode-uri'
import commands from '../../commands'
Expand Down Expand Up @@ -97,11 +97,12 @@ describe('Client integration', () => {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }
}
const documentSelector: DocumentSelector = [{ scheme: 'lsptests' }]
const documentSelector: DocumentSelector = [{ scheme: 'lsptests', language: 'bat' }]

middleware = {}
const clientOptions: LanguageClientOptions = {
documentSelector, synchronize: {}, initializationOptions: {}, middleware
documentSelector, synchronize: {}, initializationOptions: {}, middleware,
workspaceFolder: { name: 'test_folder', uri: URI.parse('file-test:///').toString() },
}

client = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)
Expand Down Expand Up @@ -137,7 +138,9 @@ describe('Client integration', () => {
resolveProvider: true
},
documentFormattingProvider: true,
documentRangeFormattingProvider: true,
documentRangeFormattingProvider: {
rangesSupport: true
},
documentOnTypeFormattingProvider: {
firstTriggerCharacter: ':'
},
Expand All @@ -154,6 +157,7 @@ describe('Client integration', () => {
documentSelector: [{ language: '*' }]
},
selectionRangeProvider: true,
inlineCompletionProvider: true,
inlineValueProvider: {},
inlayHintProvider: {
resolveProvider: true
Expand Down Expand Up @@ -270,6 +274,7 @@ describe('Client integration', () => {
testFeature(SemanticTokensRegistrationType.method, 'document')
testFeature(LinkedEditingRangeRequest.method, 'document')
testFeature(TypeHierarchyPrepareRequest.method, 'document')
testFeature(InlineCompletionRequest.method, 'document')
testFeature(InlineValueRequest.method, 'document')
testFeature(InlayHintRequest.method, 'document')
testFeature(WorkspaceSymbolRequest.method, 'workspace')
Expand Down Expand Up @@ -538,6 +543,40 @@ describe('Client integration', () => {
)
})

test('Progress percentage is an integer', async () => {
const progressToken = 'TEST-PROGRESS-PERCENTAGE'
const percentages: Array<number | undefined> = []
let currentProgressResolver: (value: unknown) => void | undefined

// Set up middleware that calls the current resolve function when it gets its 'end' progress event.
middleware.handleWorkDoneProgress = (token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, next) => {
if (token === progressToken) {
const percentage = params.kind === 'report' || params.kind === 'begin' ? params.percentage : undefined
percentages.push(percentage)

if (params.kind === 'end') {
setImmediate(currentProgressResolver)
}
}
return next(token, params)
}

// Trigger a progress event.
await new Promise<unknown>(resolve => {
currentProgressResolver = resolve
void client.sendRequest(
new ProtocolRequestType<any, null, never, any, any>('testing/sendPercentageProgress'),
{},
tokenSource.token,
)
})

middleware.handleWorkDoneProgress = undefined

// Ensure percentages are rounded according to the spec
assert.deepStrictEqual(percentages, [0, 50, undefined])
})

test('Document Formatting', async () => {
const provider = client.getFeature(DocumentFormattingRequest.method).getProvider(document)
isDefined(provider)
Expand Down Expand Up @@ -822,7 +861,7 @@ describe('Client integration', () => {

const referenceFileUri = URI.parse('/dummy-edit')
function ensureReferenceEdit(edits: WorkspaceEdit, type: string, expectedLines: string[]) {
// // Ensure the edits are as expected.
// Ensure the edits are as expected.
assert.strictEqual(edits.documentChanges?.length, 1)
const edit = edits.documentChanges[0] as TextDocumentEdit
assert.strictEqual(edit.edits.length, 1)
Expand Down Expand Up @@ -1351,6 +1390,27 @@ describe('Client integration', () => {
}, true)
})

test('Inline Completions', async () => {
const provider = client.getFeature(InlineCompletionRequest.method)?.getProvider(document)
isDefined(provider)
const results = (await provider.provideInlineCompletionItems(document, position, { triggerKind: 1, selectedCompletionInfo: { range, text: 'text' } }, tokenSource.token)) as InlineCompletionItem[]

isArray(results, InlineCompletionItem, 1)

rangeEqual(results[0].range!, 1, 2, 3, 4)
assert.strictEqual(results[0].filterText!, 'te')
assert.strictEqual(results[0].insertText, 'text inline')

let middlewareCalled = false
middleware.provideInlineCompletionItems = (d, r, c, t, n) => {
middlewareCalled = true
return n(d, r, c, t)
}
await provider.provideInlineCompletionItems(document, position, { triggerKind: 1, selectedCompletionInfo: undefined }, tokenSource.token)
middleware.provideInlineCompletionItems = undefined
assert.strictEqual(middlewareCalled, true)
})

test('Workspace symbols', async () => {
const providers = client.getFeature(WorkspaceSymbolRequest.method).getProviders()
isDefined(providers)
Expand All @@ -1365,6 +1425,58 @@ describe('Client integration', () => {
isDefined(symbol)
rangeEqual(symbol.location['range'], 1, 2, 3, 4)
})

test('General middleware', async () => {
let middlewareCallCount = 0
// Add a general middleware for both requests and notifications
middleware.sendRequest = (type, param, token, next) => {
middlewareCallCount++
return next(type, param, token)
}
middleware.sendNotification = (type, next, params) => {
middlewareCallCount++
return next(type, params)
}
// Send a request
const definitionProvider = client.getFeature(DefinitionRequest.method).getProvider(document)
isDefined(definitionProvider)
await definitionProvider.provideDefinition(document, position, tokenSource.token)
// Send a notification
const notificationProvider = client.getFeature(DidSaveTextDocumentNotification.method).getProvider(document)
isDefined(notificationProvider)
await notificationProvider.send(document)
// Verify that both the request and notification went through the middleware
middleware.sendRequest = undefined
middleware.sendNotification = undefined
assert.strictEqual(middlewareCallCount, 2)
})

test('applyEdit middleware', async () => {
const middlewareEvents: Array<ApplyWorkspaceEditParams> = []
let currentProgressResolver: (value: unknown) => void | undefined

middleware.workspace = middleware.workspace || {}
middleware.workspace.handleApplyEdit = async (params, next) => {
middlewareEvents.push(params)
setImmediate(currentProgressResolver)
return next(params, tokenSource.token)
}

// Trigger sample applyEdit event.
await new Promise<unknown>(resolve => {
currentProgressResolver = resolve
void client.sendRequest(
new ProtocolRequestType<any, null, never, any, any>('testing/sendApplyEdit'),
{},
tokenSource.token,
)
})

middleware.workspace.handleApplyEdit = undefined

// Ensure event was handled.
assert.deepStrictEqual(middlewareEvents, [{ label: 'Apply Edit', edit: {} }])
})
})

namespace CrashNotification {
Expand All @@ -1383,9 +1495,9 @@ class CrashClient extends LanguageClient {
})
}

protected handleConnectionClosed(): void {
super.handleConnectionClosed()
protected handleConnectionClosed(): Promise<void> {
this.resolve!()
return super.handleConnectionClosed()
}
}

Expand Down
20 changes: 10 additions & 10 deletions src/__tests__/client/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import os from 'os'
import { v4 as uuid } from 'uuid'
import { CancellationToken, CancellationTokenSource, DidCreateFilesNotification, Disposable, ErrorCodes, InlayHintRequest, LSPErrorCodes, MessageType, ResponseError, Trace, WorkDoneProgress } from 'vscode-languageserver-protocol'
import { IPCMessageReader, IPCMessageWriter } from 'vscode-languageserver-protocol/node'
import { Diagnostic, MarkupKind, Range } from 'vscode-languageserver-types'
import { MarkupKind, Range } from 'vscode-languageserver-types'
import { URI } from 'vscode-uri'
import * as lsclient from '../../language-client'
import { CloseAction, ErrorAction, HandleDiagnosticsSignature } from '../../language-client'
import { CloseAction, ErrorAction } from '../../language-client'
import { LSPCancellationError } from '../../language-client/features'
import { InitializationFailedHandler } from '../../language-client/utils/errorHandler'
import { disposeAll } from '../../util'
Expand Down Expand Up @@ -213,11 +213,11 @@ describe('Client events', () => {
synchronize: {},
errorHandler: {
error: () => {
return ErrorAction.Shutdown
return { action: ErrorAction.Shutdown }
},
closed: () => {
called = true
return CloseAction.DoNotRestart
return { action: CloseAction.DoNotRestart }
}
},
initializationOptions: { initEvent: true }
Expand All @@ -234,7 +234,7 @@ describe('Client events', () => {
await helper.waitValue(() => {
return called
}, true)
client.handleConnectionError(new Error('error'), { jsonrpc: '' }, 1)
void client.handleConnectionError(new Error('error'), { jsonrpc: '' }, 1)
})

it('should handle message events', async () => {
Expand Down Expand Up @@ -295,9 +295,9 @@ describe('Client events', () => {
synchronize: {},
middleware: {
window: {
showDocument: async (params, next) => {
showDocument: async (params, token, next) => {
called = true
let res = await next(params, CancellationToken.None)
let res = await next(params, token)
return res as any
}
}
Expand Down Expand Up @@ -437,11 +437,11 @@ describe('Client integration', () => {
},
stdioEncoding: 'utf8',
errorHandler: {
error: (): lsclient.ErrorAction => {
return lsclient.ErrorAction.Continue
error: () => {
return { action: lsclient.ErrorAction.Continue }
},
closed: () => {
return lsclient.CloseAction.DoNotRestart
return { action: lsclient.CloseAction.DoNotRestart }
}
},
progressOnInitialization: true,
Expand Down
Loading

0 comments on commit e6f170e

Please sign in to comment.