From cf31f112e41021633b05bb502fd15775991e9455 Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Mon, 15 Jul 2024 18:39:00 +0800 Subject: [PATCH] feat(lsp): LSP 3.18 - inlineCompletion Closes #5071 --- package-lock.json | 49 ++-- package.json | 8 +- src/__tests__/client/features.test.ts | 126 +++++++- src/__tests__/client/integration.test.ts | 20 +- src/__tests__/client/server/configServer.js | 14 +- .../client/server/crashOnShutdownServer.js | 14 +- src/__tests__/client/server/crashServer.js | 10 +- src/__tests__/client/server/customServer.js | 8 +- .../client/server/diagnosticServer.js | 16 +- src/__tests__/client/server/dynamicServer.js | 68 ++--- src/__tests__/client/server/errorServer.js | 4 +- src/__tests__/client/server/eventServer.js | 50 ++-- .../client/server/fileWatchServer.js | 14 +- src/__tests__/client/server/nullServer.js | 6 +- .../client/server/startStopServer.js | 6 +- src/__tests__/client/server/testDocuments.js | 30 +- .../client/server/testFileWatcher.js | 35 --- .../client/server/testInitializeResult.js | 14 +- src/__tests__/client/server/testServer.js | 173 ++++++----- .../client/server/timeoutOnShutdownServer.js | 10 +- src/__tests__/client/utils.test.ts | 8 +- src/__tests__/modules/plugin.test.ts | 3 + src/__tests__/modules/server.js | 12 +- src/core/files.ts | 3 +- src/core/funcs.ts | 5 +- src/handler/refactor/index.ts | 3 +- src/language-client/client.ts | 270 +++++++++++++----- src/language-client/features.ts | 15 +- src/language-client/index.ts | 67 ++--- src/language-client/inlineCompletion.ts | 81 ++++++ src/language-client/utils/errorHandler.ts | 65 ++++- src/languages.ts | 11 +- src/model/editInspect.ts | 5 +- src/provider/index.ts | 70 ++--- src/provider/inlineCompletionItemManager.ts | 36 +++ src/util/textedit.ts | 4 +- tsconfig.json | 6 +- 37 files changed, 865 insertions(+), 474 deletions(-) delete mode 100644 src/__tests__/client/server/testFileWatcher.js create mode 100644 src/language-client/inlineCompletion.ts create mode 100644 src/provider/inlineCompletionItemManager.ts diff --git a/package-lock.json b/package-lock.json index 94f9fe6bf43..52504a0bc35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,9 +32,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" }, @@ -58,7 +58,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" }, "engines": { "node": ">=16.18.0" @@ -6650,43 +6650,48 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "version": "9.0.0-next.6", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.6.tgz", + "integrity": "sha512-KCSvUNsFiVciG9iqjJKBZOd66CN3ZKohDlYRmoOi+pd8l15MFLZ8wRG4c+wuzePGba/8WcCG2TM+C/GVlvuaeA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "version": "10.0.0-next.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.0.0-next.11.tgz", + "integrity": "sha512-cmobSrVDYhlh/t02vz/bV8nNpds8mus5HnILULae2iAvOjoaJPnTAp0jJWoYdUqTpIVzT9JV6JMKqLEvdqpeqg==", "dev": true, + "license": "MIT", "dependencies": { - "vscode-languageserver-protocol": "3.17.5" + "vscode-languageserver-protocol": "3.17.6-next.11" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "version": "3.17.6-next.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.11.tgz", + "integrity": "sha512-GeJxEp1TiLsp79f8WG5n10wLViXfgFKb99hU9K8m7KDWM95/QFEqWkm79f9LVm54tUK74I91a9EeiQLCS/FABQ==", + "license": "MIT", "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" + "vscode-jsonrpc": "9.0.0-next.6", + "vscode-languageserver-types": "3.17.6-next.5" } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", - "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==" + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" }, "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + "version": "3.17.6-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.5.tgz", + "integrity": "sha512-QFmf3Yl1tCgUQfA77N9Me/LXldJXkIVypQbty2rJ1DNHQkC+iwvm4Z2tXg9czSwlhvv0pD4pbF5mT7WhAglolw==", + "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.0.8", diff --git a/package.json b/package.json index 3afc2a1b2e1..00d44da8b5e 100644 --- a/package.json +++ b/package.json @@ -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/neovim": "^6.1.2", @@ -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" } diff --git a/src/__tests__/client/features.test.ts b/src/__tests__/client/features.test.ts index ebced7de2aa..76ec1a20c63 100644 --- a/src/__tests__/client/features.test.ts +++ b/src/__tests__/client/features.test.ts @@ -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' @@ -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) @@ -137,7 +138,9 @@ describe('Client integration', () => { resolveProvider: true }, documentFormattingProvider: true, - documentRangeFormattingProvider: true, + documentRangeFormattingProvider: { + rangesSupport: true + }, documentOnTypeFormattingProvider: { firstTriggerCharacter: ':' }, @@ -154,6 +157,7 @@ describe('Client integration', () => { documentSelector: [{ language: '*' }] }, selectionRangeProvider: true, + inlineCompletionProvider: true, inlineValueProvider: {}, inlayHintProvider: { resolveProvider: true @@ -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') @@ -538,6 +543,40 @@ describe('Client integration', () => { ) }) + test('Progress percentage is an integer', async () => { + const progressToken = 'TEST-PROGRESS-PERCENTAGE' + const percentages: Array = [] + 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(resolve => { + currentProgressResolver = resolve + void client.sendRequest( + new ProtocolRequestType('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) @@ -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) @@ -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) @@ -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 = [] + 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(resolve => { + currentProgressResolver = resolve + void client.sendRequest( + new ProtocolRequestType('testing/sendApplyEdit'), + {}, + tokenSource.token, + ) + }) + + middleware.workspace.handleApplyEdit = undefined + + // Ensure event was handled. + assert.deepStrictEqual(middlewareEvents, [{ label: 'Apply Edit', edit: {} }]) + }) }) namespace CrashNotification { @@ -1383,9 +1495,9 @@ class CrashClient extends LanguageClient { }) } - protected handleConnectionClosed(): void { - super.handleConnectionClosed() + protected handleConnectionClosed(): Promise { this.resolve!() + return super.handleConnectionClosed() } } diff --git a/src/__tests__/client/integration.test.ts b/src/__tests__/client/integration.test.ts index 27210a51d7f..9aaaaf337a8 100644 --- a/src/__tests__/client/integration.test.ts +++ b/src/__tests__/client/integration.test.ts @@ -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' @@ -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 } @@ -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 () => { @@ -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 } } @@ -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, diff --git a/src/__tests__/client/server/configServer.js b/src/__tests__/client/server/configServer.js index 4368c150dde..20796a3f0e6 100644 --- a/src/__tests__/client/server/configServer.js +++ b/src/__tests__/client/server/configServer.js @@ -1,16 +1,16 @@ 'use strict' -const {createConnection, ConfigurationRequest, DidChangeConfigurationNotification} = require('vscode-languageserver') -const {URI} = require('vscode-uri') +const { createConnection, ConfigurationRequest, DidChangeConfigurationNotification } = require('vscode-languageserver/node') +const { URI } = require('vscode-uri') const connection = createConnection() console.log = connection.console.log.bind(connection.console) console.error = connection.console.error.bind(connection.console) -connection.onInitialize((_params) => { - return {capabilities: {}} +connection.onInitialize(_params => { + return { capabilities: {} } }) connection.onNotification('pull0', () => { - connection.sendRequest(ConfigurationRequest.type, { + void connection.sendRequest(ConfigurationRequest.type, { items: [{ scopeUri: URI.file(__filename).toString() }] @@ -18,7 +18,7 @@ connection.onNotification('pull0', () => { }) connection.onNotification('pull1', () => { - connection.sendRequest(ConfigurationRequest.type, { + void connection.sendRequest(ConfigurationRequest.type, { items: [{ section: 'http' }, { @@ -30,7 +30,7 @@ connection.onNotification('pull1', () => { }) connection.onNotification(DidChangeConfigurationNotification.type, params => { - connection.sendNotification('configurationChange', params) + void connection.sendNotification('configurationChange', params) }) connection.listen() diff --git a/src/__tests__/client/server/crashOnShutdownServer.js b/src/__tests__/client/server/crashOnShutdownServer.js index 34fd7d2299f..4b2c6346c53 100644 --- a/src/__tests__/client/server/crashOnShutdownServer.js +++ b/src/__tests__/client/server/crashOnShutdownServer.js @@ -1,11 +1,9 @@ -'use strict' -const {createConnection} = require('vscode-languageserver') - -const connection = createConnection() -console.log = connection.console.log.bind(connection.console) -console.error = connection.console.error.bind(connection.console) -connection.onInitialize((_params) => { - return {capabilities: {}} +"use strict" +Object.defineProperty(exports, "__esModule", { value: true }) +const node_1 = require("vscode-languageserver/node") +const connection = (0, node_1.createConnection)() +connection.onInitialize(_params => { + return { capabilities: {} } }) connection.onShutdown(() => { process.exit(100) diff --git a/src/__tests__/client/server/crashServer.js b/src/__tests__/client/server/crashServer.js index 589c39b2565..00f464f68d1 100644 --- a/src/__tests__/client/server/crashServer.js +++ b/src/__tests__/client/server/crashServer.js @@ -1,12 +1,12 @@ "use strict" -Object.defineProperty(exports, "__esModule", {value: true}) -const node_1 = require('vscode-languageserver') -var CrashNotification; -(function (CrashNotification) { +Object.defineProperty(exports, "__esModule", { value: true }) +const node_1 = require("vscode-languageserver/node") +let CrashNotification; +(function(CrashNotification) { CrashNotification.type = new node_1.NotificationType0('test/crash') })(CrashNotification || (CrashNotification = {})) const connection = (0, node_1.createConnection)() -connection.onInitialize((_params) => { +connection.onInitialize(_params => { return { capabilities: {} } diff --git a/src/__tests__/client/server/customServer.js b/src/__tests__/client/server/customServer.js index ff4484b290a..42c10275122 100644 --- a/src/__tests__/client/server/customServer.js +++ b/src/__tests__/client/server/customServer.js @@ -1,13 +1,13 @@ "use strict" -Object.defineProperty(exports, "__esModule", {value: true}) -const node_1 = require("vscode-languageserver") +Object.defineProperty(exports, "__esModule", { value: true }) +const node_1 = require("vscode-languageserver/node") const connection = (0, node_1.createConnection)() -connection.onInitialize((_params) => { +connection.onInitialize(_params => { return { capabilities: {} } }) -connection.onRequest('request', (param) => { +connection.onRequest('request', param => { return param.value + 1 }) connection.onNotification('notification', () => { diff --git a/src/__tests__/client/server/diagnosticServer.js b/src/__tests__/client/server/diagnosticServer.js index f8ec2f257c3..ae781cd1429 100644 --- a/src/__tests__/client/server/diagnosticServer.js +++ b/src/__tests__/client/server/diagnosticServer.js @@ -1,6 +1,6 @@ 'use strict' -const {createConnection, ResponseError, LSPErrorCodes, DiagnosticRefreshRequest, DocumentDiagnosticReportKind, Diagnostic, Range, DiagnosticSeverity, TextDocuments, TextDocumentSyncKind} = require('vscode-languageserver') -const {TextDocument} = require('vscode-languageserver-textdocument') +const { createConnection, ResponseError, LSPErrorCodes, DiagnosticRefreshRequest, DocumentDiagnosticReportKind, Diagnostic, Range, DiagnosticSeverity, TextDocuments, TextDocumentSyncKind } = require('vscode-languageserver/node') +const { TextDocument } = require('vscode-languageserver-textdocument') let documents = new TextDocuments(TextDocument) const connection = createConnection() @@ -8,7 +8,7 @@ console.log = connection.console.log.bind(connection.console) console.error = connection.console.error.bind(connection.console) let options documents.listen(connection) -connection.onInitialize((params) => { +connection.onInitialize(params => { options = params.initializationOptions || {} const interFileDependencies = options.interFileDependencies !== false const workspaceDiagnostics = options.workspaceDiagnostics === true @@ -27,11 +27,11 @@ connection.onInitialize((params) => { let count = 0 let saveCount = 0 -connection.languages.diagnostics.on((params) => { +connection.languages.diagnostics.on(params => { let uri = params.textDocument.uri if (uri.endsWith('error')) return Promise.reject(new Error('server error')) - if (uri.endsWith('cancel')) return new ResponseError(LSPErrorCodes.ServerCancelled, 'cancel', {retriggerRequest: false}) - if (uri.endsWith('retrigger')) return new ResponseError(LSPErrorCodes.ServerCancelled, 'retrigger', {retriggerRequest: true}) + if (uri.endsWith('cancel')) return new ResponseError(LSPErrorCodes.ServerCancelled, 'cancel', { retriggerRequest: false }) + if (uri.endsWith('retrigger')) return new ResponseError(LSPErrorCodes.ServerCancelled, 'retrigger', { retriggerRequest: true }) if (uri.endsWith('change')) count++ if (uri.endsWith('save')) saveCount++ if (uri.endsWith('empty')) return null @@ -79,7 +79,7 @@ connection.languages.diagnostics.onWorkspace((params, _, __, reporter) => { }) }, 20) setTimeout(() => { - resolve({items: []}) + resolve({ items: [] }) }, 50) }) } @@ -100,7 +100,7 @@ connection.languages.diagnostics.onWorkspace((params, _, __, reporter) => { }) connection.onNotification('fireRefresh', () => { - connection.sendRequest(DiagnosticRefreshRequest.type) + void connection.sendRequest(DiagnosticRefreshRequest.type) }) connection.onRequest('getChangeCount', () => { diff --git a/src/__tests__/client/server/dynamicServer.js b/src/__tests__/client/server/dynamicServer.js index 0dfd0bc879e..91ea7c76bb0 100644 --- a/src/__tests__/client/server/dynamicServer.js +++ b/src/__tests__/client/server/dynamicServer.js @@ -1,5 +1,5 @@ 'use strict' -const {createConnection, ProtocolRequestType, Range, TextDocumentSyncKind, Command, RenameRequest, WorkspaceSymbolRequest, CodeAction, SemanticTokensRegistrationType, CodeActionRequest, ConfigurationRequest, DidChangeConfigurationNotification, InlineValueRefreshRequest, ExecuteCommandRequest, CompletionRequest, WorkspaceFoldersRequest} = require('vscode-languageserver') +const { createConnection, ProtocolRequestType, Range, TextDocumentSyncKind, Command, RenameRequest, WorkspaceSymbolRequest, SemanticTokensRegistrationType, CodeActionRequest, ConfigurationRequest, DidChangeConfigurationNotification, InlineValueRefreshRequest, ExecuteCommandRequest, CompletionRequest, WorkspaceFoldersRequest } = require('vscode-languageserver/node') const connection = createConnection() console.log = connection.console.log.bind(connection.console) @@ -11,7 +11,7 @@ let prepareResponse let configuration let folders let foldersEvent -connection.onInitialize((params) => { +connection.onInitialize(params => { options = params.initializationOptions || {} let changeNotifications = options.changeNotifications ?? 'b346648e-88e0-44e3-91e3-52fd6addb8c7' return { @@ -19,9 +19,9 @@ connection.onInitialize((params) => { inlineValueProvider: {}, executeCommandProvider: { }, - documentSymbolProvider: options.label ? {label: 'test'} : true, + documentSymbolProvider: options.label ? { label: 'test' } : true, textDocumentSync: TextDocumentSyncKind.Full, - renameProvider: options.prepareRename ? {prepareProvider: true} : true, + renameProvider: options.prepareRename ? { prepareProvider: true } : true, workspaceSymbolProvider: true, codeLensProvider: { resolveProvider: true @@ -34,30 +34,30 @@ connection.onInitialize((params) => { // Static reg is folders + .txt files with operation kind in the path didCreate: { filters: [ - {scheme: 'lsptest', pattern: {glob: '**/*', matches: 'file', options: {}}}, - {scheme: 'file', pattern: {glob: '**/*', matches: 'file', options: {ignoreCase: false}}} + { scheme: 'lsptest', pattern: { glob: '**/*', matches: 'file', options: {} } }, + { scheme: 'file', pattern: { glob: '**/*', matches: 'file', options: { ignoreCase: false } } } ] }, didRename: { filters: [ - {scheme: 'file', pattern: {glob: '**/*', matches: 'folder'}}, - {scheme: 'file', pattern: {glob: '**/*', matches: 'file'}} + { scheme: 'file', pattern: { glob: '**/*', matches: 'folder' } }, + { scheme: 'file', pattern: { glob: '**/*', matches: 'file' } } ] }, didDelete: { - filters: [{scheme: 'file', pattern: {glob: '**/*'}}] + filters: [{ scheme: 'file', pattern: { glob: '**/*' } }] }, willCreate: { - filters: [{scheme: 'file', pattern: {glob: '**/*'}}] + filters: [{ scheme: 'file', pattern: { glob: '**/*' } }] }, willRename: { filters: [ - {scheme: 'file', pattern: {glob: '**/*', matches: 'folder'}}, - {scheme: 'file', pattern: {glob: '**/*', matches: 'file'}} + { scheme: 'file', pattern: { glob: '**/*', matches: 'folder' } }, + { scheme: 'file', pattern: { glob: '**/*', matches: 'file' } } ] }, willDelete: { - filters: [{scheme: 'file', pattern: {glob: '**/*'}}] + filters: [{ scheme: 'file', pattern: { glob: '**/*' } }] }, } }, @@ -66,21 +66,21 @@ connection.onInitialize((params) => { }) connection.onInitialized(() => { - connection.client.register(RenameRequest.type, { + void connection.client.register(RenameRequest.type, { prepareProvider: options.prepareRename }).then(d => { disposables.push(d) }) - connection.client.register(WorkspaceSymbolRequest.type, { + void connection.client.register(WorkspaceSymbolRequest.type, { resolveProvider: true }).then(d => { disposables.push(d) }) let full = false if (options.delta) { - full = {delta: true} + full = { delta: true } } - connection.client.register(SemanticTokensRegistrationType.method, { + void connection.client.register(SemanticTokensRegistrationType.method, { full, range: options.rangeTokens, legend: { @@ -88,21 +88,21 @@ connection.onInitialized(() => { tokenModifiers: [] }, }) - connection.client.register(CodeActionRequest.method, { + void connection.client.register(CodeActionRequest.method, { resolveProvider: false }) - connection.client.register(DidChangeConfigurationNotification.type, {section: undefined}) - connection.client.register(ExecuteCommandRequest.type, { + void connection.client.register(DidChangeConfigurationNotification.type, { section: undefined }) + void connection.client.register(ExecuteCommandRequest.type, { commands: ['test_command'] }).then(d => { disposables.push(d) }) - connection.client.register(CompletionRequest.type, { - documentSelector: [{language: 'vim'}] + void connection.client.register(CompletionRequest.type, { + documentSelector: [{ language: 'vim' }] }).then(d => { disposables.push(d) }) - connection.client.register(CompletionRequest.type, { + void connection.client.register(CompletionRequest.type, { triggerCharacters: ['/'], }).then(d => { disposables.push(d) @@ -110,11 +110,11 @@ connection.onInitialized(() => { }) let lastFileOperationRequest -connection.workspace.onDidCreateFiles(params => {lastFileOperationRequest = {type: 'create', params}}) -connection.workspace.onDidRenameFiles(params => {lastFileOperationRequest = {type: 'rename', params}}) -connection.workspace.onDidDeleteFiles(params => {lastFileOperationRequest = {type: 'delete', params}}) -connection.workspace.onWillRenameFiles(params => {lastFileOperationRequest = {type: 'willRename', params}}) -connection.workspace.onWillDeleteFiles(params => {lastFileOperationRequest = {type: 'willDelete', params}}) +connection.workspace.onDidCreateFiles(params => { lastFileOperationRequest = { type: 'create', params } }) +connection.workspace.onDidRenameFiles(params => { lastFileOperationRequest = { type: 'rename', params } }) +connection.workspace.onDidDeleteFiles(params => { lastFileOperationRequest = { type: 'delete', params } }) +connection.workspace.onWillRenameFiles(params => { lastFileOperationRequest = { type: 'willRename', params } }) +connection.workspace.onWillDeleteFiles(params => { lastFileOperationRequest = { type: 'willDelete', params } }) // connection.onDidChangeWorkspaceFolders(e => { // foldersEvent = params @@ -122,7 +122,7 @@ connection.workspace.onWillDeleteFiles(params => {lastFileOperationRequest = {ty connection.onCompletion(_params => { return [ - {label: 'item', insertText: 'text'} + { label: 'item', insertText: 'text' } ] }) @@ -151,7 +151,7 @@ connection.onDocumentSymbol(() => { connection.onExecuteCommand(param => { if (param.command === 'test_command') { - return {success: true} + return { success: true } } }) @@ -168,7 +168,7 @@ connection.onRequest('setPrepareResponse', param => { connection.onNotification('pullConfiguration', () => { configuration = connection.sendRequest(ConfigurationRequest.type, { - items: [{section: 'foo'}] + items: [{ section: 'foo' }] }) }) @@ -185,7 +185,7 @@ connection.onRequest('getFoldersEvent', () => { }) connection.onNotification('fireInlineValueRefresh', () => { - connection.sendRequest(InlineValueRefreshRequest.type) + void connection.sendRequest(InlineValueRefreshRequest.type) }) connection.onNotification('requestFolders', async () => { @@ -211,11 +211,11 @@ connection.onWorkspaceSymbolResolve(item => { }) connection.onCodeLens(params => { - return [{range: Range.create(0, 0, 0, 3)}, {range: Range.create(1, 0, 1, 3)}] + return [{ range: Range.create(0, 0, 0, 3) }, { range: Range.create(1, 0, 1, 3) }] }) connection.onCodeLensResolve(codelens => { - return {range: codelens.range, command: {title: 'format', command: 'format'}} + return { range: codelens.range, command: { title: 'format', command: 'format' } } }) connection.listen() diff --git a/src/__tests__/client/server/errorServer.js b/src/__tests__/client/server/errorServer.js index c17305eebe9..114839307ee 100644 --- a/src/__tests__/client/server/errorServer.js +++ b/src/__tests__/client/server/errorServer.js @@ -1,7 +1,7 @@ "use strict" -const {createConnection, ResponseError} = require("vscode-languageserver") +const { createConnection, ResponseError } = require("vscode-languageserver/node") const connection = createConnection() -connection.onInitialize((_params) => { +connection.onInitialize(_params => { return { capabilities: {} } diff --git a/src/__tests__/client/server/eventServer.js b/src/__tests__/client/server/eventServer.js index 69cea05ad93..549b8d15fa0 100644 --- a/src/__tests__/client/server/eventServer.js +++ b/src/__tests__/client/server/eventServer.js @@ -1,6 +1,6 @@ 'use strict' -const {createConnection, TextEdit, TextDocuments, Range, DiagnosticSeverity, Location, Diagnostic, DiagnosticRelatedInformation, PositionEncodingKind, WorkDoneProgress, ResponseError, LogMessageNotification, MessageType, ShowMessageNotification, ShowMessageRequest, ShowDocumentRequest, ApplyWorkspaceEditRequest, TextDocumentSyncKind, Position} = require('vscode-languageserver') -const {TextDocument} = require('vscode-languageserver-textdocument') +const { createConnection, TextEdit, TextDocuments, Range, DiagnosticSeverity, Location, Diagnostic, DiagnosticRelatedInformation, PositionEncodingKind, WorkDoneProgress, ResponseError, LogMessageNotification, MessageType, ShowMessageNotification, ShowMessageRequest, ShowDocumentRequest, ApplyWorkspaceEditRequest, TextDocumentSyncKind, Position } = require('vscode-languageserver/node') +const { TextDocument } = require('vscode-languageserver-textdocument') let documents = new TextDocuments(TextDocument) const connection = createConnection() @@ -8,13 +8,13 @@ console.log = connection.console.log.bind(connection.console) console.error = connection.console.error.bind(connection.console) let options documents.listen(connection) -connection.onInitialize((params) => { +connection.onInitialize(params => { options = params.initializationOptions || {} if (options.throwError) { setTimeout(() => { process.exit() }, 10) - return new ResponseError(1, 'message', {retry: true}) + return new ResponseError(1, 'message', { retry: true }) } if (options.normalThrow) { setTimeout(() => { @@ -23,7 +23,7 @@ connection.onInitialize((params) => { throw new Error('normal throw error') } if (options.utf8) { - return {capabilities: {positionEncoding: PositionEncodingKind.UTF8}} + return { capabilities: { positionEncoding: PositionEncodingKind.UTF8 } } } return { capabilities: { @@ -39,13 +39,13 @@ connection.onNotification('diagnostics', () => { related.push(DiagnosticRelatedInformation.create(Location.create(uri, Range.create(0, 0, 0, 1)), 'dup')) related.push(DiagnosticRelatedInformation.create(Location.create(uri, Range.create(0, 0, 1, 0)), 'dup')) diagnostics.push(Diagnostic.create(Range.create(0, 0, 1, 0), 'msg', DiagnosticSeverity.Error, undefined, undefined, related)) - connection.sendDiagnostics({uri: 'lsptest:///1', diagnostics}) - connection.sendDiagnostics({uri: 'lsptest:///3', version: 1, diagnostics}) + void connection.sendDiagnostics({ uri: 'lsptest:///1', diagnostics }) + void connection.sendDiagnostics({ uri: 'lsptest:///3', version: 1, diagnostics }) }) connection.onNotification('simpleEdit', async () => { - let res = await connection.sendRequest(ApplyWorkspaceEditRequest.type, {edit: {documentChanges: []}}) - connection.sendNotification('result', res) + let res = await connection.sendRequest(ApplyWorkspaceEditRequest.type, { edit: { documentChanges: [] } }) + void connection.sendNotification('result', res) }) connection.onNotification('edits', async () => { @@ -54,45 +54,45 @@ connection.onNotification('edits', async () => { edit: { documentChanges: uris.map(uri => { return { - textDocument: {uri, version: documents.get(uri).version + 1}, + textDocument: { uri, version: documents.get(uri).version + 1 }, edits: [TextEdit.insert(Position.create(0, 0), 'foo')] } }) } }) - connection.sendNotification('result', res) + void connection.sendNotification('result', res) }) connection.onNotification('send', () => { - connection.sendRequest('customRequest') - connection.sendNotification('customNotification') - connection.sendProgress(WorkDoneProgress.type, '4fb247f8-0ede-415d-a80a-6629b6a9eaf8', {kind: 'end', message: 'end message'}) + void connection.sendRequest('customRequest') + void connection.sendNotification('customNotification') + void connection.sendProgress(WorkDoneProgress.type, '4fb247f8-0ede-415d-a80a-6629b6a9eaf8', { kind: 'end', message: 'end message' }) }) connection.onNotification('logMessage', () => { - connection.sendNotification(LogMessageNotification.type, {type: MessageType.Error, message: 'msg'}) - connection.sendNotification(LogMessageNotification.type, {type: MessageType.Info, message: 'msg'}) - connection.sendNotification(LogMessageNotification.type, {type: MessageType.Log, message: 'msg'}) - connection.sendNotification(LogMessageNotification.type, {type: MessageType.Warning, message: 'msg'}) + void connection.sendNotification(LogMessageNotification.type, { type: MessageType.Error, message: 'msg' }) + void connection.sendNotification(LogMessageNotification.type, { type: MessageType.Info, message: 'msg' }) + void connection.sendNotification(LogMessageNotification.type, { type: MessageType.Log, message: 'msg' }) + void connection.sendNotification(LogMessageNotification.type, { type: MessageType.Warning, message: 'msg' }) }) connection.onNotification('showMessage', () => { - connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Error, message: 'msg'}) - connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Info, message: 'msg'}) - connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Log, message: 'msg'}) - connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Warning, message: 'msg'}) + void connection.sendNotification(ShowMessageNotification.type, { type: MessageType.Error, message: 'msg' }) + void connection.sendNotification(ShowMessageNotification.type, { type: MessageType.Info, message: 'msg' }) + void connection.sendNotification(ShowMessageNotification.type, { type: MessageType.Log, message: 'msg' }) + void connection.sendNotification(ShowMessageNotification.type, { type: MessageType.Warning, message: 'msg' }) }) connection.onNotification('requestMessage', async params => { - await connection.sendRequest(ShowMessageRequest.type, {type: params.type, message: 'msg', actions: [{title: 'open'}]}) + await connection.sendRequest(ShowMessageRequest.type, { type: params.type, message: 'msg', actions: [{ title: 'open' }] }) }) connection.onNotification('showDocument', async params => { await connection.sendRequest(ShowDocumentRequest.type, params) }) -connection.onProgress(WorkDoneProgress.type, '4b3a71d0-2b3f-46af-be2c-2827f548579f', (params) => { - connection.sendNotification('progressResult', params) +connection.onProgress(WorkDoneProgress.type, '4b3a71d0-2b3f-46af-be2c-2827f548579f', params => { + void connection.sendNotification('progressResult', params) }) connection.onRequest('doExit', () => { diff --git a/src/__tests__/client/server/fileWatchServer.js b/src/__tests__/client/server/fileWatchServer.js index ccaebed24b9..6bead180505 100644 --- a/src/__tests__/client/server/fileWatchServer.js +++ b/src/__tests__/client/server/fileWatchServer.js @@ -1,17 +1,17 @@ 'use strict' -const {createConnection, DidChangeWatchedFilesNotification} = require('vscode-languageserver') -const {URI} = require('vscode-uri') +const { createConnection, DidChangeWatchedFilesNotification } = require('vscode-languageserver/node') +const { URI } = require('vscode-uri') const connection = createConnection() console.log = connection.console.log.bind(connection.console) console.error = connection.console.error.bind(connection.console) -connection.onInitialize((_params) => { - return {capabilities: {}} +connection.onInitialize(_params => { + return { capabilities: {} } }) let disposables = [] connection.onInitialized(() => { - connection.client.register(DidChangeWatchedFilesNotification.type, { + void connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: '**/jsconfig.json', }, { @@ -32,14 +32,14 @@ connection.onInitialized(() => { }).then(d => { disposables.push(d) }) - connection.client.register(DidChangeWatchedFilesNotification.type, { + void connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: null }).then(d => { disposables.push(d) }) }) connection.onNotification(DidChangeWatchedFilesNotification.type, params => { - connection.sendNotification('filesChange', params) + void connection.sendNotification('filesChange', params) }) connection.onNotification('unwatch', () => { diff --git a/src/__tests__/client/server/nullServer.js b/src/__tests__/client/server/nullServer.js index c59e430ee39..40e05694417 100644 --- a/src/__tests__/client/server/nullServer.js +++ b/src/__tests__/client/server/nullServer.js @@ -1,8 +1,8 @@ "use strict" -Object.defineProperty(exports, "__esModule", {value: true}) -const node_1 = require("vscode-languageserver") +Object.defineProperty(exports, "__esModule", { value: true }) +const node_1 = require("vscode-languageserver/node") const connection = (0, node_1.createConnection)() -connection.onInitialize((_params) => { +connection.onInitialize(_params => { return { capabilities: {} } diff --git a/src/__tests__/client/server/startStopServer.js b/src/__tests__/client/server/startStopServer.js index 51d2b69a5f7..8836f01a924 100644 --- a/src/__tests__/client/server/startStopServer.js +++ b/src/__tests__/client/server/startStopServer.js @@ -1,8 +1,8 @@ "use strict" -Object.defineProperty(exports, "__esModule", {value: true}) -const node_1 = require("vscode-languageserver") +Object.defineProperty(exports, "__esModule", { value: true }) +const node_1 = require("vscode-languageserver/node") const connection = (0, node_1.createConnection)() -connection.onInitialize((_params) => { +connection.onInitialize(_params => { return { capabilities: { executeCommandProvider: { diff --git a/src/__tests__/client/server/testDocuments.js b/src/__tests__/client/server/testDocuments.js index 8c9e31ff1b5..7f0c274d84a 100644 --- a/src/__tests__/client/server/testDocuments.js +++ b/src/__tests__/client/server/testDocuments.js @@ -1,6 +1,6 @@ -const {ResponseError, LSPErrorCodes} = require('vscode-languageserver') -const ls = require('vscode-languageserver') -const {TextDocument} = require('vscode-languageserver-textdocument') +const { ResponseError, LSPErrorCodes } = require('vscode-languageserver/node') +const ls = require('vscode-languageserver/node') +const { TextDocument } = require('vscode-languageserver-textdocument') let connection = ls.createConnection() let documents = new ls.TextDocuments(TextDocument) @@ -10,16 +10,16 @@ let lastChangeEvent let lastWillSave let lastDidSave documents.onDidOpen(e => { - lastOpenEvent = {uri: e.document.uri, version: e.document.version} + lastOpenEvent = { uri: e.document.uri, version: e.document.version } }) documents.onDidClose(e => { - lastCloseEvent = {uri: e.document.uri} + lastCloseEvent = { uri: e.document.uri } }) documents.onDidChangeContent(e => { - lastChangeEvent = {uri: e.document.uri, text: e.document.getText()} + lastChangeEvent = { uri: e.document.uri, text: e.document.getText() } }) documents.onWillSave(e => { - lastWillSave = {uri: e.document.uri} + lastWillSave = { uri: e.document.uri } }) documents.onWillSaveWaitUntil(e => { let uri = e.document.uri @@ -28,7 +28,7 @@ documents.onWillSaveWaitUntil(e => { return [ls.TextEdit.insert(ls.Position.create(0, 0), 'abc')] }) documents.onDidSave(e => { - lastDidSave = {uri: e.document.uri} + lastDidSave = { uri: e.document.uri } }) documents.listen(connection) @@ -47,7 +47,7 @@ connection.onInitialize(params => { save: true } } - return {capabilities} + return { capabilities } }) connection.onRequest('getLastOpen', () => { @@ -72,22 +72,22 @@ connection.onRequest('getLastDidSave', () => { let disposables = [] connection.onNotification('registerDocumentSync', () => { - let opt = {documentSelector: [{language: 'vim'}]} - connection.client.register(ls.DidOpenTextDocumentNotification.type, opt).then(d => { + let opt = { documentSelector: [{ language: 'vim' }] } + void connection.client.register(ls.DidOpenTextDocumentNotification.type, opt).then(d => { disposables.push(d) }) - connection.client.register(ls.DidCloseTextDocumentNotification.type, opt).then(d => { + void connection.client.register(ls.DidCloseTextDocumentNotification.type, opt).then(d => { disposables.push(d) }) - connection.client.register(ls.DidChangeTextDocumentNotification.type, Object.assign({ + void connection.client.register(ls.DidChangeTextDocumentNotification.type, Object.assign({ syncKind: opts.none === true ? ls.TextDocumentSyncKind.None : ls.TextDocumentSyncKind.Incremental }, opt)).then(d => { disposables.push(d) }) - connection.client.register(ls.WillSaveTextDocumentNotification.type, opt).then(d => { + void connection.client.register(ls.WillSaveTextDocumentNotification.type, opt).then(d => { disposables.push(d) }) - connection.client.register(ls.WillSaveTextDocumentWaitUntilRequest.type, opt).then(d => { + void connection.client.register(ls.WillSaveTextDocumentWaitUntilRequest.type, opt).then(d => { disposables.push(d) }) }) diff --git a/src/__tests__/client/server/testFileWatcher.js b/src/__tests__/client/server/testFileWatcher.js deleted file mode 100644 index 6ac96e6f98a..00000000000 --- a/src/__tests__/client/server/testFileWatcher.js +++ /dev/null @@ -1,35 +0,0 @@ -const languageserver = require('vscode-languageserver') -let connection = languageserver.createConnection() -let documents = new languageserver.TextDocuments() -documents.listen(connection) - -connection.onInitialize(() => { - let capabilities = { - textDocumentSync: documents.syncKind - } - return { capabilities } -}) - -connection.onInitialized(() => { - connection.sendRequest('client/registerCapability', { - registrations: [{ - id: 'didChangeWatchedFiles', - method: 'workspace/didChangeWatchedFiles', - registerOptions: { - watchers: [{ globPattern: "**" }] - } - }] - }) -}) - -let received - -connection.onNotification('workspace/didChangeWatchedFiles', params => { - received = params -}) - -connection.onRequest('custom/received', async () => { - return received -}) - -connection.listen() diff --git a/src/__tests__/client/server/testInitializeResult.js b/src/__tests__/client/server/testInitializeResult.js index e9e297642dd..1921f52d547 100644 --- a/src/__tests__/client/server/testInitializeResult.js +++ b/src/__tests__/client/server/testInitializeResult.js @@ -1,13 +1,11 @@ 'use strict' -Object.defineProperty(exports, "__esModule", {value: true}) +Object.defineProperty(exports, "__esModule", { value: true }) const tslib_1 = require("tslib") const assert = tslib_1.__importStar(require("assert")) -const vscode_languageserver_1 = require("vscode-languageserver") +const vscode_languageserver_1 = require("vscode-languageserver/node") let connection = vscode_languageserver_1.createConnection() -let documents = new vscode_languageserver_1.TextDocuments() -documents.listen(connection) -connection.onInitialize((params) => { +connection.onInitialize(params => { assert.equal(params.capabilities.workspace.applyEdit, true) assert.equal(params.capabilities.workspace.workspaceEdit.documentChanges, true) assert.deepEqual(params.capabilities.workspace.workspaceEdit.resourceOperations, [vscode_languageserver_1.ResourceOperationKind.Create, vscode_languageserver_1.ResourceOperationKind.Rename, vscode_languageserver_1.ResourceOperationKind.Delete]) @@ -21,16 +19,16 @@ connection.onInitialize((params) => { assert.equal(valueSet[valueSet.length - 1], vscode_languageserver_1.CompletionItemKind.TypeParameter) let capabilities = { textDocumentSync: 1, - completionProvider: {resolveProvider: true, triggerCharacters: ['"', ':']}, + completionProvider: { resolveProvider: true, triggerCharacters: ['"', ':'] }, hoverProvider: true, renameProvider: { prepareProvider: true } } - return {capabilities, customResults: {"hello": "world"}} + return { capabilities, customResults: { hello: "world" } } }) connection.onInitialized(() => { - connection.sendDiagnostics({uri: "uri:/test.ts", diagnostics: []}) + void connection.sendDiagnostics({ uri: "uri:/test.ts", diagnostics: [] }) }) // Listen on the connection connection.listen() diff --git a/src/__tests__/client/server/testServer.js b/src/__tests__/client/server/testServer.js index 3927c3b3f78..0848989a072 100644 --- a/src/__tests__/client/server/testServer.js +++ b/src/__tests__/client/server/testServer.js @@ -1,17 +1,20 @@ const assert = require('assert') -const {URI} = require('vscode-uri') +const { URI } = require('vscode-uri') const { createConnection, CompletionItemKind, ResourceOperationKind, FailureHandlingKind, DiagnosticTag, CompletionItemTag, TextDocumentSyncKind, MarkupKind, SignatureInformation, ParameterInformation, Location, Range, DocumentHighlight, DocumentHighlightKind, CodeAction, Command, TextEdit, Position, DocumentLink, ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress, - SignatureHelpRequest, SemanticTokensRefreshRequest, WorkDoneProgressCreateRequest, CodeLensRefreshRequest, InlayHintRefreshRequest, WorkspaceSymbolRequest, DidChangeConfigurationNotification} = require('vscode-languageserver') + SignatureHelpRequest, SemanticTokensRefreshRequest, WorkDoneProgressCreateRequest, CodeLensRefreshRequest, InlayHintRefreshRequest, WorkspaceSymbolRequest, DidChangeConfigurationNotification } = require('vscode-languageserver/node') const { DidCreateFilesNotification, DidRenameFilesNotification, DidDeleteFilesNotification, - WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, InlayHint, InlayHintLabelPart, InlayHintKind, DocumentDiagnosticReportKind, Diagnostic, DiagnosticSeverity, InlineValueText, InlineValueVariableLookup, InlineValueEvaluatableExpression + InlineCompletionItem, + WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, InlayHint, InlayHintLabelPart, InlayHintKind, DocumentDiagnosticReportKind, Diagnostic, DiagnosticSeverity, InlineValueText, InlineValueVariableLookup, InlineValueEvaluatableExpression, + ApplyWorkspaceEditRequest, + DocumentSymbol } = require('vscode-languageserver-protocol') let connection = createConnection() @@ -64,7 +67,7 @@ connection.onInitialize(params => { triggerCharacters: [','], retriggerCharacters: [';'] }, - completionProvider: {resolveProvider: true, triggerCharacters: ['"', ':']}, + completionProvider: { resolveProvider: true, triggerCharacters: ['"', ':'] }, referencesProvider: true, documentHighlightProvider: true, codeActionProvider: { @@ -74,7 +77,9 @@ connection.onInitialize(params => { resolveProvider: true }, documentFormattingProvider: true, - documentRangeFormattingProvider: true, + documentRangeFormattingProvider: { + rangesSupport: true + }, documentOnTypeFormattingProvider: { firstTriggerCharacter: ':' }, @@ -84,20 +89,22 @@ connection.onInitialize(params => { documentLinkProvider: { resolveProvider: true }, + documentSymbolProvider: true, colorProvider: true, declarationProvider: true, foldingRangeProvider: true, implementationProvider: { - documentSelector: [{language: '*'}] + documentSelector: [{ language: '*' }] }, selectionRangeProvider: true, inlineValueProvider: {}, + inlineCompletionProvider: {}, inlayHintProvider: { resolveProvider: true }, typeDefinitionProvider: { id: '82671a9a-2a69-4e9f-a8d7-e1034eaa0d2e', - documentSelector: [{language: '*'}] + documentSelector: [{ language: '*' }] }, callHierarchyProvider: true, semanticTokensProvider: { @@ -114,28 +121,28 @@ connection.onInitialize(params => { fileOperations: { // Static reg is folders + .txt files with operation kind in the path didCreate: { - filters: [{scheme: 'file', pattern: {glob: '**/created-static/**{/,/*.txt}'}}] + filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' } }] }, didRename: { filters: [ - {scheme: 'file', pattern: {glob: '**/renamed-static/**/', matches: 'folder'}}, - {scheme: 'file', pattern: {glob: '**/renamed-static/**/*.txt', matches: 'file'}} + { scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } }, + { scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } } ] }, didDelete: { - filters: [{scheme: 'file', pattern: {glob: '**/deleted-static/**{/,/*.txt}'}}] + filters: [{ scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] }, willCreate: { - filters: [{scheme: 'file', pattern: {glob: '**/created-static/**{/,/*.txt}'}}] + filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' } }] }, willRename: { filters: [ - {scheme: 'file', pattern: {glob: '**/renamed-static/**/', matches: 'folder'}}, - {scheme: 'file', pattern: {glob: '**/renamed-static/**/*.txt', matches: 'file'}} + { scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } }, + { scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } } ] }, willDelete: { - filters: [{scheme: 'file', pattern: {glob: '**/deleted-static/**{/,/*.txt}'}}] + filters: [{ scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] }, }, }, @@ -151,58 +158,58 @@ connection.onInitialize(params => { }, notebookDocumentSync: { notebookSelector: [{ - notebook: {notebookType: 'jupyter-notebook'}, - cells: [{language: 'python'}] + notebook: { notebookType: 'jupyter-notebook' }, + cells: [{ language: 'python' }] }] } } - return {capabilities, customResults: {hello: 'world'}} + return { capabilities, customResults: { hello: 'world' } } }) connection.onInitialized(() => { // Dynamic reg is folders + .js files with operation kind in the path - connection.client.register(DidCreateFilesNotification.type, { - filters: [{scheme: 'file', pattern: {glob: '**/created-dynamic/**{/,/*.js}'}}] + void connection.client.register(DidCreateFilesNotification.type, { + filters: [{ scheme: 'file', pattern: { glob: '**/created-dynamic/**{/,/*.js}' } }] }) - connection.client.register(DidRenameFilesNotification.type, { + void connection.client.register(DidRenameFilesNotification.type, { filters: [ - {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/', matches: 'folder'}}, - {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/*.js', matches: 'file'}} + { scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/', matches: 'folder' } }, + { scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/*.js', matches: 'file' } } ] }) - connection.client.register(DidDeleteFilesNotification.type, { - filters: [{scheme: 'file', pattern: {glob: '**/deleted-dynamic/**{/,/*.js}'}}] + void connection.client.register(DidDeleteFilesNotification.type, { + filters: [{ scheme: 'file', pattern: { glob: '**/deleted-dynamic/**{/,/*.js}' } }] }) - connection.client.register(WillCreateFilesRequest.type, { - filters: [{scheme: 'file', pattern: {glob: '**/created-dynamic/**{/,/*.js}'}}] + void connection.client.register(WillCreateFilesRequest.type, { + filters: [{ scheme: 'file', pattern: { glob: '**/created-dynamic/**{/,/*.js}' } }] }) - connection.client.register(WillRenameFilesRequest.type, { + void connection.client.register(WillRenameFilesRequest.type, { filters: [ - {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/', matches: 'folder'}}, - {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/*.js', matches: 'file'}} + { scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/', matches: 'folder' } }, + { scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/*.js', matches: 'file' } } ] }) - connection.client.register(WillDeleteFilesRequest.type, { - filters: [{scheme: 'file', pattern: {glob: '**/deleted-dynamic/**{/,/*.js}'}}] + void connection.client.register(WillDeleteFilesRequest.type, { + filters: [{ scheme: 'file', pattern: { glob: '**/deleted-dynamic/**{/,/*.js}' } }] }) - connection.client.register(SignatureHelpRequest.type, { + void connection.client.register(SignatureHelpRequest.type, { triggerCharacters: [':'], retriggerCharacters: [':'] }).then(d => { disposables.push(d) }) - connection.client.register(WorkspaceSymbolRequest.type, { + void connection.client.register(WorkspaceSymbolRequest.type, { workDoneProgress: false, resolveProvider: true }).then(d => { disposables.push(d) }) - connection.client.register(DidChangeConfigurationNotification.type, { + void connection.client.register(DidChangeConfigurationNotification.type, { section: 'http' }).then(d => { disposables.push(d) }) - connection.client.register(DidCreateFilesNotification.type, { + void connection.client.register(DidCreateFilesNotification.type, { filters: [{ pattern: { glob: '**/renamed-dynamic/**/', @@ -225,35 +232,35 @@ connection.onNotification('unregister', () => { }) connection.onCodeLens(params => { - return [{range: Range.create(0, 0, 0, 3)}, {range: Range.create(1, 0, 1, 3)}] + return [{ range: Range.create(0, 0, 0, 3) }, { range: Range.create(1, 0, 1, 3) }] }) connection.onNotification('fireCodeLensRefresh', () => { - connection.sendRequest(CodeLensRefreshRequest.type) + void connection.sendRequest(CodeLensRefreshRequest.type) }) connection.onNotification('fireSemanticTokensRefresh', () => { - connection.sendRequest(SemanticTokensRefreshRequest.type) + void connection.sendRequest(SemanticTokensRefreshRequest.type) }) connection.onNotification('fireInlayHintsRefresh', () => { - connection.sendRequest(InlayHintRefreshRequest.type) + void connection.sendRequest(InlayHintRefreshRequest.type) }) connection.onCodeLensResolve(codelens => { - return {range: codelens.range, command: {title: 'format', command: 'editor.action.format'}} + return { range: codelens.range, command: { title: 'format', command: 'editor.action.format' } } }) connection.onDeclaration(params => { assert.equal(params.position.line, 1) assert.equal(params.position.character, 1) - return {uri: params.textDocument.uri, range: {start: {line: 1, character: 1}, end: {line: 1, character: 2}}} + return { uri: params.textDocument.uri, range: { start: { line: 1, character: 1 }, end: { line: 1, character: 2 } } } }) connection.onDefinition(params => { assert.equal(params.position.line, 1) assert.equal(params.position.character, 1) - return {uri: params.textDocument.uri, range: {start: {line: 0, character: 0}, end: {line: 0, character: 1}}} + return { uri: params.textDocument.uri, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } } } }) connection.onHover(_params => { @@ -267,7 +274,7 @@ connection.onHover(_params => { connection.onCompletion(_params => { return [ - {label: 'item', insertText: 'text'} + { label: 'item', insertText: 'text' } ] }) @@ -309,7 +316,7 @@ connection.onCodeAction(params => { connection.onExecuteCommand(params => { if (params.command == 'test_command') { - return {success: true} + return { success: true } } }) @@ -341,7 +348,7 @@ connection.onPrepareRename(_params => { }) connection.onRenameRequest(_params => { - return {documentChanges: []} + return { documentChanges: [] } }) connection.onDocumentLinks(_params => { @@ -355,6 +362,12 @@ connection.onDocumentLinkResolve(link => { return link }) +connection.onDocumentSymbol(_params => { + return [ + DocumentSymbol.create('name', undefined, SymbolKind.Method, Range.create(1, 1, 3, 1), Range.create(2, 1, 2, 3)) + ] +}) + connection.onDocumentColor(_params => { return [ ColorInformation.create(Range.create(1, 1, 1, 2), Color.create(1, 1, 1, 1)) @@ -376,7 +389,7 @@ connection.onFoldingRanges(_params => { connection.onImplementation(params => { assert.equal(params.position.line, 1) assert.equal(params.position.character, 1) - return {uri: params.textDocument.uri, range: {start: {line: 2, character: 2}, end: {line: 3, character: 3}}} + return { uri: params.textDocument.uri, range: { start: { line: 2, character: 2 }, end: { line: 3, character: 3 } } } }) connection.onSelectionRanges(_params => { @@ -386,9 +399,9 @@ connection.onSelectionRanges(_params => { }) let lastFileOperationRequest -connection.workspace.onDidCreateFiles(params => {lastFileOperationRequest = {type: 'create', params}}) -connection.workspace.onDidRenameFiles(params => {lastFileOperationRequest = {type: 'rename', params}}) -connection.workspace.onDidDeleteFiles(params => {lastFileOperationRequest = {type: 'delete', params}}) +connection.workspace.onDidCreateFiles(params => { lastFileOperationRequest = { type: 'create', params } }) +connection.workspace.onDidRenameFiles(params => { lastFileOperationRequest = { type: 'rename', params } }) +connection.workspace.onDidDeleteFiles(params => { lastFileOperationRequest = { type: 'delete', params } }) connection.onRequest( new ProtocolRequestType('testing/lastFileOperationRequest'), @@ -401,7 +414,7 @@ connection.workspace.onWillCreateFiles(params => { const createdFilenames = params.files.map(f => `${f.uri}`).join('\n') return { documentChanges: [{ - textDocument: {uri: '/dummy-edit', version: null}, + textDocument: { uri: '/dummy-edit', version: null }, edits: [ TextEdit.insert(Position.create(0, 0), `WILL CREATE:\n${createdFilenames}`), ] @@ -413,7 +426,7 @@ connection.workspace.onWillRenameFiles(params => { const renamedFilenames = params.files.map(f => `${f.oldUri} -> ${f.newUri}`).join('\n') return { documentChanges: [{ - textDocument: {uri: '/dummy-edit', version: null}, + textDocument: { uri: '/dummy-edit', version: null }, edits: [ TextEdit.insert(Position.create(0, 0), `WILL RENAME:\n${renamedFilenames}`), ] @@ -425,7 +438,7 @@ connection.workspace.onWillDeleteFiles(params => { const deletedFilenames = params.files.map(f => `${f.uri}`).join('\n') return { documentChanges: [{ - textDocument: {uri: '/dummy-edit', version: null}, + textDocument: { uri: '/dummy-edit', version: null }, edits: [ TextEdit.insert(Position.create(0, 0), `WILL DELETE:\n${deletedFilenames}`), ] @@ -436,7 +449,7 @@ connection.workspace.onWillDeleteFiles(params => { connection.onTypeDefinition(params => { assert.equal(params.position.line, 1) assert.equal(params.position.character, 1) - return {uri: params.textDocument.uri, range: {start: {line: 2, character: 2}, end: {line: 3, character: 3}}} + return { uri: params.textDocument.uri, range: { start: { line: 2, character: 2 }, end: { line: 3, character: 3 } } } }) connection.languages.callHierarchy.onPrepare(params => { @@ -490,6 +503,13 @@ connection.languages.semanticTokens.onDelta(() => { } }) +connection.languages.onLinkedEditingRange(() => { + return { + ranges: [Range.create(1, 1, 1, 1)], + wordPattern: '\\w' + } +}) + connection.languages.diagnostics.on(() => { return { kind: DocumentDiagnosticReportKind.Full, @@ -524,8 +544,8 @@ connection.languages.typeHierarchy.onPrepare(params => { selectionRange: Range.create(2, 2, 2, 2), uri: params.textDocument.uri } - typeHierarchySample.superTypes = [{...currentItem, name: 'classA', uri: 'uri-for-A'}] - typeHierarchySample.subTypes = [{...currentItem, name: 'classC', uri: 'uri-for-C'}] + typeHierarchySample.superTypes = [{ ...currentItem, name: 'classA', uri: 'uri-for-A' }] + typeHierarchySample.subTypes = [{ ...currentItem, name: 'classC', uri: 'uri-for-C' }] return [currentItem] }) @@ -561,21 +581,24 @@ connection.languages.inlayHint.resolve(hint => { return hint }) -connection.languages.onLinkedEditingRange(() => { - return { - ranges: [Range.create(1, 1, 1, 1)], - wordPattern: '\\w' - } +connection.languages.inlineCompletion.on(_params => { + return [ + InlineCompletionItem.create('text inline', 'te', Range.create(1, 2, 3, 4)) + ] +}) + +connection.workspace.textDocumentContent.on(_params => { + return { text: 'Some test content' } }) connection.onRequest( new ProtocolRequestType('testing/sendSampleProgress'), async (_, __) => { const progressToken = 'TEST-PROGRESS-TOKEN' - await connection.sendRequest(WorkDoneProgressCreateRequest.type, {token: progressToken}) - void connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'begin', title: 'Test Progress'}) - void connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'report', percentage: 50, message: 'Halfway!'}) - void connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'end', message: 'Completed!'}) + await connection.sendRequest(WorkDoneProgressCreateRequest.type, { token: progressToken }) + void connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'begin', title: 'Test Progress' }) + void connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'report', percentage: 50, message: 'Halfway!' }) + void connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'end', message: 'Completed!' }) }, ) @@ -583,13 +606,24 @@ connection.onRequest( new ProtocolRequestType('testing/beginOnlyProgress'), async (_, __) => { const progressToken = 'TEST-PROGRESS-BEGIN' - await connection.sendRequest(WorkDoneProgressCreateRequest.type, {token: progressToken}) + await connection.sendRequest(WorkDoneProgressCreateRequest.type, { token: progressToken }) }, ) +connection.onRequest(new ProtocolRequestType('testing/sendPercentageProgress'), async (_, __) => { + // According to the spec, the reported percentage has to be an integer. + // Because JS doesn't have integer support, we have rounding code in place. + const progressToken2 = 'TEST-PROGRESS-PERCENTAGE' + await connection.sendRequest(WorkDoneProgressCreateRequest.type, { token: progressToken2 }) + const progress = connection.window.attachWorkDoneProgress(progressToken2) + progress.begin('Test Progress', 0.1) + progress.report(49.9, 'Halfway!') + progress.done() +}) + connection.onWorkspaceSymbol(() => { return [ - {name: 'name', kind: SymbolKind.Array, location: {uri: 'file:///abc.txt'}} + { name: 'name', kind: SymbolKind.Array, location: { uri: 'file:///abc.txt' } } ] }) @@ -598,5 +632,10 @@ connection.onWorkspaceSymbolResolve(symbol => { return symbol }) +connection.onRequest(new ProtocolRequestType('testing/sendApplyEdit'), async (_, __) => { + const params = { label: 'Apply Edit', edit: {} } + await connection.sendRequest(ApplyWorkspaceEditRequest.type, params) +}) + // Listen on the connection connection.listen() diff --git a/src/__tests__/client/server/timeoutOnShutdownServer.js b/src/__tests__/client/server/timeoutOnShutdownServer.js index e6eacefce05..d5833db5c6a 100644 --- a/src/__tests__/client/server/timeoutOnShutdownServer.js +++ b/src/__tests__/client/server/timeoutOnShutdownServer.js @@ -1,12 +1,12 @@ "use strict" -Object.defineProperty(exports, "__esModule", {value: true}) -const node_1 = require("vscode-languageserver") +Object.defineProperty(exports, "__esModule", { value: true }) +const node_1 = require("vscode-languageserver/node") const connection = (0, node_1.createConnection)() -connection.onInitialize((_params) => { - return {capabilities: {}} +connection.onInitialize(_params => { + return { capabilities: {} } }) connection.onShutdown(async () => { - return new Promise((resolve) => { + return new Promise(resolve => { setTimeout(resolve, 200000) }) }) diff --git a/src/__tests__/client/utils.test.ts b/src/__tests__/client/utils.test.ts index a6b2a527823..528599000a4 100644 --- a/src/__tests__/client/utils.test.ts +++ b/src/__tests__/client/utils.test.ts @@ -49,16 +49,16 @@ test('DefaultErrorHandler', async () => { // ignore }) const handler = new DefaultErrorHandler('test', 2) - expect(handler.error(new Error('test'), { jsonrpc: '' }, 1)).toBe(ErrorAction.Continue) - expect(handler.error(new Error('test'), { jsonrpc: '' }, 5)).toBe(ErrorAction.Shutdown) + expect(handler.error(new Error('test'), { jsonrpc: '' }, 1).action).toBe(ErrorAction.Continue) + expect(handler.error(new Error('test'), { jsonrpc: '' }, 5).action).toBe(ErrorAction.Shutdown) handler.closed() handler.milliseconds = 1 await wait(10) let res = handler.closed() - expect(res).toBe(CloseAction.Restart) + expect(res.action).toBe(CloseAction.Restart) handler.milliseconds = 10 * 1000 res = handler.closed() - expect(res).toBe(CloseAction.DoNotRestart) + expect(res.action).toBe(CloseAction.DoNotRestart) spy.mockRestore() }) diff --git a/src/__tests__/modules/plugin.test.ts b/src/__tests__/modules/plugin.test.ts index 9c7c5df9daa..1e9fd9d2357 100644 --- a/src/__tests__/modules/plugin.test.ts +++ b/src/__tests__/modules/plugin.test.ts @@ -38,11 +38,14 @@ describe('Plugin', () => { describe('exports', () => { it('should exports all types from vscode-languageserver-types', () => { // TODO: remove inline types after inline completion added + // TODO: remove this after vscode-languageserver-types updated to 3.18 const excludes = [ 'EOL', 'URI', + 'LanguageKind', 'TextDocument', 'StringValue', + 'SnippetTextEdit', 'InlineCompletionItem', 'InlineCompletionList', 'InlineCompletionTriggerKind', diff --git a/src/__tests__/modules/server.js b/src/__tests__/modules/server.js index 308d6110188..850752484a3 100644 --- a/src/__tests__/modules/server.js +++ b/src/__tests__/modules/server.js @@ -1,15 +1,15 @@ "use strict" -Object.defineProperty(exports, "__esModule", {value: true}) -const node_1 = require("vscode-languageserver") +Object.defineProperty(exports, "__esModule", { value: true }) +const node_1 = require("vscode-languageserver/node") const connection = (0, node_1.createConnection)() let notified = false -connection.onInitialize((_params) => { +connection.onInitialize(_params => { return { capabilities: {} } }) -connection.onRequest('request', (param) => { +connection.onRequest('request', param => { return param.value + 1 }) @@ -18,7 +18,7 @@ connection.onNotification('notification', () => { }) connection.onRequest('notified', () => { - return {notified} + return { notified } }) connection.onRequest('triggerRequest', async () => { @@ -26,7 +26,7 @@ connection.onRequest('triggerRequest', async () => { }) connection.onNotification('triggerNotification', async () => { - await connection.sendNotification('notification', {x: 1}) + await connection.sendNotification('notification', { x: 1 }) }) connection.listen() diff --git a/src/core/files.ts b/src/core/files.ts index 412161f526a..63aec3b9b28 100644 --- a/src/core/files.ts +++ b/src/core/files.ts @@ -514,7 +514,8 @@ export default class Files { let { textDocument, edits } = change let { uri } = textDocument let doc = await this.loadResource(uri) - let revertEdit = await doc.applyEdits(edits, false, uri === currentUri) + // TODO: filter SnippetTextEdit for now + let revertEdit = await doc.applyEdits(edits.filter(edit => 'newText' in edit), false, uri === currentUri) if (revertEdit) { let version = doc.version let { newText, range } = revertEdit diff --git a/src/core/funcs.ts b/src/core/funcs.ts index 49a30fb3ed6..806a7890625 100644 --- a/src/core/funcs.ts +++ b/src/core/funcs.ts @@ -1,6 +1,6 @@ 'use strict' import type { Neovim } from '@chemzqm/neovim' -import type { DocumentFilter, DocumentSelector } from 'vscode-languageserver-protocol' +import { type DocumentFilter, type DocumentSelector } from 'vscode-languageserver-protocol' import { URI } from 'vscode-uri' import Configurations from '../configuration' import Resolver from '../model/resolver' @@ -137,7 +137,8 @@ export function score(selector: DocumentSelector | DocumentFilter | string, uri: } if (pattern) { - let p = caseInsensitive ? pattern.toLowerCase() : pattern + const realPattern = typeof pattern === 'string' ? pattern : pattern.pattern + let p = caseInsensitive ? realPattern.toLowerCase() : realPattern let f = caseInsensitive ? u.fsPath.toLowerCase() : u.fsPath if (p === f || minimatch(f, p, { dot: true })) { ret = Math.max(ret, 5) diff --git a/src/handler/refactor/index.ts b/src/handler/refactor/index.ts index b4e9dafdbe6..835dfb7ccce 100644 --- a/src/handler/refactor/index.ts +++ b/src/handler/refactor/index.ts @@ -167,7 +167,8 @@ export default class Refactor { for (let change of documentChanges || []) { if (TextDocumentEdit.is(change)) { let { textDocument, edits } = change - changes[textDocument.uri] = edits + // TODO: filter SnippetTextEdit for now + changes[textDocument.uri] = edits.filter(edit => 'newText' in edit) } } } diff --git a/src/language-client/client.ts b/src/language-client/client.ts index 4f25a5bf2f5..17207263705 100644 --- a/src/language-client/client.ts +++ b/src/language-client/client.ts @@ -1,21 +1,21 @@ 'use strict' -import type { ApplyWorkspaceEditParams, ApplyWorkspaceEditResult, CallHierarchyPrepareRequest, CancellationStrategy, CancellationToken, ClientCapabilities, CodeActionRequest, CodeLensRequest, CompletionRequest, ConfigurationRequest, ConnectionStrategy, DeclarationRequest, DefinitionRequest, DidChangeConfigurationNotification, DidChangeConfigurationRegistrationOptions, DidChangeTextDocumentNotification, DidChangeWatchedFilesNotification, DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersNotification, DidCloseTextDocumentNotification, DidCreateFilesNotification, DidDeleteFilesNotification, DidOpenTextDocumentNotification, DidRenameFilesNotification, DidSaveTextDocumentNotification, Disposable, DocumentColorRequest, DocumentDiagnosticRequest, DocumentFormattingRequest, DocumentHighlightRequest, DocumentLinkRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest, DocumentSelector, DocumentSymbolRequest, ExecuteCommandRegistrationOptions, ExecuteCommandRequest, FileOperationRegistrationOptions, FoldingRangeRequest, GenericNotificationHandler, GenericRequestHandler, HoverRequest, ImplementationRequest, InitializeParams, InitializeResult, InlineValueRequest, LinkedEditingRangeRequest, Message, MessageActionItem, MessageSignature, NotificationHandler, NotificationHandler0, NotificationType, NotificationType0, ProgressToken, ProgressType, ProtocolNotificationType, ProtocolNotificationType0, ProtocolRequestType, ProtocolRequestType0, PublishDiagnosticsParams, ReferencesRequest, RegistrationParams, RenameRequest, RequestHandler, RequestHandler0, RequestType, RequestType0, SelectionRangeRequest, SemanticTokensRegistrationType, ServerCapabilities, ShowDocumentParams, ShowDocumentResult, ShowMessageRequestParams, SignatureHelpRequest, TextDocumentRegistrationOptions, TextDocumentSyncOptions, TextEdit, TraceOptions, Tracer, TypeDefinitionRequest, TypeHierarchyPrepareRequest, UnregistrationParams, WillCreateFilesRequest, WillDeleteFilesRequest, WillRenameFilesRequest, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest, WorkDoneProgressBegin, WorkDoneProgressCreateRequest, WorkDoneProgressEnd, WorkDoneProgressReport, WorkspaceEdit, WorkspaceSymbolRequest } from 'vscode-languageserver-protocol' +import { ApplyWorkspaceEditParams, ApplyWorkspaceEditResult, CallHierarchyPrepareRequest, CancellationStrategy, CancellationToken, ClientCapabilities, CodeActionRequest, CodeLensRequest, CompletionRequest, ConfigurationRequest, ConnectionStrategy, DeclarationRequest, DefinitionRequest, DidChangeConfigurationNotification, DidChangeConfigurationRegistrationOptions, DidChangeTextDocumentNotification, DidChangeWatchedFilesNotification, DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersNotification, DidCloseTextDocumentNotification, DidCreateFilesNotification, DidDeleteFilesNotification, DidOpenTextDocumentNotification, DidRenameFilesNotification, DidSaveTextDocumentNotification, Disposable, DocumentColorRequest, DocumentDiagnosticRequest, DocumentFormattingRequest, DocumentHighlightRequest, DocumentLinkRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest, DocumentSelector, DocumentSymbolRequest, ExecuteCommandRegistrationOptions, ExecuteCommandRequest, FileEvent, FileOperationRegistrationOptions, FoldingRangeRequest, GenericNotificationHandler, GenericRequestHandler, HandlerResult, HoverRequest, ImplementationRequest, InitializeParams, InitializeResult, InlineCompletionRequest, InlineValueRequest, LinkedEditingRangeRequest, Message, MessageActionItem, MessageSignature, NotificationHandler, NotificationHandler0, NotificationType, NotificationType0, ProgressToken, ProgressType, ProtocolNotificationType, ProtocolNotificationType0, ProtocolRequestType, ProtocolRequestType0, PublishDiagnosticsParams, ReferencesRequest, RegistrationParams, RenameRequest, RequestHandler, RequestHandler0, RequestType, RequestType0, SelectionRangeRequest, SemanticTokensRegistrationType, ServerCapabilities, ShowDocumentParams, ShowDocumentResult, ShowMessageRequestParams, SignatureHelpRequest, TextDocumentRegistrationOptions, TextDocumentSyncOptions, TextEdit, TraceOptions, Tracer, TypeDefinitionRequest, TypeHierarchyPrepareRequest, UnregistrationParams, WillCreateFilesRequest, WillDeleteFilesRequest, WillRenameFilesRequest, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest, WorkDoneProgressBegin, WorkDoneProgressCreateRequest, WorkDoneProgressEnd, WorkDoneProgressReport, WorkspaceEdit, WorkspaceSymbolRequest } from 'vscode-languageserver-protocol' import { TextDocument } from "vscode-languageserver-textdocument" -import { Diagnostic, DiagnosticSeverity, DiagnosticTag, MarkupKind, TextDocumentEdit } from 'vscode-languageserver-types' +import { Diagnostic, DiagnosticTag, MarkupKind, TextDocumentEdit } from 'vscode-languageserver-types' import { URI } from 'vscode-uri' import { FileCreateEvent, FileDeleteEvent, FileRenameEvent, FileWillCreateEvent, FileWillDeleteEvent, FileWillRenameEvent, TextDocumentWillSaveEvent } from '../core/files' import DiagnosticCollection from '../diagnostic/collection' import languages from '../languages' import { createLogger } from '../logger' import type { MessageItem } from '../model/notification' -import { CallHierarchyProvider, CodeActionProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentLinkProvider, DocumentRangeFormattingEditProvider, DocumentSymbolProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, LinkedEditingRangeProvider, OnTypeFormattingEditProvider, ProviderResult, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, TypeHierarchyProvider, WorkspaceSymbolProvider } from '../provider' +import { CallHierarchyProvider, CodeActionProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentLinkProvider, DocumentRangeFormattingEditProvider, DocumentSymbolProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlineCompletionItemProvider, LinkedEditingRangeProvider, OnTypeFormattingEditProvider, ProviderResult, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, TypeHierarchyProvider, WorkspaceSymbolProvider } from '../provider' import { OutputChannel, Thenable } from '../types' import { defaultValue } from '../util' import { isFalsyOrEmpty, toArray } from '../util/array' import { CancellationError } from '../util/errors' import { sameFile } from '../util/fs' import * as Is from '../util/is' -import { os, path } from '../util/node' +import { os } from '../util/node' import { comparePosition } from '../util/position' import { ApplyWorkspaceEditRequest, createProtocolConnection, Emitter, ErrorCodes, Event, ExitNotification, FailureHandlingKind, InitializedNotification, InitializeRequest, InlayHintRequest, LogMessageNotification, LSPErrorCodes, MessageReader, MessageType, MessageWriter, PositionEncodingKind, PublishDiagnosticsNotification, RegistrationRequest, ResourceOperationKind, ResponseError, SemanticTokensDeltaRequest, SemanticTokensRangeRequest, SemanticTokensRequest, ShowDocumentRequest, ShowMessageNotification, ShowMessageRequest, ShutdownRequest, TextDocumentSyncKind, Trace, TraceFormat, UnregistrationRequest, WorkDoneProgress @@ -38,12 +38,13 @@ import { DocumentSymbolFeature, DocumentSymbolMiddleware } from './documentSymbo import { ExecuteCommandFeature, ExecuteCommandMiddleware } from './executeCommand' import { Connection, DynamicFeature, ensure, FeatureClient, LSPCancellationError, RegistrationData, StaticFeature, TextDocumentProviderFeature, TextDocumentSendFeature } from './features' import { DidCreateFilesFeature, DidDeleteFilesFeature, DidRenameFilesFeature, FileOperationsMiddleware, WillCreateFilesFeature, WillDeleteFilesFeature, WillRenameFilesFeature } from './fileOperations' -import { FileSystemWatcherFeature, FileSystemWatcherMiddleware } from './fileSystemWatcher' +import { DidChangeWatchedFileSignature, FileSystemWatcherFeature, FileSystemWatcherMiddleware } from './fileSystemWatcher' import { FoldingRangeFeature, FoldingRangeProviderMiddleware } from './foldingRange' import { $FormattingOptions, DocumentFormattingFeature, DocumentOnTypeFormattingFeature, DocumentRangeFormattingFeature, FormattingMiddleware } from './formatting' import { HoverFeature, HoverMiddleware } from './hover' import { ImplementationFeature, ImplementationMiddleware } from './implementation' import { InlayHintsFeature, InlayHintsMiddleware, InlayHintsProviderShape } from './inlayHint' +import { InlineCompletionItemFeature, InlineCompletionMiddleware } from './inlineCompletion' import { InlineValueFeature, InlineValueMiddleware, InlineValueProviderShape } from './inlineValue' import { LinkedEditingFeature, LinkedEditingRangeMiddleware } from './linkedEditingRange' import { ProgressFeature } from './progress' @@ -58,7 +59,7 @@ import { TypeDefinitionFeature, TypeDefinitionMiddleware } from './typeDefinitio import { TypeHierarchyFeature, TypeHierarchyMiddleware } from './typeHierarchy' import { currentTimeStamp, data2String, getLocale, getTraceMessage, parseTraceData, toMethod } from './utils' import * as cv from './utils/converter' -import { CloseAction, DefaultErrorHandler, ErrorAction, ErrorHandler, InitializationFailedHandler } from './utils/errorHandler' +import { CloseAction, CloseHandlerResult, DefaultErrorHandler, ErrorAction, ErrorHandler, InitializationFailedHandler } from './utils/errorHandler' import { ConsoleLogger, NullLogger } from './utils/logger' import * as UUID from './utils/uuid' import { $WorkspaceOptions, WorkspaceFolderMiddleware, WorkspaceFoldersFeature } from './workspaceFolders' @@ -93,26 +94,34 @@ function createConnection(input: MessageReader, output: MessageWriter, errorHand connection.onClose(closeHandler) let result: Connection = { id: '', - hasPendingResponse: (): boolean => connection.hasPendingResponse(), listen: (): void => connection.listen(), - sendRequest: (type: string | MessageSignature, ...params: any[]): Promise => { - return connection.sendRequest(toMethod(type), ...params) - }, - onRequest: (type: string | MessageSignature, handler: GenericRequestHandler): Disposable => connection.onRequest(toMethod(type), handler), - sendNotification: (type: string | MessageSignature, params?: any): Promise => { - return connection.sendNotification(toMethod(type), params) - }, - onNotification: (type: string | MessageSignature, handler: GenericNotificationHandler): Disposable => connection.onNotification(toMethod(type), handler), + + hasPendingResponse: connection.hasPendingResponse, + + sendRequest: connection.sendRequest, + onRequest: connection.onRequest, + + sendNotification: connection.sendNotification, + onNotification: connection.onNotification, onProgress: connection.onProgress, sendProgress: connection.sendProgress, - trace: ( - value: Trace, - tracer: Tracer, - sendNotificationOrTraceOptions: TraceOptions - ): Promise => { - return connection.trace(value, tracer, sendNotificationOrTraceOptions) + + trace: (value: Trace, tracer: Tracer, sendNotificationOrTraceOptions?: boolean | TraceOptions): Promise => { + const defaultTraceOptions: TraceOptions = { + sendNotification: false, + traceFormat: TraceFormat.Text + } + + if (sendNotificationOrTraceOptions === undefined) { + return connection.trace(value, tracer, defaultTraceOptions) + } else if (Is.boolean(sendNotificationOrTraceOptions)) { + return connection.trace(value, tracer, sendNotificationOrTraceOptions) + } else { + return connection.trace(value, tracer, sendNotificationOrTraceOptions) + } }, + initialize: (params: InitializeParams) => { return connection.sendRequest(InitializeRequest.type, params) }, @@ -129,6 +138,7 @@ function createConnection(input: MessageReader, output: MessageWriter, errorHand } export enum RevealOutputChannelOn { + Debug = 0, Info = 1, Warn = 2, Error = 3, @@ -143,12 +153,19 @@ export interface HandleDiagnosticsSignature { (this: void, uri: string, diagnostics: Diagnostic[]): void } -export type WorkspaceMiddleware = DidChangeConfigurationMiddleware & FileSystemWatcherMiddleware & ConfigurationMiddleware & WorkspaceFolderMiddleware & FileOperationsMiddleware +interface _WorkspaceMiddleware { + didChangeWatchedFile?: (this: void, event: FileEvent, next: DidChangeWatchedFileSignature) => Promise + handleApplyEdit?: (this: void, params: ApplyWorkspaceEditParams, next: ApplyWorkspaceEditRequest.HandlerSignature) => HandlerResult +} + +export type WorkspaceMiddleware = _WorkspaceMiddleware & DidChangeConfigurationMiddleware & FileSystemWatcherMiddleware & ConfigurationMiddleware & WorkspaceFolderMiddleware & FileOperationsMiddleware export interface _WindowMiddleware { - showDocument?: (this: void, params: ShowDocumentParams, next: ShowDocumentRequest.HandlerSignature) => Promise + showDocument?: ShowDocumentRequest.MiddlewareSignature } +export type WindowMiddleware = _WindowMiddleware + /** * The Middleware lets extensions intercept the request and notifications send and received * from the server @@ -156,19 +173,40 @@ export interface _WindowMiddleware { export interface _Middleware { handleDiagnostics?: (this: void, uri: string, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) => void handleWorkDoneProgress?: (this: void, token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, next: HandleWorkDoneProgressSignature) => void + handleRegisterCapability?: (this: void, params: RegistrationParams, next: RegistrationRequest.HandlerSignature) => Promise + handleUnregisterCapability?: (this: void, params: UnregistrationParams, next: UnregistrationRequest.HandlerSignature) => Promise workspace?: WorkspaceMiddleware - window?: _WindowMiddleware + window?: WindowMiddleware +} + +// A general middleware is applied to both requests and notifications +interface GeneralMiddleware { + sendRequest?( + this: void, + type: string | MessageSignature, + param: P | undefined, + token: CancellationToken | undefined, + next: (type: string | MessageSignature, param?: P, token?: CancellationToken) => Promise, + ): Promise + + sendNotification?( + this: void, + type: string | MessageSignature, + next: (type: string | MessageSignature, params?: R) => Promise, + params: R + ): Promise } +// TODO: TextDocumentContentMiddleware export type Middleware = _Middleware & TextDocumentSynchronizationMiddleware & SignatureHelpMiddleware & ReferencesMiddleware & DefinitionMiddleware & DocumentHighlightMiddleware & DocumentSymbolMiddleware & DocumentLinkMiddleware & CodeActionMiddleware & FormattingMiddleware & RenameMiddleware & CodeLensMiddleware & HoverMiddleware & CompletionMiddleware & ExecuteCommandMiddleware & TypeDefinitionMiddleware & ImplementationMiddleware & ColorProviderMiddleware & DeclarationMiddleware & FoldingRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware & - InlayHintsMiddleware & InlineValueMiddleware & TypeHierarchyMiddleware & + InlayHintsMiddleware & InlineCompletionMiddleware & InlineValueMiddleware & TypeHierarchyMiddleware & WorkspaceSymbolMiddleware & DiagnosticProviderMiddleware & LinkedEditingRangeMiddleware & - SelectionRangeProviderMiddleware + SelectionRangeProviderMiddleware & GeneralMiddleware export type LanguageClientOptions = { rootPatterns?: string[] @@ -199,6 +237,9 @@ export type LanguageClientOptions = { isTrusted?: boolean supportHtml?: boolean } + textSynchronization?: { + delayOpenNotifications: boolean + } } & $ConfigurationOptions & $CompletionOptions & $FormattingOptions & $DiagnosticPullOptions & $WorkspaceOptions type ResolvedClientOptions = { @@ -222,12 +263,16 @@ type ResolvedClientOptions = { isTrusted: boolean supportHtml?: boolean } + textSynchronization?: { + delayOpenNotifications: boolean + } } & $ConfigurationOptions & Required<$CompletionOptions> & Required<$FormattingOptions> & Required<$DiagnosticPullOptions> & Required<$WorkspaceOptions> export enum State { Stopped = 1, Running = 2, Starting = 3, + StartFailed = 4, } export interface StateChangeEvent { @@ -262,6 +307,11 @@ export namespace MessageTransports { } } +export enum ShutdownMode { + Restart = 'restart', + Stop = 'stop' +} + export abstract class BaseLanguageClient implements FeatureClient { public registeredExtensionName: string @@ -405,6 +455,9 @@ export abstract class BaseLanguageClient implements FeatureClient(type, ...params) + + let param: any | undefined + let token: CancellationToken | undefined + // Separate cancellation tokens from other parameters for a better client interface + if (params.length === 1) { + // CancellationToken is an interface, so we need to check if the first param complies to it + if (CancellationToken.is(params[0])) { + token = params[0] + } else { + param = params[0] + } + } else if (params.length === 2) { + param = params[0] + token = params[1] + } + if (token !== undefined && token.isCancellationRequested) { + return Promise.reject(new ResponseError(LSPErrorCodes.RequestCancelled, 'Request got cancelled')) + } + const _sendRequest = this._clientOptions.middleware?.sendRequest + if (_sendRequest !== undefined) { + // Return the general middleware invocation defining `next` as a utility function that reorganizes parameters to + // pass them to the original sendRequest function. + return _sendRequest(type, param, token, (type, param, token) => { + const params: any[] = [] + + // Add the parameters if there are any + if (param !== undefined) { + params.push(param) + } + + // Add the cancellation token if there is one + if (token !== undefined) { + params.push(token) + } + + return connection.sendRequest(type, ...params) + }) + } else { + return connection.sendRequest(type, ...params) + } } catch (error) { this.error(`Sending request ${toMethod(type)} failed.`, error) throw error @@ -528,7 +620,11 @@ export abstract class BaseLanguageClient implements FeatureClient => { + connection.onRequest(ShowDocumentRequest.type, async (params, token) => { const showDocument = async (params: ShowDocumentParams): Promise => { try { if (params.external === true || /^https?:\/\//.test(params.uri)) { @@ -938,7 +1038,7 @@ export abstract class BaseLanguageClient implements FeatureClient { // Wait 2 seconds on stop - return this.shutdown('stop', timeout) + return this.shutdown(ShutdownMode.Stop, timeout) } - private async shutdown(mode: 'suspend' | 'stop', timeout: number): Promise { + protected async shutdown(mode: ShutdownMode, timeout = 2000): Promise { // If the client is stopped or in its initial state return. if (this.$state === ClientState.Stopped || this.$state === ClientState.Initial) { return @@ -1112,7 +1212,11 @@ export abstract class BaseLanguageClient implements FeatureClient { this.$state = ClientState.Stopped - mode === 'stop' && this.cleanUpChannel() + mode === ShutdownMode.Stop && this.cleanUpChannel() this._onStart = undefined this._onStop = undefined this._connection = undefined @@ -1167,20 +1271,27 @@ export abstract class BaseLanguageClient implements FeatureClient listener.dispose()) this._listeners = [] } + + const disposables = this._listeners.splice(0, this._listeners.length) + for (const disposable of disposables) { + disposable.dispose() + } + if (this._syncedDocuments) { this._syncedDocuments.clear() } - for (let feature of this._features.values()) { + // Clear features in reverse order; + for (const feature of Array.from(this._features.entries()).map(entry => entry[1]).reverse()) { if (typeof feature.dispose === 'function') { feature.dispose() } } - if (mode === 'stop' && this._diagnostics) { + if ((mode === ShutdownMode.Stop || mode === ShutdownMode.Restart) && this._diagnostics !== undefined) { this._diagnostics.dispose() this._diagnostics = undefined } @@ -1214,47 +1325,45 @@ export abstract class BaseLanguageClient implements FeatureClient + protected abstract createMessageTransports(encoding: string): Promise private async createConnection(): Promise { let errorHandler = (error: Error, message: Message | undefined, count: number | undefined) => { - this.handleConnectionError(error, message, count) + this.handleConnectionError(error, message, count).catch(error => this.error(`Handling connection error failed`, error)) } let closeHandler = () => { - this.handleConnectionClosed() + this.handleConnectionClosed().catch(error => this.error(`Handling connection close failed`, error)) } const transports = await this.createMessageTransports(defaultValue(this._clientOptions.stdioEncoding, 'utf8')) this._connection = createConnection(transports.reader, transports.writer, errorHandler, closeHandler, this._clientOptions.connectionOptions) return this._connection } - protected handleConnectionClosed() { + protected async handleConnectionClosed(): Promise { // Check whether this is a normal shutdown in progress or the client stopped normally. if (this.$state === ClientState.Stopped) { logger.debug(`client ${this._id} normal closed`) return } try { - if (this._connection) { + if (this._connection !== undefined) { this._connection.dispose() } } catch (error) { // Disposing a connection could fail if error cases. } - let action = CloseAction.DoNotRestart - if (this.$state !== ClientState.Stopping && this._clientOptions.errorHandler) { + let handlerResult: CloseHandlerResult = { action: CloseAction.DoNotRestart } + if (this.$state !== ClientState.Stopping) { try { - action = this._clientOptions.errorHandler!.closed() + handlerResult = await this._clientOptions.errorHandler.closed() } catch (error) { // Ignore errors coming from the error handler. } } this._connection = undefined - if (action === CloseAction.DoNotRestart) { - this.error('Connection to server got closed. Server will not be restarted.', undefined, 'force') - this.cleanUp('stop') + if (handlerResult.action === CloseAction.DoNotRestart) { + this.error(handlerResult.message ?? 'Connection to server got closed. Server will not be restarted.', undefined, handlerResult.handled === true ? false : 'force') + this.cleanUp(ShutdownMode.Stop) if (this.$state === ClientState.Starting) { this.$state = ClientState.StartFailed } else { @@ -1262,13 +1371,13 @@ export abstract class BaseLanguageClient implements FeatureClient this.error(`Restarting server failed`, error, 'force')) } } @@ -1278,11 +1387,15 @@ export abstract class BaseLanguageClient implements FeatureClient { + let result = await this._clientOptions.errorHandler!.error(error, message, count) + if (result.action === ErrorAction.Shutdown) { + const message = `Client ${this._name}: connection to server is erroring.\n${error.message}\nShutting down server.` + this.error(result.message ?? message, error, result.handled === true ? false : 'force') this.stop().catch(this.error.bind(this, `Stopping server failed`)) + } else { + const message = `Client ${this._name}: connection to server is erroring.\n${error.message}` + this.error(result.message ?? message, error, result.handled === true ? false : 'force') } } @@ -1400,6 +1513,7 @@ export abstract class BaseLanguageClient implements FeatureClient & TextDocumentProviderFeature public getFeature(request: typeof LinkedEditingRangeRequest.method): DynamicFeature & TextDocumentProviderFeature public getFeature(request: typeof TypeHierarchyPrepareRequest.method): DynamicFeature & TextDocumentProviderFeature + public getFeature(request: typeof InlineCompletionRequest.method): DynamicFeature & TextDocumentProviderFeature public getFeature(request: typeof InlineValueRequest.method): DynamicFeature & TextDocumentProviderFeature public getFeature(request: typeof InlayHintRequest.method): DynamicFeature & TextDocumentProviderFeature public getFeature(request: typeof WorkspaceSymbolRequest.method): DynamicFeature & WorkspaceProviderFeature @@ -1450,6 +1564,7 @@ export abstract class BaseLanguageClient implements FeatureClient void /** * Called to fill in the client capabilities this feature implements. - * * @param capabilities The client capabilities to fill. */ fillClientCapabilities(capabilities: ClientCapabilities): void @@ -190,7 +188,6 @@ export interface StaticFeature { * A preflight where the server capabilities are shown to all features * before a feature is actually initialized. This allows feature to * capture some state if they are a pre-requisite for other features. - * * @param capabilities the server capabilities * @param documentSelector the document selector pass to the client's constructor. * May be `undefined` if the client was created without a selector. @@ -202,7 +199,6 @@ export interface StaticFeature { * when the client has successfully received the initialize request from * the server and before the client sends the initialized notification * to the server. - * * @param capabilities the server capabilities * @param documentSelector the document selector pass to the client's constructor. * May be `undefined` if the client was created without a selector. @@ -237,14 +233,12 @@ export interface DynamicFeature { /** * Called to fill the initialize params. - * * @params the initialize params. */ fillInitializeParams?: (params: InitializeParams) => void /** * Called to fill in the client capabilities this feature implements. - * * @param capabilities The client capabilities to fill. */ fillClientCapabilities(capabilities: ClientCapabilities): void @@ -253,7 +247,6 @@ export interface DynamicFeature { * A preflight where the server capabilities are shown to all features * before a feature is actually initialized. This allows feature to * capture some state if they are a pre-requisite for other features. - * * @param capabilities the server capabilities * @param documentSelector the document selector pass to the client's constructor. * May be `undefined` if the client was created without a selector. @@ -265,7 +258,6 @@ export interface DynamicFeature { * when the client has successfully received the initialize request from * the server and before the client sends the initialized notification * to the server. - * * @param capabilities the server capabilities. * @param documentSelector the document selector pass to the client's constructor. * May be `undefined` if the client was created without a selector. @@ -284,14 +276,12 @@ export interface DynamicFeature { /** * Is called when the server send a register request for the given message. - * * @param data additional registration data as defined in the protocol. */ register(data: RegistrationData): void /** * Is called when the server wants to unregister a feature. - * * @param id the id used when registering the feature. */ unregister(id: string): void @@ -683,6 +673,7 @@ export interface FeatureClient { getFeature(request: typeof SemanticTokensRegistrationType.method): DynamicFeature & TextDocumentProviderFeature getFeature(request: typeof LinkedEditingRangeRequest.method): DynamicFeature & TextDocumentProviderFeature getFeature(request: typeof TypeHierarchyPrepareRequest.method): DynamicFeature & TextDocumentProviderFeature + getFeature(request: typeof InlineCompletionRequest.method): DynamicFeature & TextDocumentProviderFeature getFeature(request: typeof InlineValueRequest.method): DynamicFeature & TextDocumentProviderFeature getFeature(request: typeof InlayHintRequest.method): DynamicFeature & TextDocumentProviderFeature getFeature(request: typeof WorkspaceSymbolRequest.method): DynamicFeature & WorkspaceProviderFeature diff --git a/src/language-client/index.ts b/src/language-client/index.ts index 818431db64b..9c54a91ac2f 100644 --- a/src/language-client/index.ts +++ b/src/language-client/index.ts @@ -8,7 +8,7 @@ import { child_process, fs, path } from '../util/node' import { terminate } from '../util/processes' import { createClientPipeTransport, createClientSocketTransport, Disposable, generateRandomPipeName, IPCMessageReader, IPCMessageWriter, StreamMessageReader, StreamMessageWriter } from '../util/protocol' import workspace from '../workspace' -import { BaseLanguageClient, LanguageClientOptions, MessageTransports } from './client' +import { BaseLanguageClient, LanguageClientOptions, MessageTransports, ShutdownMode } from './client' const logger = createLogger('language-client-index') const debugStartWith: string[] = ['--debug=', '--debug-brk=', '--inspect=', '--inspect-brk='] @@ -19,33 +19,6 @@ export * from './client' declare let v8debug: any -export interface ExecutableOptions { - cwd?: string - env?: any - detached?: boolean - shell?: boolean -} - -export interface Executable { - command: string - args?: string[] - options?: ExecutableOptions -} - -namespace Executable { - export function is(value: any): value is Executable { - return Is.string(value.command) - } -} - -export interface ForkOptions { - cwd?: string - env?: any - execPath?: string - encoding?: string - execArgv?: string[] -} - export enum TransportKind { stdio, ipc, @@ -82,6 +55,34 @@ namespace Transport { */ export type Transport = TransportKind | SocketTransport +export interface ExecutableOptions { + cwd?: string + env?: any + detached?: boolean + shell?: boolean +} + +export interface Executable { + command: string + transport?: Transport + args?: string[] + options?: ExecutableOptions +} + +namespace Executable { + export function is(value: any): value is Executable { + return Is.string(value.command) + } +} + +export interface ForkOptions { + cwd?: string + env?: any + execPath?: string + encoding?: string + execArgv?: string[] +} + export interface NodeModule { module: string transport?: Transport @@ -187,8 +188,8 @@ export class LanguageClient extends BaseLanguageClient { this._isInDebugMode = !!forceDebug } - public stop(timeout = STOP_TIMEOUT): Promise { - return super.stop(timeout).then(() => { + protected shutdown(mode: ShutdownMode, timeout = STOP_TIMEOUT): Promise { + return super.shutdown(mode, timeout).then(() => { if (this._serverProcess) { let toCheck = this._serverProcess this._serverProcess = undefined @@ -224,9 +225,9 @@ export class LanguageClient extends BaseLanguageClient { }, STOP_TIMEOUT) } - protected handleConnectionClosed(): void { + protected handleConnectionClosed(): Promise { this._serverProcess = undefined - super.handleConnectionClosed() + return super.handleConnectionClosed() } public get isInDebugMode(): boolean { @@ -303,8 +304,10 @@ export class LanguageClient extends BaseLanguageClient { if (runDebug.run || runDebug.debug) { if (typeof v8debug === 'object' || this._forceDebug || startedInDebugMode(process.execArgv)) { json = runDebug.debug + this._isInDebugMode = true } else { json = runDebug.run + this._isInDebugMode = false } } else { json = server as NodeModule | Executable diff --git a/src/language-client/inlineCompletion.ts b/src/language-client/inlineCompletion.ts new file mode 100644 index 00000000000..96b006ee0c9 --- /dev/null +++ b/src/language-client/inlineCompletion.ts @@ -0,0 +1,81 @@ +import { + CancellationToken, + ClientCapabilities, + Disposable, + DocumentSelector, + InlineCompletionContext, + InlineCompletionItem, + InlineCompletionList, + InlineCompletionOptions, + InlineCompletionParams, + InlineCompletionRegistrationOptions, + InlineCompletionRequest, + Position, + ServerCapabilities +} from 'vscode-languageserver-protocol' +import { TextDocument } from 'vscode-languageserver-textdocument' +import languages from '../languages' +import { InlineCompletionItemProvider, ProviderResult } from '../provider' +import { + ensure, + FeatureClient, + TextDocumentLanguageFeature +} from './features' +import * as UUID from './utils/uuid' + +export interface ProvideInlineCompletionItemsSignature { + (this: void, document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult +} + +export interface InlineCompletionMiddleware { + provideInlineCompletionItems?: (this: void, document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken, next: ProvideInlineCompletionItemsSignature) => ProviderResult +} + +export interface InlineCompletionProviderShape { + provider: InlineCompletionItemProvider +} + +export class InlineCompletionItemFeature extends TextDocumentLanguageFeature { + + constructor(client: FeatureClient) { + super(client, InlineCompletionRequest.type) + } + + public fillClientCapabilities(capabilities: ClientCapabilities): void { + const inlineCompletion = ensure(ensure(capabilities, 'textDocument')!, 'inlineCompletion')! + inlineCompletion.dynamicRegistration = true + } + + public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void { + const options = this.getRegistrationOptions(documentSelector, capabilities.inlineCompletionProvider) + if (!options) { + return + } + + this.register({ + id: UUID.generateUuid(), + registerOptions: options + }) + } + + protected registerLanguageProvider(options: InlineCompletionRegistrationOptions): [Disposable, InlineCompletionItemProvider] { + const provider: InlineCompletionItemProvider = { + provideInlineCompletionItems: (document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult => { + const provideInlineCompletionItems: ProvideInlineCompletionItemsSignature = (document, position, context, token) => { + const params: InlineCompletionParams = { + textDocument: { uri: document.uri }, + position, + context + } + return this.sendRequest(InlineCompletionRequest.type, params, token, null) + } + + const middleware = this._client.middleware + return middleware.provideInlineCompletionItems + ? middleware.provideInlineCompletionItems(document, position, context, token, provideInlineCompletionItems) + : provideInlineCompletionItems(document, position, context, token) + } + } + return [languages.registerInlineCompletionItemProvider(options.documentSelector, provider), provider] + } +} diff --git a/src/language-client/utils/errorHandler.ts b/src/language-client/utils/errorHandler.ts index 51f07209774..eae9d19497d 100644 --- a/src/language-client/utils/errorHandler.ts +++ b/src/language-client/utils/errorHandler.ts @@ -1,6 +1,5 @@ 'use strict' import type { InitializeError, Message, ResponseError } from 'vscode-languageserver-protocol' -import window from '../../window' /** * An action to be performed when the connection to a server got closed. @@ -16,6 +15,26 @@ export enum CloseAction { Restart = 2 } +export interface CloseHandlerResult { + /** + * The action to take. + */ + action: CloseAction + + /** + * An optional message to be presented to the user. + */ + message?: string + + /** + * If set to true the client assumes that the corresponding + * close handler has presented an appropriate message to the + * user and the message will only be log to the client's + * output channel. + */ + handled?: boolean +} + /** * An action to be performed when the connection is producing errors. */ @@ -30,6 +49,26 @@ export enum ErrorAction { Shutdown = 2 } +export interface ErrorHandlerResult { + /** + * The action to take. + */ + action: ErrorAction + + /** + * An optional message to be presented to the user. + */ + message?: string + + /** + * If set to true the client assumes that the corresponding + * error handler has presented an appropriate message to the + * user and the message will only be log to the client's + * output channel. + */ + handled?: boolean +} + /** * A pluggable error handler that is invoked when the connection is either * producing errors or got closed. @@ -37,18 +76,17 @@ export enum ErrorAction { export interface ErrorHandler { /** * An error has occurred while writing or reading from the connection. - * * @param error - the error received * @param message - the message to be delivered to the server if know. * @param count - a count indicating how often an error is received. Will * be reset if a message got successfully send or received. */ - error(error: Error, message: Message | undefined, count: number | undefined): ErrorAction + error(error: Error, message: Message | undefined, count: number | undefined): ErrorHandlerResult | Promise /** * The connection to the server got closed. */ - closed(): CloseAction + closed(): CloseHandlerResult | Promise } export interface InitializationFailedHandler { @@ -63,25 +101,28 @@ export class DefaultErrorHandler implements ErrorHandler { this.restarts = [] } - public error(_error: Error, _message: Message, count: number): ErrorAction { + public error(_error: Error, _message: Message, count: number): ErrorHandlerResult { if (count && count <= 3) { - return ErrorAction.Continue + return { action: ErrorAction.Continue } } - return ErrorAction.Shutdown + return { action: ErrorAction.Shutdown } } - public closed(): CloseAction { + public closed(): CloseHandlerResult { this.restarts.push(Date.now()) if (this.restarts.length < this.maxRestartCount) { - return CloseAction.Restart + return { action: CloseAction.Restart } } else { let diff = this.restarts[this.restarts.length - 1] - this.restarts[0] if (diff <= this.milliseconds) { - console.error(`The "${this.name}" server crashed ${this.maxRestartCount} times in the last 3 minutes. The server will not be restarted.`) - return CloseAction.DoNotRestart + console.error(`The "${this.name}" server crashed ${this.maxRestartCount + 1} times in the last 3 minutes. The server will not be restarted.`) + return { + action: CloseAction.DoNotRestart, + message: `The "${this.name}" server crashed ${this.maxRestartCount + 1} times in the last 3 minutes. The server will not be restarted.` + } } else { this.restarts.shift() - return CloseAction.Restart + return { action: CloseAction.Restart } } } } diff --git a/src/languages.ts b/src/languages.ts index 2542b388b72..bee825db7a9 100644 --- a/src/languages.ts +++ b/src/languages.ts @@ -5,7 +5,7 @@ import { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall import type { Sources } from './completion/sources' import DiagnosticCollection from './diagnostic/collection' import diagnosticManager from './diagnostic/manager' -import { CallHierarchyProvider, CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentLinkProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSelector, DocumentSemanticTokensProvider, DocumentSymbolProvider, DocumentSymbolProviderMetadata, FoldingContext, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineValuesProvider, LinkedEditingRangeProvider, OnTypeFormattingEditProvider, ReferenceContext, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, TypeHierarchyProvider, WorkspaceSymbolProvider } from './provider' +import { CallHierarchyProvider, CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentLinkProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSelector, DocumentSemanticTokensProvider, DocumentSymbolProvider, DocumentSymbolProviderMetadata, FoldingContext, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionItemProvider, InlineValuesProvider, LinkedEditingRangeProvider, OnTypeFormattingEditProvider, ReferenceContext, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, TypeHierarchyProvider, WorkspaceSymbolProvider } from './provider' import CallHierarchyManager from './provider/callHierarchyManager' import CodeActionManager from './provider/codeActionManager' import CodeLensManager from './provider/codeLensManager' @@ -21,6 +21,7 @@ import FormatRangeManager from './provider/formatRangeManager' import HoverManager from './provider/hoverManager' import ImplementationManager from './provider/implementationManager' import InlayHintManger, { InlayHintWithProvider } from './provider/inlayHintManager' +import InlineCompletionItemManager from './provider/inlineCompletionItemManager' import InlineValueManager from './provider/inlineValueManager' import LinkedEditingRangeManager from './provider/linkedEditingRangeManager' import OnTypeFormatManager from './provider/onTypeFormatManager' @@ -78,6 +79,7 @@ export enum ProviderName { LinkedEditing = 'linkedEditing', InlayHint = 'inlayHint', InlineValue = 'inlineValue', + InlineCompletion = 'inlineCompletion', TypeHierarchy = 'typeHierarchy' } @@ -118,6 +120,7 @@ class Languages { private semanticTokensRangeManager = new SemanticTokensRangeManager() private linkedEditingManager = new LinkedEditingRangeManager() private inlayHintManager = new InlayHintManger() + private inlineCompletionItemManager = new InlineCompletionItemManager() private inlineValueManager = new InlineValueManager() public registerReferenceProvider: (selector: DocumentSelector, provider: ReferenceProvider) => Disposable @@ -159,6 +162,10 @@ class Languages { return sources.createLanguageSource(name, shortcut, selector, provider, triggerCharacters, priority, allCommitCharacters) } + public registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable { + return this.inlineCompletionItemManager.register(selector, provider) + } + public registerCodeActionProvider(selector: DocumentSelector, provider: CodeActionProvider, clientId: string | undefined, codeActionKinds?: CodeActionKind[]): Disposable { return this.codeActionManager.register(selector, provider, clientId, codeActionKinds) } @@ -578,6 +585,8 @@ class Languages { return this.linkedEditingManager.hasProvider(document) case ProviderName.InlayHint: return this.inlayHintManager.hasProvider(document) + case ProviderName.InlineCompletion: + return this.inlineCompletionItemManager.hasProvider(document) case ProviderName.InlineValue: return this.inlineValueManager.hasProvider(document) case ProviderName.TypeHierarchy: diff --git a/src/model/editInspect.ts b/src/model/editInspect.ts index 48efef612f1..dd47507a808 100644 --- a/src/model/editInspect.ts +++ b/src/model/editInspect.ts @@ -1,6 +1,6 @@ 'use strict' import { Neovim } from '@chemzqm/neovim' -import { ChangeAnnotation, CreateFile, DeleteFile, Position, RenameFile, TextDocumentEdit, WorkspaceEdit } from 'vscode-languageserver-types' +import { ChangeAnnotation, CreateFile, DeleteFile, Position, RenameFile, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types' import { URI } from 'vscode-uri' import type { LinesChange } from '../core/files' import type Keymaps from '../core/keymaps' @@ -191,7 +191,8 @@ export function getOriginalLine(item: ChangedFileItem, change: TextDocumentEdit if (typeof item.lnum !== 'number') return undefined let lnum = item.lnum if (change) { - let edits = mergeSortEdits(change.edits) + // TODO: filter SnippetTextEdit for now + let edits = mergeSortEdits(change.edits.filter(edit => 'newText' in edit)) let pos = getPositionFromEdits(Position.create(lnum - 1, 0), edits) lnum = pos.line + 1 } diff --git a/src/provider/index.ts b/src/provider/index.ts index 02dff528d71..0b72d0953fc 100644 --- a/src/provider/index.ts +++ b/src/provider/index.ts @@ -1,5 +1,5 @@ 'use strict' -import type { CallHierarchyIncomingCall, DocumentFilter, CallHierarchyItem, CallHierarchyOutgoingCall, CancellationToken, CodeAction, CodeActionContext, CodeActionKind, CodeLens, Color, ColorInformation, ColorPresentation, Command, CompletionContext, CompletionItem, CompletionList, Definition, DefinitionLink, DocumentDiagnosticReport, DocumentHighlight, DocumentLink, DocumentSymbol, Event, FoldingRange, FormattingOptions, Hover, InlayHint, InlineValue, InlineValueContext, LinkedEditingRanges, Location, Position, PreviousResultId, Range, SelectionRange, SemanticTokens, SemanticTokensDelta, SignatureHelp, SignatureHelpContext, SymbolInformation, TextEdit, TypeHierarchyItem, WorkspaceDiagnosticReport, WorkspaceDiagnosticReportPartialResult, WorkspaceEdit, WorkspaceSymbol } from 'vscode-languageserver-protocol' +import type { CallHierarchyIncomingCall, DocumentFilter, CallHierarchyItem, CallHierarchyOutgoingCall, CancellationToken, CodeAction, CodeActionContext, CodeActionKind, CodeLens, Color, ColorInformation, ColorPresentation, Command, CompletionContext, CompletionItem, CompletionList, Definition, DefinitionLink, DocumentDiagnosticReport, DocumentHighlight, DocumentLink, DocumentSymbol, Event, FoldingRange, FormattingOptions, Hover, InlayHint, InlineValue, InlineValueContext, LinkedEditingRanges, Location, Position, PreviousResultId, Range, SelectionRange, SemanticTokens, SemanticTokensDelta, SignatureHelp, SignatureHelpContext, SymbolInformation, TextEdit, TypeHierarchyItem, WorkspaceDiagnosticReport, WorkspaceDiagnosticReportPartialResult, WorkspaceEdit, WorkspaceSymbol, InlineCompletionContext, InlineCompletionItem, InlineCompletionList } from 'vscode-languageserver-protocol' import type { TextDocument } from 'vscode-languageserver-textdocument' import type { URI } from 'vscode-uri' @@ -57,12 +57,10 @@ export type ProviderResult = export interface CompletionItemProvider { /** * Provide completion items for the given position and document. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. * @param context How the completion was triggered. - * * @return An array of completions, a [completion list](#CompletionList), or a thenable that resolves to either. * The lack of a result can be signaled by returning `undefined`, `null`, or an empty array. */ @@ -78,7 +76,6 @@ export interface CompletionItemProvider { * or [details](#CompletionItem.detail). * * The editor will only resolve a completion item once. - * * @param item A completion item currently active in the UI. * @param token A cancellation token. * @return The resolved completion item or a thenable that resolves to of such. It is OK to return the given @@ -90,6 +87,34 @@ export interface CompletionItemProvider { ): ProviderResult } +/** + * The inline completion item provider interface defines the contract between extensions and + * the inline completion feature. + * + * Providers are asked for completions either explicitly by a user gesture or implicitly when typing. + */ +export interface InlineCompletionItemProvider { + + /** + * Provides inline completion items for the given position and document. + * If inline completions are enabled, this method will be called whenever the user stopped typing. + * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion. + * In that case, all available inline completions should be returned. + * `context.triggerKind` can be used to distinguish between these scenarios. + * @param document The document inline completions are requested for. + * @param position The position inline completions are requested for. + * @param context A context object with additional information. + * @param token A cancellation token. + * @returns An array of completion items or a thenable that resolves to an array of completion items. + */ + provideInlineCompletionItems( + document: TextDocument, + position: Position, + context: InlineCompletionContext, + token: CancellationToken + ): ProviderResult +} + /** * The hover provider interface defines the contract between extensions and * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature. @@ -99,7 +124,6 @@ export interface HoverProvider { * Provide a hover for the given position and document. Multiple hovers at the same * position will be merged by the editor. A hover can have a range which defaults * to the word range at the position when omitted. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -121,7 +145,6 @@ export interface HoverProvider { export interface DefinitionProvider { /** * Provide the definition of the symbol at the given position and document. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -154,7 +177,6 @@ export interface DeclarationProvider { export interface SignatureHelpProvider { /** * Provide help for the signature at the given position and document. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -176,7 +198,6 @@ export interface SignatureHelpProvider { export interface TypeDefinitionProvider { /** * Provide the type definition of the symbol at the given position and document. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -208,7 +229,6 @@ export interface ReferenceContext { export interface ReferenceProvider { /** * Provide a set of project-wide references for the given position and document. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param context @@ -237,7 +257,6 @@ export interface FoldingRangeProvider { /** * Returns a list of folding ranges or null and undefined if the provider * does not want to participate or was cancelled. - * * @param document The document in which the command was invoked. * @param context Additional context information (for future use) * @param token A cancellation token. @@ -266,7 +285,6 @@ export interface DocumentSymbolProvider { /** * Provide symbol information for the given document. - * * @param document The document in which the command was invoked. * @param token A cancellation token. * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be @@ -285,7 +303,6 @@ export interface DocumentSymbolProvider { export interface ImplementationProvider { /** * Provide the implementations of the symbol at the given position and document. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -314,7 +331,6 @@ export interface WorkspaceSymbolProvider { * and scoring on the results. A good rule of thumb is to match case-insensitive and to simply check that the * characters of *query* appear in their order in a candidate symbol. Don't use prefix, substring, or similar * strict matching. - * * @param query A non-empty query string. * @param token A cancellation token. * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be @@ -330,7 +346,6 @@ export interface WorkspaceSymbolProvider { * is selected in the UI. Providers can implement this method and return incomplete symbols from * [`provideWorkspaceSymbols`](#WorkspaceSymbolProvider.provideWorkspaceSymbols) which often helps to improve * performance. - * * @param symbol The symbol that is to be resolved. Guaranteed to be an instance of an object returned from an * earlier call to `provideWorkspaceSymbols`. * @param token A cancellation token. @@ -351,7 +366,6 @@ export interface RenameProvider { /** * Provide an edit that describes changes that have to be made to one * or many resources to rename a symbol to a different name. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param newName The new name of the symbol. If the given name is not valid, the provider must return a rejected promise. @@ -370,7 +384,6 @@ export interface RenameProvider { * Optional function for resolving and validating a position *before* running rename. The result can * be a range or a range and a placeholder text. The placeholder text should be the identifier of the symbol * which is being renamed - when omitted the text in the returned range is used. - * * @param document The document in which rename will be invoked. * @param position The position at which rename will be invoked. * @param token A cancellation token. @@ -390,7 +403,6 @@ export interface RenameProvider { export interface DocumentFormattingEditProvider { /** * Provide formatting edits for a whole document. - * * @param document The document in which the command was invoked. * @param options Options controlling formatting. * @param token A cancellation token. @@ -415,7 +427,6 @@ export interface DocumentRangeFormattingEditProvider { * The given range is a hint and providers can decide to format a smaller * or larger range. Often this is done by adjusting the start and end * of the range to full syntax nodes. - * * @param document The document in which the command was invoked. * @param range The range which should be formatted. * @param options Options controlling formatting. @@ -440,7 +451,6 @@ export interface DocumentRangeFormattingEditProvider { export interface CodeActionProvider { /** * Provide commands for the given document and range. - * * @param document The document in which the command was invoked. * @param range The selector or range for which the command was invoked. This will always be a selection if * there is a currently active editor. @@ -460,7 +470,6 @@ export interface CodeActionProvider { * Given a code action fill in its [`edit`](#CodeAction.edit)-property. Changes to * all other properties, like title, are ignored. A code action that has an edit * will not be resolved. - * * @param codeAction A code action. * @param token A cancellation token. * @return The resolved code action or a thenable that resolves to such. It is OK to return the given @@ -491,7 +500,6 @@ export interface DocumentHighlightProvider { /** * Provide a set of document highlights, like all occurrences of a variable or * all exit-points of a function. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -514,7 +522,6 @@ export interface DocumentLinkProvider { /** * Provide links for the given document. Note that the editor ships with a default provider that detects * `http(s)` and `file` links. - * * @param document The document in which the command was invoked. * @param token A cancellation token. * @return An array of [document links](#DocumentLink) or a thenable that resolves to such. The lack of a result @@ -527,7 +534,6 @@ export interface DocumentLinkProvider { * link is selected in the UI. Providers can implement this method and return incomple links * (without target) from the [`provideDocumentLinks`](#DocumentLinkProvider.provideDocumentLinks) method which * often helps to improve performance. - * * @param link The link that is to be resolved. * @param token A cancellation token. */ @@ -549,7 +555,6 @@ export interface CodeLensProvider { * Compute a list of [lenses](#CodeLens). This call should return as fast as possible and if * computing the commands is expensive implementors should only return code lens objects with the * range set and implement [resolve](#CodeLensProvider.resolveCodeLens). - * * @param document The document in which the command was invoked. * @param token A cancellation token. * @return An array of code lenses or a thenable that resolves to such. The lack of a result can be @@ -560,7 +565,6 @@ export interface CodeLensProvider { /** * This function will be called for each visible code lens, usually when scrolling and after * calls to [compute](#CodeLensProvider.provideCodeLenses)-lenses. - * * @param codeLens code lens that must be resolved. * @param token A cancellation token. * @return The given, resolved code lens or thenable that resolves to such. @@ -580,7 +584,6 @@ export interface OnTypeFormattingEditProvider { * The given position and character should hint to the provider * what range the position to expand to, like find the matching `{` * when `}` has been entered. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param ch The character that has been typed. @@ -600,7 +603,6 @@ export interface DocumentColorProvider { /** * Provide colors for the given document. - * * @param document The document in which the command was invoked. * @param token A cancellation token. * @return An array of [color information](#ColorInformation) or a thenable that resolves to such. The lack of a result @@ -610,7 +612,6 @@ export interface DocumentColorProvider { /** * Provide [representations](#ColorPresentation) for a color. - * * @param color The color to show and insert. * @param context A context object with additional information * @param token A cancellation token. @@ -633,7 +634,6 @@ export interface TextDocumentContentProvider { * The editor will use the returned string-content to create a readonly * [document](#TextDocument). Resources allocated should be released when * the corresponding document has been [closed](#workspace.onDidCloseTextDocument). - * * @param uri An uri which scheme matches the scheme this provider was [registered](#workspace.registerTextDocumentContentProvider) for. * @param token A cancellation token. * @return A string or a thenable that resolves to such. @@ -660,7 +660,6 @@ export interface CallHierarchyProvider { * Bootstraps call hierarchy by returning the item that is denoted by the given document * and position. This item will be used as entry into the call graph. Providers should * return `undefined` or `null` when there is no item at the given location. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -673,7 +672,6 @@ export interface CallHierarchyProvider { * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed * and annotated edges inside the call graph, e.g the given item is the starting node and the result is the nodes * that can be reached. - * * @param item The hierarchy item for which incoming calls should be computed. * @param token A cancellation token. * @returns A set of incoming calls or a thenable that resolves to such. The lack of a result can be @@ -685,7 +683,6 @@ export interface CallHierarchyProvider { * Provide all outgoing calls for an item, e.g call calls to functions, methods, or constructors from the given item. In * graph terms this describes directed and annotated edges inside the call graph, e.g the given item is the starting * node and the result is the nodes that can be reached. - * * @param item The hierarchy item for which outgoing calls should be computed. * @param token A cancellation token. * @returns A set of outgoing calls or a thenable that resolves to such. The lack of a result can be @@ -759,7 +756,6 @@ export interface DocumentSemanticTokensProvider { * // 1st token, 2nd token, 3rd token * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] * ``` - * * @see [SemanticTokensBuilder](#SemanticTokensBuilder) for a helper to encode tokens as integers. * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider. * *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'. @@ -815,7 +811,6 @@ export interface LinkedEditingRangeProvider { * that have the same content. A change to one of the ranges can be applied to all other ranges if the new content * is valid. An optional word pattern can be returned with the result to describe valid contents. * If no result-specific word pattern is provided, the word pattern from the language configuration is used. - * * @param document The document in which the provider was invoked. * @param position The position at which the provider was invoked. * @param token A cancellation token. @@ -839,7 +834,6 @@ export interface InlayHintsProvider { * Provide inlay hints for the given range and document. * * *Note* that inlay hints that are not {@link Range.contains contained} by the given range are ignored. - * * @param document The document in which the command was invoked. * @param range The range for which inlay hints should be computed. * @param token A cancellation token. @@ -852,7 +846,6 @@ export interface InlayHintsProvider { * or complete label {@link InlayHintLabelPart parts}. * * *Note* that the editor will resolve an inlay hint at most once. - * * @param hint An inlay hint. * @param token A cancellation token. * @return The resolved inlay hint or a thenable that resolves to such. It is OK to return the given `item`. When no result is returned, the given `item` will be used. @@ -870,7 +863,6 @@ export interface TypeHierarchyProvider { * Bootstraps type hierarchy by returning the item that is denoted by the given document * and position. This item will be used as entry into the type graph. Providers should * return `undefined` or `null` when there is no item at the given location. - * * @param document The document in which the command was invoked. * @param position The position at which the command was invoked. * @param token A cancellation token. @@ -883,7 +875,6 @@ export interface TypeHierarchyProvider { * Provide all supertypes for an item, e.g all types from which a type is derived/inherited. In graph terms this describes directed * and annotated edges inside the type graph, e.g the given item is the starting node and the result is the nodes * that can be reached. - * * @param item The hierarchy item for which super types should be computed. * @param token A cancellation token. * @returns A set of direct supertypes or a thenable that resolves to such. The lack of a result can be @@ -895,7 +886,6 @@ export interface TypeHierarchyProvider { * Provide all subtypes for an item, e.g all types which are derived/inherited from the given item. In * graph terms this describes directed and annotated edges inside the type graph, e.g the given item is the starting * node and the result is the nodes that can be reached. - * * @param item The hierarchy item for which subtypes should be computed. * @param token A cancellation token. * @returns A set of direct subtypes or a thenable that resolves to such. The lack of a result can be @@ -913,7 +903,6 @@ export interface InlineValuesProvider { /** * An optional event to signal that inline values have changed. - * * @see {@link EventEmitter} */ onDidChangeInlineValues?: Event | undefined @@ -922,7 +911,6 @@ export interface InlineValuesProvider { * Provide "inline value" information for a given document and range. * The editor calls this method whenever debugging stops in the given document. * The returned inline values information is rendered in the editor at the end of lines. - * * @param document The document for which the inline values information is needed. * @param viewPort The visible document range for which inline values should be computed. * @param context A bag containing contextual information like the current location. diff --git a/src/provider/inlineCompletionItemManager.ts b/src/provider/inlineCompletionItemManager.ts new file mode 100644 index 00000000000..fa459e6133b --- /dev/null +++ b/src/provider/inlineCompletionItemManager.ts @@ -0,0 +1,36 @@ +'use strict' +import { v4 as uuid } from 'uuid' +import { TextDocument } from 'vscode-languageserver-textdocument' +import { InlineCompletionContext, InlineCompletionItem, InlineCompletionList, Position } from 'vscode-languageserver-types' +import { CancellationToken, Disposable } from '../util/protocol' +import { DocumentSelector, InlineCompletionItemProvider } from './index' +import Manager from './manager' + +export default class InlineCompletionItemManager extends Manager { + public register(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable { + return this.addProvider({ + id: uuid(), + selector, + provider + }) + } + + public async provideInlineCompletionItems( + document: TextDocument, + position: Position, + context: InlineCompletionContext, + token: CancellationToken + ): Promise { + const item = this.getProvider(document) + if (!item) return + + const { provider } = item + let res: InlineCompletionList | InlineCompletionItem[] = null + try { + res = await Promise.resolve(provider.provideInlineCompletionItems(document, position, context, token)) + } catch (e) { + this.handleResults([{ status: 'rejected', reason: e }], 'provideInlineCompletionItems') + } + return res + } +} diff --git a/src/util/textedit.ts b/src/util/textedit.ts index 81959b9ff00..395bbcc472b 100644 --- a/src/util/textedit.ts +++ b/src/util/textedit.ts @@ -1,5 +1,5 @@ 'use strict' -import { AnnotatedTextEdit, ChangeAnnotation, Position, Range, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types' +import { AnnotatedTextEdit, ChangeAnnotation, Position, Range, SnippetTextEdit, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types' import { LinesTextDocument } from '../model/textdocument' import { DocumentChange } from '../types' import { isFalsyOrEmpty } from './array' @@ -126,7 +126,7 @@ export function getConfirmAnnotations(changes: ReadonlyArray, ch return keys } -export function isDeniedEdit(edit: TextEdit | AnnotatedTextEdit, denied: string[]): boolean { +export function isDeniedEdit(edit: TextEdit | AnnotatedTextEdit | SnippetTextEdit, denied: string[]): boolean { if (AnnotatedTextEdit.is(edit) && denied.includes(edit.annotationId)) return true return false } diff --git a/tsconfig.json b/tsconfig.json index 6f6fb311dac..729c9cedbba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,9 +12,9 @@ "noUnusedLocals": false, "noUnusedParameters": false, "strictPropertyInitialization": false, - "target": "es2017", - "module": "commonjs", - "moduleResolution": "node", + "target": "es2020", + "module": "es2020", + "moduleResolution": "bundler", "lib": ["es2017", "es2018", "es2019", "ES2020.Promise"], "declaration": false, "resolveJsonModule": true,