From 1ee241eccd952ab52cee699eb62ff533d7b992ae Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Mon, 6 Sep 2021 17:10:47 +0100 Subject: [PATCH 1/2] Add support for textdocument/inlineValues --- README.md | 5 +- client-node-tests/src/converter.test.ts | 35 ++++- client-node-tests/src/integration.test.ts | 28 ++++ client-node-tests/src/servers/testServer.ts | 10 ++ client/src/common/client.ts | 10 +- client/src/common/codeConverter.ts | 9 ++ client/src/common/inlineValues.ts | 74 ++++++++++ client/src/common/protocolConverter.ts | 45 +++++- protocol/src/common/protocol.inlineValue.ts | 58 ++++++++ protocol/src/common/protocol.ts | 17 +++ server/src/common/server.ts | 12 +- types/src/main.ts | 155 ++++++++++++++++++++ 12 files changed, 449 insertions(+), 9 deletions(-) create mode 100644 client/src/common/inlineValues.ts create mode 100644 protocol/src/common/protocol.inlineValue.ts diff --git a/README.md b/README.md index f9452709c..e0ab6d1ac 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,10 @@ After cloning the repository, run `npm install` to install dependencies and `npm Library specific changes are: -- all `sendNotification` methods now return a promise. Returning a promise was necessary since the actual writing of the message to the underlying transport is async and a client for example could not determine if a notification was handed of to the transport. This is a breaking change in the sense that it might result in floating promise and might be flagged by a linter. +- all `sendNotification` methods now return a promise. Returning a promise was necessary since the actual writing of the message to the underlying transport is async and a client for example could not determine if a notification was handed off to the transport. This is a breaking change in the sense that it might result in floating promise and might be flagged by a linter. +- `InlineValuesRequest` protocol added: + - New APIs in Types: `InlineValues` + - New APIs in Protocol: `InlineValuesRequest`, `InlineValuesParams`, `InlineValuesClientCapabilities`, `InlineValuesProviderOptions`, ## 3.16.0 Protocol, 6.0.0 JSON-RPC, 7.0.0 Client and 7.0.0 Server. diff --git a/client-node-tests/src/converter.test.ts b/client-node-tests/src/converter.test.ts index bd679ab3b..2c279933a 100644 --- a/client-node-tests/src/converter.test.ts +++ b/client-node-tests/src/converter.test.ts @@ -999,6 +999,26 @@ suite('Protocol Converter', () => { strictEqual('file://localhost/folder/file.vscode', result.toString()); }); + test('InlineValues', () => { + const items: vscode.InlineValue[] = [ + new vscode.InlineValueText(new vscode.Range(1, 2, 8, 9), 'literalString'), + new vscode.InlineValueVariableLookup(new vscode.Range(1, 2, 8, 9), 'varName', false), + new vscode.InlineValueVariableLookup(new vscode.Range(1, 2, 8, 9), undefined, true), + new vscode.InlineValueEvaluatableExpression(new vscode.Range(1, 2, 8, 9), 'expression'), + new vscode.InlineValueEvaluatableExpression(new vscode.Range(1, 2, 8, 9), undefined), + ]; + + let result = p2c.asInlineValues(items); + + ok(result.every(proto.Range.is)); + + ok(proto.InlineValueText.is(result[0]) && result[0].text === 'literalString'); + ok(proto.InlineValueVariableLookup.is(result[1]) && result[1].variableName === 'varName' && result[1].caseSensitiveLookup === false); + ok(proto.InlineValueVariableLookup.is(result[2]) && result[2].variableName === undefined && result[2].caseSensitiveLookup === true); + ok(proto.InlineValueEvaluatableExpression.is(result[3]) && result[3].expression === 'expression'); + ok(proto.InlineValueEvaluatableExpression.is(result[4]) && result[4].expression === undefined); + }); + test('Bug #361', () => { const item: proto.CompletionItem = { 'label': 'MyLabel', @@ -1263,4 +1283,17 @@ suite('Code Converter', () => { let result = converter.asUri(vscode.Uri.parse('file://localhost/folder/file')); strictEqual('file://localhost/folder/file.vscode', result); }); -}); \ No newline at end of file + + test('InlineValuesContext', () => { + const item: proto.InlineValuesContext = { + stoppedLocation: new vscode.Range(1, 2, 8, 9), + }; + + let result = c2p.asInlineValuesContext(item); + + strictEqual(result.stoppedLocation.start.line, 1); + strictEqual(result.stoppedLocation.start.character, 2); + strictEqual(result.stoppedLocation.end.line, 8); + strictEqual(result.stoppedLocation.end.character, 9); + }); +}); diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index ba951ed71..ec56cb997 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -138,6 +138,7 @@ suite('Client integration', () => { foldingRangeProvider: true, implementationProvider: true, selectionRangeProvider: true, + inlineValuesProvider: true, typeDefinitionProvider: true, callHierarchyProvider: true, semanticTokensProvider: { @@ -646,6 +647,33 @@ suite('Client integration', () => { assert.strictEqual(middlewareCalled, true); }); + test('Inline Values', async () => { + const provider = client.getFeature(lsclient.InlineValuesRequest.method).getProvider(document); + isDefined(provider); + const results = (await provider.provideInlineValues(document, range, { frameId: 1, stoppedLocation: range }, tokenSource.token)); + + isArray(results, undefined, 3); + assert.ok(results.every((r) => rangeEqual(r.range, 1, 2, 3, 4))); + + assert.ok(results[0] instanceof vscode.InlineValueText); + assert.strictEqual((results[0] as vscode.InlineValueText).text, 'text'); + + assert.ok(results[1] instanceof vscode.InlineValueVariableLookup); + assert.strictEqual((results[1] as vscode.InlineValueVariableLookup).variableName, 'variableName'); + + assert.ok(results[2] instanceof vscode.InlineValueEvaluatableExpression); + assert.strictEqual((results[2] as vscode.InlineValueEvaluatableExpression).expression, 'expression'); + + let middlewareCalled: boolean = false; + middleware.provideInlineValues = (d, r, c, t, n) => { + middlewareCalled = true; + return n(d, r, c, t); + }; + await provider.provideInlineValues(document, range, { frameId: 1, stoppedLocation: range }, tokenSource.token); + middleware.provideInlineValues = undefined; + assert.strictEqual(middlewareCalled, true); + }); + test('Type Definition', async () => { const provider = client.getFeature(lsclient.TypeDefinitionRequest.method).getProvider(document); isDefined(provider); diff --git a/client-node-tests/src/servers/testServer.ts b/client-node-tests/src/servers/testServer.ts index 9f65ffc56..e882c0a51 100644 --- a/client-node-tests/src/servers/testServer.ts +++ b/client-node-tests/src/servers/testServer.ts @@ -9,6 +9,7 @@ import { DiagnosticTag, CompletionItemTag, TextDocumentSyncKind, MarkupKind, SignatureHelp, SignatureInformation, ParameterInformation, Location, Range, DocumentHighlight, DocumentHighlightKind, CodeAction, Command, TextEdit, Position, DocumentLink, ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress, + InlineValueText, InlineValueVariableLookup, InlineValueEvaluatableExpression, WorkDoneProgressCreateRequest, WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, DidDeleteFilesNotification, DidRenameFilesNotification, DidCreateFilesNotification, Proposed, ProposedFeatures, Diagnostic, DiagnosticSeverity } from '../../../server/node'; @@ -81,6 +82,7 @@ connection.onInitialize((params: InitializeParams): any => { foldingRangeProvider: true, implementationProvider: true, selectionRangeProvider: true, + inlineValuesProvider: {}, typeDefinitionProvider: true, callHierarchyProvider: true, semanticTokensProvider: { @@ -296,6 +298,14 @@ connection.onSelectionRanges((_params) => { ]; }); +connection.onInlineValues((_params) => { + return [ + InlineValueText.create(Range.create(1, 2, 3, 4), 'text'), + InlineValueVariableLookup.create(Range.create(1, 2, 3, 4), 'variableName', false), + InlineValueEvaluatableExpression.create(Range.create(1, 2, 3, 4), 'expression'), + ]; +}); + let lastFileOperationRequest: unknown; connection.workspace.onDidCreateFiles((params) => { lastFileOperationRequest = { type: 'create', params }; }); connection.workspace.onDidRenameFiles((params) => { lastFileOperationRequest = { type: 'rename', params }; }); diff --git a/client/src/common/client.ts b/client/src/common/client.ts index 547a71659..cdb094346 100644 --- a/client/src/common/client.ts +++ b/client/src/common/client.ts @@ -20,7 +20,7 @@ import { DocumentRangeFormattingEditProvider, OnTypeFormattingEditProvider, RenameProvider, DocumentLinkProvider, DocumentColorProvider, DeclarationProvider, FoldingRangeProvider, ImplementationProvider, SelectionRangeProvider, TypeDefinitionProvider, WorkspaceSymbolProvider, CallHierarchyProvider, DocumentSymbolProviderMetadata, EventEmitter, env as Env, TextDocumentShowOptions, FileWillCreateEvent, FileWillRenameEvent, FileWillDeleteEvent, FileCreateEvent, FileDeleteEvent, FileRenameEvent, - LinkedEditingRangeProvider, Event as VEvent, CancellationError + LinkedEditingRangeProvider, Event as VEvent, CancellationError, InlineValuesProvider } from 'vscode'; import { @@ -52,7 +52,7 @@ import { CancellationStrategy, SaveOptions, LSPErrorCodes, CodeActionResolveRequest, RegistrationType, SemanticTokensRegistrationType, InsertTextMode, ShowDocumentRequest, FileOperationRegistrationOptions, WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, DidCreateFilesNotification, DidDeleteFilesNotification, DidRenameFilesNotification, ShowDocumentParams, ShowDocumentResult, LinkedEditingRangeRequest, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, WorkDoneProgressReport, PrepareSupportDefaultBehavior, - SemanticTokensRequest, SemanticTokensRangeRequest, SemanticTokensDeltaRequest, Proposed + SemanticTokensRequest, SemanticTokensRangeRequest, SemanticTokensDeltaRequest, Proposed, InlineValuesRequest } from 'vscode-languageserver-protocol'; import { toJSONObject } from './configuration'; @@ -69,6 +69,7 @@ import type { SemanticTokensMiddleware, SemanticTokensProviders } from './semant import type { FileOperationsMiddleware } from './fileOperations'; import type { LinkedEditingRangeMiddleware } from './linkedEditingRange'; import type { DiagnosticFeatureProvider } from './proposed.diagnostic'; +import { InlineValuesProviderMiddleware } from './inlineValues'; import * as c2p from './codeConverter'; import * as p2c from './protocolConverter'; @@ -515,8 +516,8 @@ export interface _Middleware { } export type Middleware = _Middleware & TypeDefinitionMiddleware & ImplementationMiddleware & ColorProviderMiddleware & -FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware & -LinkedEditingRangeMiddleware; + FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware & + LinkedEditingRangeMiddleware & InlineValuesProviderMiddleware; export interface LanguageClientOptions { documentSelector?: DocumentSelector | string[]; @@ -3618,6 +3619,7 @@ export abstract class BaseLanguageClient { public getFeature(request: typeof FoldingRangeRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof ImplementationRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof SelectionRangeRequest.method): DynamicFeature & TextDocumentProviderFeature; + public getFeature(request: typeof InlineValuesRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof TypeDefinitionRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof CallHierarchyPrepareRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof SemanticTokensRegistrationType.method): DynamicFeature & TextDocumentProviderFeature; diff --git a/client/src/common/codeConverter.ts b/client/src/common/codeConverter.ts index 7e8f6b18c..48edac4f8 100644 --- a/client/src/common/codeConverter.ts +++ b/client/src/common/codeConverter.ts @@ -106,6 +106,8 @@ export interface Converter { asCodeActionContext(context: code.CodeActionContext): proto.CodeActionContext; + asInlineValuesContext(context: code.InlineValueContext): proto.InlineValuesContext; + asCommand(item: code.Command): proto.Command; asCodeLens(item: code.CodeLens): proto.CodeLens; @@ -724,6 +726,12 @@ export function createConverter(uriConverter?: URIConverter): Converter { return item.value; } + function asInlineValuesContext(context: code.InlineValueContext): proto.InlineValuesContext { + if (context === undefined || context === null) { + return context; + } + return proto.InlineValuesContext.create(context.stoppedLocation); + } function asCommand(item: code.Command): proto.Command { let result = proto.Command.create(item.title, item.command); @@ -834,6 +842,7 @@ export function createConverter(uriConverter?: URIConverter): Converter { asReferenceParams, asCodeAction, asCodeActionContext, + asInlineValuesContext, asCommand, asCodeLens, asFormattingOptions, diff --git a/client/src/common/inlineValues.ts b/client/src/common/inlineValues.ts new file mode 100644 index 000000000..bab63e6f6 --- /dev/null +++ b/client/src/common/inlineValues.ts @@ -0,0 +1,74 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { languages as Languages, Disposable, TextDocument, ProviderResult, Range as VRange, InlineValueContext as VInlineValueContext, InlineValue as VInlineValue, InlineValuesProvider } from 'vscode'; + +import { + ClientCapabilities, CancellationToken, ServerCapabilities, DocumentSelector, + InlineValuesParams, InlineValuesRequest, InlineValuesOptions, InlineValuesRegistrationOptions +} from 'vscode-languageserver-protocol'; + +import { TextDocumentFeature, BaseLanguageClient } from './client'; + +function ensure(target: T, key: K): T[K] { + if (target[key] === void 0) { + target[key] = Object.create(null) as any; + } + return target[key]; +} + +export interface ProvideInlineValuesSignature { + (this: void, document: TextDocument, viewPort: VRange, context: VInlineValueContext, token: CancellationToken): ProviderResult; +} + +export interface InlineValuesProviderMiddleware { + provideInlineValues?: (this: void, document: TextDocument, viewPort: VRange, context: VInlineValueContext, token: CancellationToken, next: ProvideInlineValuesSignature) => ProviderResult; +} + +export class InlineValueFeature extends TextDocumentFeature { + constructor(client: BaseLanguageClient) { + super(client, InlineValuesRequest.type); + } + + public fillClientCapabilities(capabilities: ClientCapabilities): void { + let capability = ensure(ensure(capabilities, 'textDocument')!, 'inlineValues')!; + capability.dynamicRegistration = true; + } + + public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void { + let [id, options] = this.getRegistration(documentSelector, capabilities.inlineValuesProvider); + if (!id || !options) { + return; + } + this.register({ id: id, registerOptions: options }); + } + + protected registerLanguageProvider(options: InlineValuesRegistrationOptions): [Disposable, InlineValuesProvider] { + const provider: InlineValuesProvider = { + provideInlineValues: (document, viewPort, context, token) => { + const client = this._client; + const provideInlineValues: ProvideInlineValuesSignature = (document, viewPort, context, token) => { + const requestParams: InlineValuesParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + viewPort: client.code2ProtocolConverter.asRange(viewPort), + context: client.code2ProtocolConverter.asInlineValuesContext(context) + }; + return client.sendRequest(InlineValuesRequest.type, requestParams, token).then( + (values) => client.protocol2CodeConverter.asInlineValues(values), + (error: any) => { + return client.handleFailedRequest(InlineValuesRequest.type, token, error, null); + } + ); + }; + const middleware = client.clientOptions.middleware!; + return middleware.provideInlineValues + ? middleware.provideInlineValues(document, viewPort, context, token, provideInlineValues) + : provideInlineValues(document, viewPort, context, token); + + } + }; + return [Languages.registerInlineValuesProvider(options.documentSelector!, provider), provider]; + } +} diff --git a/client/src/common/protocolConverter.ts b/client/src/common/protocolConverter.ts index a7ea54fd1..0d459a136 100644 --- a/client/src/common/protocolConverter.ts +++ b/client/src/common/protocolConverter.ts @@ -184,6 +184,12 @@ export interface Converter { asSelectionRanges(selectionRanges: ls.SelectionRange[] | undefined | null): code.SelectionRange[] | undefined; asSelectionRanges(selectionRanges: ls.SelectionRange[] | undefined | null): code.SelectionRange[] | undefined; + asInlineValue(value: ls.InlineValue): code.InlineValue; + asInlineValues(values: ls.InlineValue[]): code.InlineValue[]; + asInlineValues(values: undefined | null): undefined; + asInlineValues(values: ls.InlineValue[] | undefined | null): code.InlineValue[] | undefined; + asInlineValues(values: ls.InlineValue[] | undefined | null): code.InlineValue[] | undefined; + asSemanticTokensLegend(value: ls.SemanticTokensLegend): code.SemanticTokensLegend; asSemanticTokens(value: ls.SemanticTokens): code.SemanticTokens; @@ -610,7 +616,7 @@ export function createConverter(uriConverter: URIConverter | undefined, trustMar if (item.documentation !== undefined) { result.documentation = asDocumentation(item.documentation); } if (item.parameters !== undefined) { result.parameters = asParameterInformations(item.parameters); } if (item.activeParameter !== undefined) { result.activeParameter = item.activeParameter; } - {return result;} + { return result; } } function asParameterInformations(item: ls.ParameterInformation[]): code.ParameterInformation[] { @@ -750,7 +756,7 @@ export function createConverter(uriConverter: URIConverter | undefined, trustMar } function asSymbolTag(value: ls.SymbolTag): code.SymbolTag | undefined { - switch(value) { + switch (value) { case ls.SymbolTag.Deprecated: return code.SymbolTag.Deprecated; default: @@ -1086,6 +1092,39 @@ export function createConverter(uriConverter: URIConverter | undefined, trustMar return result; } + function asInlineValue(inlineValue: ls.InlineValue): code.InlineValue { + if (ls.InlineValueText.is(inlineValue)) { + return new code.InlineValueText( + asRange(inlineValue.range), + inlineValue.text, + ); + } else if (ls.InlineValueVariableLookup.is(inlineValue)) { + return new code.InlineValueVariableLookup( + asRange(inlineValue.range), + inlineValue.variableName, + inlineValue.caseSensitiveLookup, + ); + } else { + return new code.InlineValueEvaluatableExpression( + asRange(inlineValue.range), + inlineValue.expression, + ); + } + } + function asInlineValues(inlineValues: ls.InlineValue[]): code.SelectionRange[]; + function asInlineValues(inlineValues: undefined | null): undefined; + function asInlineValues(inlineValues: ls.SelectionRange[] | undefined | null): code.SelectionRange[] | undefined; + function asInlineValues(inlineValues: ls.SelectionRange[] | undefined | null): code.SelectionRange[] | undefined { + if (!Array.isArray(inlineValues)) { + return []; + } + let result: code.InlineValue[] = []; + for (let inlineValue of inlineValues) { + result.push(asInlineValue(inlineValue)); + } + return result; + } + //----- call hierarchy function asCallHierarchyItem(item: null): undefined; @@ -1254,6 +1293,8 @@ export function createConverter(uriConverter: URIConverter | undefined, trustMar asColorPresentations, asSelectionRange, asSelectionRanges, + asInlineValue, + asInlineValues, asSemanticTokensLegend, asSemanticTokens, asSemanticTokensEdit, diff --git a/protocol/src/common/protocol.inlineValue.ts b/protocol/src/common/protocol.inlineValue.ts new file mode 100644 index 000000000..d02ade66b --- /dev/null +++ b/protocol/src/common/protocol.inlineValue.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RequestHandler } from 'vscode-jsonrpc'; +import { TextDocumentIdentifier, Range, InlineValue, InlineValuesContext } from 'vscode-languageserver-types'; + +import { ProtocolRequestType } from './messages'; +import { TextDocumentRegistrationOptions, WorkDoneProgressOptions, StaticRegistrationOptions, WorkDoneProgressParams } from './protocol'; + +// ---- capabilities + +export interface InlineValuesClientCapabilities { + /** + * Whether implementation supports dynamic registration for inline value providers. + */ + dynamicRegistration?: boolean; +} + +export interface InlineValuesOptions extends WorkDoneProgressOptions { +} + +export interface InlineValuesRegistrationOptions extends InlineValuesOptions, TextDocumentRegistrationOptions, StaticRegistrationOptions { +} + +/** + * A parameter literal used in selection range requests. + */ +export interface InlineValuesParams extends WorkDoneProgressParams { + /** + * The text document. + */ + textDocument: TextDocumentIdentifier; + + /** + * The visible document range for which inline values should be computed. + */ + viewPort: Range; + + /** + * Additional information about the context in which inline values were + * requested. + */ + context: InlineValuesContext; +} + +/** + * A request to provide inline values in a document. The request's + * parameter is of type [InlineValuesParams](#InlineValuesParams), the + * response is of type [InlineValue[]](#InlineValue[]) or a Thenable + * that resolves to such. + */ +export namespace InlineValuesRequest { + export const method: 'textDocument/inlineValues' = 'textDocument/inlineValues'; + export const type = new ProtocolRequestType(method); + export type HandlerSignature = RequestHandler; +} diff --git a/protocol/src/common/protocol.ts b/protocol/src/common/protocol.ts index 16d1afb2c..9e76e42a7 100644 --- a/protocol/src/common/protocol.ts +++ b/protocol/src/common/protocol.ts @@ -40,6 +40,10 @@ import { SelectionRangeClientCapabilities, SelectionRangeOptions, SelectionRangeRequest, SelectionRangeParams, SelectionRangeRegistrationOptions } from './protocol.selectionRange'; +import { + InlineValuesClientCapabilities, InlineValuesOptions, InlineValuesRequest, InlineValuesParams, InlineValuesRegistrationOptions +} from './protocol.inlineValue'; + import { WorkDoneProgressClientCapabilities, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, WorkDoneProgress, WorkDoneProgressCreateParams, WorkDoneProgressCreateRequest, WorkDoneProgressCancelParams, WorkDoneProgressCancelNotification @@ -487,6 +491,13 @@ export interface TextDocumentClientCapabilities { */ selectionRange?: SelectionRangeClientCapabilities; + /** + * Capabilities specific to the `textDocument/inlineValues` request. + * + * @since 3.17.0 + */ + inlineValues?: InlineValuesClientCapabilities; + /** * Capabilities specific to `textDocument/publishDiagnostics` notification. */ @@ -852,6 +863,11 @@ export interface _ServerCapabilities { */ selectionRangeProvider?: boolean | SelectionRangeOptions | SelectionRangeRegistrationOptions; + /** + * The server provides inline values. + */ + inlineValuesProvider?: InlineValuesOptions; + /** * The server provides execute command support. */ @@ -3263,6 +3279,7 @@ export { FoldingRangeClientCapabilities, FoldingRangeOptions, FoldingRangeRequest, FoldingRangeParams, FoldingRangeRegistrationOptions, DeclarationClientCapabilities, DeclarationRequest, DeclarationParams, DeclarationRegistrationOptions, DeclarationOptions, SelectionRangeClientCapabilities, SelectionRangeOptions, SelectionRangeParams, SelectionRangeRequest, SelectionRangeRegistrationOptions, + InlineValuesClientCapabilities, InlineValuesOptions, InlineValuesParams, InlineValuesRequest, InlineValuesRegistrationOptions, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, WorkDoneProgress, WorkDoneProgressCreateParams, WorkDoneProgressCreateRequest, WorkDoneProgressCancelParams, WorkDoneProgressCancelNotification, // Call Hierarchy diff --git a/server/src/common/server.ts b/server/src/common/server.ts index fbf2a405e..d0032df86 100644 --- a/server/src/common/server.ts +++ b/server/src/common/server.ts @@ -24,7 +24,7 @@ import { DocumentSymbolRequest, WorkspaceSymbolRequest, CodeActionRequest, CodeLensRequest, CodeLensResolveRequest, DocumentFormattingRequest, DocumentRangeFormattingRequest, DocumentOnTypeFormattingRequest, RenameRequest, PrepareRenameRequest, DocumentLinkRequest, DocumentLinkResolveRequest, DocumentColorRequest, ColorPresentationRequest, FoldingRangeRequest, SelectionRangeRequest, ExecuteCommandRequest, InitializeRequest, ResponseError, RegistrationType, RequestType0, RequestType, - NotificationType0, NotificationType, CodeActionResolveRequest, RAL + NotificationType0, NotificationType, CodeActionResolveRequest, RAL, InlineValuesParams, InlineValue, InlineValuesRequest } from 'vscode-languageserver-protocol'; import * as Is from './utils/is'; @@ -1477,6 +1477,13 @@ export interface _Connection): void; + /** + * Installs a handler for the inline values request. + * + * @param handler The corresponding handler. + */ + onInlineValues(handler: ServerRequestHandler): void; + /** * Installs a handler for the execute command request. * @@ -1743,6 +1750,9 @@ export function createConnection connection.onRequest(SelectionRangeRequest.type, (params, cancel) => { return handler(params, cancel, attachWorkDone(connection, params), attachPartialResult(connection, params)); }), + onInlineValues: (handler) => connection.onRequest(InlineValuesRequest.type, (params, cancel) => { + return handler(params, cancel, attachWorkDone(connection, params)); + }), onExecuteCommand: (handler) => connection.onRequest(ExecuteCommandRequest.type, (params, cancel) => { return handler(params, cancel, attachWorkDone(connection, params), undefined); }), diff --git a/types/src/main.ts b/types/src/main.ts index c5d324348..41f07064e 100644 --- a/types/src/main.ts +++ b/types/src/main.ts @@ -3205,6 +3205,161 @@ export namespace SelectionRange { } } +/** + * Inline value information can be provided by different means: + * - directly as a text value (class InlineValueText). + * - as a name to use for a variable lookup (class InlineValueVariableLookup) + * - as an evaluatable expression (class InlineValueEvaluatableExpression) + * The InlineValue types combines all inline value types into one type. + */ +export type InlineValue = InlineValueText | InlineValueVariableLookup | InlineValueEvaluatableExpression; + +/** + * Provide inline value as text. + */ +export interface InlineValueText { + /** + * The document range for which the inline value applies. + */ + range: Range; + + /** + * The text of the inline value. + */ + text: string; +} + +/** + * The InlineValueText namespace provides functions to deal with InlineValueTexts. + * + * @since 3.17.0 + */ +export namespace InlineValueText { + /** + * Creates a new InlineValueText literal. + */ + export function create(range: Range, text: string): InlineValueText { + return { range, text }; + } + + export function is(value: InlineValue | undefined | null): value is InlineValueText { + const candidate = value as InlineValueText; + return candidate !== undefined && candidate !== null && Range.is(candidate.range) && Is.string(candidate.text); + } +} + +/** + * Provide inline value through a variable lookup. + * If only a range is specified, the variable name will be extracted from the underlying document. + * An optional variable name can be used to override the extracted name. + */ +export interface InlineValueVariableLookup { + /** + * The document range for which the inline value applies. + * The range is used to extract the variable name from the underlying document. + */ + range: Range; + + /** + * If specified the name of the variable to look up. + */ + variableName?: string; + + /** + * How to perform the lookup. + */ + caseSensitiveLookup: boolean; +} + + +/** + * The InlineValueVariableLookup namespace provides functions to deal with InlineValueVariableLookups. + * + * @since 3.17.0 + */ +export namespace InlineValueVariableLookup { + /** + * Creates a new InlineValueText literal. + */ + export function create(range: Range, variableName: string | undefined, caseSensitiveLookup: boolean): InlineValueVariableLookup { + return { range, variableName, caseSensitiveLookup }; + } + + export function is(value: InlineValue | undefined | null): value is InlineValueVariableLookup { + const candidate = value as InlineValueVariableLookup; + return candidate !== undefined && candidate !== null && Range.is(candidate.range) && Is.boolean(candidate.caseSensitiveLookup) + && (Is.string(candidate.variableName) || candidate.variableName === undefined); + } +} + + +/** + * Provide an inline value through an expression evaluation. + * If only a range is specified, the expression will be extracted from the underlying document. + * An optional expression can be used to override the extracted expression. + */ +export interface InlineValueEvaluatableExpression { + /** + * The document range for which the inline value applies. + * The range is used to extract the evaluatable expression from the underlying document. + */ + range: Range; + + /** + * If specified the expression overrides the extracted expression. + */ + expression?: string; +} + +/** + * The InlineValueEvaluatableExpression namespace provides functions to deal with InlineValueEvaluatableExpression. + * + * @since 3.17.0 + */ +export namespace InlineValueEvaluatableExpression { + /** + * Creates a new InlineValueEvaluatableExpression literal. + */ + export function create(range: Range, expression: string | undefined): InlineValueEvaluatableExpression { + return { range, expression }; + } + + export function is(value: InlineValue | undefined | null): value is InlineValueEvaluatableExpression { + const candidate = value as InlineValueEvaluatableExpression; + return candidate !== undefined && candidate !== null && Range.is(candidate.range) + && (Is.string(candidate.expression) || candidate.expression === undefined); + } +} + +export interface InlineValuesContext { + /** + * The document range where execution has stopped. + * Typically the end position of the range denotes the line where the inline values are shown. + */ + stoppedLocation: Range; +} + +/** + * The InlineValuesContext namespace provides helper functions to work with + * [InlineValuesContext](#InlineValuesContext) literals. + */ +export namespace InlineValuesContext { + /** + * Creates a new InlineValuesContext literal. + */ + export function create(stoppedLocation: Range): InlineValuesContext { + return { stoppedLocation }; + } + + /** + * Checks whether the given literal conforms to the [InlineValuesContext](#InlineValuesContext) interface. + */ + export function is(value: any): value is InlineValuesContext { + const candidate = value as InlineValuesContext; + return Is.defined(candidate) && Range.is(value.stoppedLocation); + } +} + /** * Represents programming constructs like functions or constructors in the context * of call hierarchy. From e1e28681fa1b20c3a887d00de0670a55064f7cc5 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Mon, 6 Sep 2021 18:11:09 +0100 Subject: [PATCH 2/2] Fix tests --- client-node-tests/src/converter.test.ts | 28 +++++++++++++---------- client-node-tests/src/integration.test.ts | 7 ++++-- client/src/common/commonClient.ts | 2 ++ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/client-node-tests/src/converter.test.ts b/client-node-tests/src/converter.test.ts index 2c279933a..4d47957af 100644 --- a/client-node-tests/src/converter.test.ts +++ b/client-node-tests/src/converter.test.ts @@ -1000,23 +1000,27 @@ suite('Protocol Converter', () => { }); test('InlineValues', () => { - const items: vscode.InlineValue[] = [ - new vscode.InlineValueText(new vscode.Range(1, 2, 8, 9), 'literalString'), - new vscode.InlineValueVariableLookup(new vscode.Range(1, 2, 8, 9), 'varName', false), - new vscode.InlineValueVariableLookup(new vscode.Range(1, 2, 8, 9), undefined, true), - new vscode.InlineValueEvaluatableExpression(new vscode.Range(1, 2, 8, 9), 'expression'), - new vscode.InlineValueEvaluatableExpression(new vscode.Range(1, 2, 8, 9), undefined), + const items: proto.InlineValue[] = [ + proto.InlineValueText.create(proto.Range.create(1, 2, 8, 9), 'literalString'), + proto.InlineValueVariableLookup.create(proto.Range.create(1, 2, 8, 9), 'varName', false), + proto.InlineValueVariableLookup.create(proto.Range.create(1, 2, 8, 9), undefined, true), + proto.InlineValueEvaluatableExpression.create(proto.Range.create(1, 2, 8, 9), 'expression'), + proto.InlineValueEvaluatableExpression.create(proto.Range.create(1, 2, 8, 9), undefined), ]; let result = p2c.asInlineValues(items); - ok(result.every(proto.Range.is)); - ok(proto.InlineValueText.is(result[0]) && result[0].text === 'literalString'); - ok(proto.InlineValueVariableLookup.is(result[1]) && result[1].variableName === 'varName' && result[1].caseSensitiveLookup === false); - ok(proto.InlineValueVariableLookup.is(result[2]) && result[2].variableName === undefined && result[2].caseSensitiveLookup === true); - ok(proto.InlineValueEvaluatableExpression.is(result[3]) && result[3].expression === 'expression'); - ok(proto.InlineValueEvaluatableExpression.is(result[4]) && result[4].expression === undefined); + ok(result.every((r) => r.range instanceof vscode.Range)); + for (const r of result) { + rangeEqual(r.range, proto.Range.create(1, 2, 8, 9)); + } + + ok(result[0] instanceof vscode.InlineValueText && result[0].text === 'literalString'); + ok(result[1] instanceof vscode.InlineValueVariableLookup && result[1].variableName === 'varName' && result[1].caseSensitiveLookup === false); + ok(result[2] instanceof vscode.InlineValueVariableLookup && result[2].variableName === undefined && result[2].caseSensitiveLookup === true); + ok(result[3] instanceof vscode.InlineValueEvaluatableExpression && result[3].expression === 'expression'); + ok(result[4] instanceof vscode.InlineValueEvaluatableExpression && result[4].expression === undefined); }); test('Bug #361', () => { diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index ec56cb997..6b19e5b26 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -138,7 +138,7 @@ suite('Client integration', () => { foldingRangeProvider: true, implementationProvider: true, selectionRangeProvider: true, - inlineValuesProvider: true, + inlineValuesProvider: {}, typeDefinitionProvider: true, callHierarchyProvider: true, semanticTokensProvider: { @@ -653,7 +653,10 @@ suite('Client integration', () => { const results = (await provider.provideInlineValues(document, range, { frameId: 1, stoppedLocation: range }, tokenSource.token)); isArray(results, undefined, 3); - assert.ok(results.every((r) => rangeEqual(r.range, 1, 2, 3, 4))); + + for (const r of results) { + rangeEqual(r.range, 1, 2, 3, 4); + } assert.ok(results[0] instanceof vscode.InlineValueText); assert.strictEqual((results[0] as vscode.InlineValueText).text, 'text'); diff --git a/client/src/common/commonClient.ts b/client/src/common/commonClient.ts index 07af1126b..feaa036b8 100644 --- a/client/src/common/commonClient.ts +++ b/client/src/common/commonClient.ts @@ -13,6 +13,7 @@ import { WorkspaceFoldersFeature } from './workspaceFolders'; import { FoldingRangeFeature } from './foldingRange'; import { DeclarationFeature } from './declaration'; import { SelectionRangeFeature } from './selectionRange'; +import { InlineValueFeature } from './inlineValues'; import { ProgressFeature } from './progress'; import { CallHierarchyFeature } from './callHierarchy'; import { SemanticTokensFeature } from './semanticTokens'; @@ -39,6 +40,7 @@ export abstract class CommonLanguageClient extends BaseLanguageClient { this.registerFeature(new FoldingRangeFeature(this)); this.registerFeature(new DeclarationFeature(this)); this.registerFeature(new SelectionRangeFeature(this)); + this.registerFeature(new InlineValueFeature(this)); this.registerFeature(new ProgressFeature(this)); this.registerFeature(new CallHierarchyFeature(this)); this.registerFeature(new SemanticTokensFeature(this));