Skip to content

Commit

Permalink
feat(lsp): inlineCompletion
Browse files Browse the repository at this point in the history
Closes #5071
  • Loading branch information
fannheyward committed Jul 25, 2024
1 parent 33d0a52 commit 747039e
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 146 deletions.
40 changes: 22 additions & 18 deletions package-lock.json

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

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"eslint-plugin-jsdoc": "^48.5.0",
"jest": "29.7.0",
"typescript": "^5.5.3",
"vscode-languageserver": "^9.0.1"
"vscode-languageserver": "^10.0.0-next.7"
},
"dependencies": {
"@chemzqm/neovim": "^6.1.2",
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-protocol": "^3.17.6-next.7",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-languageserver-types": "^3.17.5",
"vscode-languageserver-types": "^3.17.6-next.4",
"vscode-uri": "^3.0.8",
"which": "^4.0.0"
}
Expand Down
111 changes: 110 additions & 1 deletion 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 @@ -154,6 +154,7 @@ describe('Client integration', () => {
documentSelector: [{ language: '*' }]
},
selectionRangeProvider: true,
inlineCompletionProvider: true,
inlineValueProvider: {},
inlayHintProvider: {
resolveProvider: true
Expand Down Expand Up @@ -270,6 +271,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 +540,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 @@ -1351,6 +1387,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 +1422,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 Down
Loading

0 comments on commit 747039e

Please sign in to comment.