diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index ba951ed71..187b64be8 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -175,7 +175,8 @@ suite('Client integration', () => { identifier: 'da348dc5-c30a-4515-9d98-31ff3be38d14', interFileDependencies: true, workspaceDiagnostics: true - } + }, + typeHierarchyProvider: true }, customResults: { 'hello': 'world' @@ -1121,6 +1122,46 @@ suite('Client integration', () => { assert.strictEqual(middlewareCalled, true); }); + test('Type Hierarchy', async () => { + const provider = client.getFeature(lsclient.Proposed.TypeHierarchyPrepareRequest.method).getProvider(document); + isDefined(provider); + const result = (await provider.prepareTypeHierarchy(document, position, tokenSource.token)) as vscode.TypeHierarchyItem[]; + + isArray(result, vscode.TypeHierarchyItem, 1); + const item = result[0]; + + let middlewareCalled: boolean = false; + middleware.prepareTypeHierarchy = (d, p, t, n) => { + middlewareCalled = true; + return n(d, p, t); + }; + await provider.prepareTypeHierarchy(document, position, tokenSource.token); + middleware.prepareTypeHierarchy = undefined; + assert.strictEqual(middlewareCalled, true); + + const incoming = (await provider.provideTypeHierarchySupertypes(item, tokenSource.token)) as vscode.TypeHierarchyItem[]; + isArray(incoming, vscode.TypeHierarchyItem, 1); + middlewareCalled = false; + middleware.provideTypeHierarchySupertypes = (i, t, n) => { + middlewareCalled = true; + return n(i, t); + }; + await provider.provideTypeHierarchySupertypes(item, tokenSource.token); + middleware.provideTypeHierarchySupertypes = undefined; + assert.strictEqual(middlewareCalled, true); + + const outgoing = (await provider.provideTypeHierarchySubtypes(item, tokenSource.token)) as vscode.TypeHierarchyItem[]; + isArray(outgoing, vscode.TypeHierarchyItem, 1); + middlewareCalled = false; + middleware.provideTypeHierarchySubtypes = (i, t, n) => { + middlewareCalled = true; + return n(i, t); + }; + await provider.provideTypeHierarchySubtypes(item, tokenSource.token); + middleware.provideTypeHierarchySubtypes = undefined; + assert.strictEqual(middlewareCalled, true); + }); + test('Stop fails if server crashes after shutdown request', async () => { let serverOptions: lsclient.ServerOptions = { module: path.join(__dirname, './servers/crashOnShutdownServer.js'), diff --git a/client-node-tests/src/runTests.ts b/client-node-tests/src/runTests.ts index 815b8b93b..86f881bb3 100644 --- a/client-node-tests/src/runTests.ts +++ b/client-node-tests/src/runTests.ts @@ -46,7 +46,7 @@ async function go() { * Basic usage */ await runTests({ - version: '1.58.0', + version: '1.59.0', extensionDevelopmentPath, extensionTestsPath, launchArgs: [ diff --git a/client-node-tests/src/servers/testServer.ts b/client-node-tests/src/servers/testServer.ts index 9f65ffc56..92d6fc0b5 100644 --- a/client-node-tests/src/servers/testServer.ts +++ b/client-node-tests/src/servers/testServer.ts @@ -10,7 +10,7 @@ import { Location, Range, DocumentHighlight, DocumentHighlightKind, CodeAction, Command, TextEdit, Position, DocumentLink, ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress, WorkDoneProgressCreateRequest, WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, DidDeleteFilesNotification, - DidRenameFilesNotification, DidCreateFilesNotification, Proposed, ProposedFeatures, Diagnostic, DiagnosticSeverity + DidRenameFilesNotification, DidCreateFilesNotification, Proposed, ProposedFeatures, Diagnostic, DiagnosticSeverity, TypeHierarchyItem } from '../../../server/node'; import { URI } from 'vscode-uri'; @@ -430,6 +430,31 @@ connection.languages.diagnostics.onWorkspace(() => { }; }); +let typeHierarchySample = { + superTypes: [] as TypeHierarchyItem[], + subTypes: [] as TypeHierarchyItem[] +}; +connection.languages.typeHierarchy.onPrepare((params) => { + const currentItem = { + kind: SymbolKind.Class, + name: 'ClazzB', + range: Range.create(1, 1, 1, 1), + selectionRange: Range.create(2, 2, 2, 2), + uri: params.textDocument.uri + } as TypeHierarchyItem; + typeHierarchySample.superTypes = [ {...currentItem, name: 'classA', uri: 'uri-for-A'}]; + typeHierarchySample.subTypes = [ {...currentItem, name: 'classC', uri: 'uri-for-C'}]; + return [currentItem]; +}); + +connection.languages.typeHierarchy.onSupertypes((_params) => { + return typeHierarchySample.superTypes; +}); + +connection.languages.typeHierarchy.onSubtypes((_params) => { + return typeHierarchySample.subTypes; +}); + connection.onRequest( new ProtocolRequestType('testing/sendSampleProgress'), async (_, __) => { diff --git a/client/src/common/client.ts b/client/src/common/client.ts index 895d99f75..53bddb4e6 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, TypeHierarchyProvider as VTypeHierarchyProvider } from 'vscode'; import { @@ -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 type { TypeHierarchyMiddleware } from './proposed.typeHierarchy'; import * as c2p from './codeConverter'; import * as p2c from './protocolConverter'; @@ -516,7 +517,7 @@ export interface _Middleware { export type Middleware = _Middleware & TypeDefinitionMiddleware & ImplementationMiddleware & ColorProviderMiddleware & FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware & -LinkedEditingRangeMiddleware; +LinkedEditingRangeMiddleware & TypeHierarchyMiddleware; export interface LanguageClientOptions { documentSelector?: DocumentSelector | string[]; @@ -3265,6 +3266,10 @@ export abstract class BaseLanguageClient { private doInitialize(connection: Connection, initParams: InitializeParams): Promise { return connection.initialize(initParams).then((result) => { + + // Trick (yanzh): always enable type hierarchy + (result as InitializeResult).capabilities.typeHierarchyProvider=true; + this._resolvedConnection = connection; this._initializeResult = result; this.state = ClientState.Running; @@ -3622,6 +3627,7 @@ export abstract class BaseLanguageClient { public getFeature(request: typeof CallHierarchyPrepareRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof SemanticTokensRegistrationType.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof LinkedEditingRangeRequest.method): DynamicFeature & TextDocumentProviderFeature; + public getFeature(request: typeof Proposed.TypeHierarchyPrepareRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof Proposed.DocumentDiagnosticRequest.method): DynamicFeature & TextDocumentProviderFeature; public getFeature(request: typeof WorkspaceSymbolRequest.method): DynamicFeature & WorkspaceProviderFeature; diff --git a/client/src/common/codeConverter.ts b/client/src/common/codeConverter.ts index 7e8f6b18c..e608a200b 100644 --- a/client/src/common/codeConverter.ts +++ b/client/src/common/codeConverter.ts @@ -16,6 +16,7 @@ import { ProtocolDiagnostic, DiagnosticCode } from './protocolDiagnostic'; import ProtocolCallHierarchyItem from './protocolCallHierarchyItem'; import { InsertTextMode, uinteger } from 'vscode-languageserver-protocol'; import { CreateFilesParams, DeleteFilesParams, RenameFilesParams } from 'vscode-languageserver-protocol/lib/common/protocol.fileOperations'; +import ProtocolTypeHierarchyItem from './protocolTypeHierarchyItem'; interface InsertReplaceRange { inserting: code.Range; @@ -121,6 +122,8 @@ export interface Converter { asDocumentLinkParams(textDocument: code.TextDocument): proto.DocumentLinkParams; asCallHierarchyItem(value: code.CallHierarchyItem): proto.CallHierarchyItem; + + asTypeHierarchyItem(value: code.TypeHierarchyItem): proto.TypeHierarchyItem; } export interface URIConverter { @@ -799,6 +802,22 @@ export function createConverter(uriConverter?: URIConverter): Converter { return result; } + function asTypeHierarchyItem(value: code.TypeHierarchyItem): proto.TypeHierarchyItem { + const result: proto.TypeHierarchyItem = { + name: value.name, + kind: asSymbolKind(value.kind), + uri: asUri(value.uri), + range: asRange(value.range), + selectionRange: asRange(value.selectionRange), + }; + if (value.detail !== undefined && value.detail.length > 0) { result.detail = value.detail; } + if (value.tags !== undefined) { result.tags = asSymbolTags(value.tags); } + if (value instanceof ProtocolTypeHierarchyItem && value.data !== undefined) { + result.data = value.data; + } + return result; + } + return { asUri, asTextDocumentIdentifier, @@ -841,6 +860,7 @@ export function createConverter(uriConverter?: URIConverter): Converter { asCodeLensParams, asDocumentLink, asDocumentLinkParams, - asCallHierarchyItem + asCallHierarchyItem, + asTypeHierarchyItem }; } diff --git a/client/src/common/commonClient.ts b/client/src/common/commonClient.ts index 07af1126b..52189553c 100644 --- a/client/src/common/commonClient.ts +++ b/client/src/common/commonClient.ts @@ -54,11 +54,13 @@ export abstract class CommonLanguageClient extends BaseLanguageClient { // Exporting proposed protocol. import * as pd from './proposed.diagnostic'; +import * as pt from './proposed.typeHierarchy'; export namespace ProposedFeatures { export function createAll(_client: BaseLanguageClient): (StaticFeature | DynamicFeature)[] { let result: (StaticFeature | DynamicFeature)[] = [ - new pd.DiagnosticFeature(_client) + new pd.DiagnosticFeature(_client), + new pt.TypeHierarchyFeature(_client) ]; return result; } -} \ No newline at end of file +} diff --git a/client/src/common/proposed.typeHierarchy.ts b/client/src/common/proposed.typeHierarchy.ts new file mode 100644 index 000000000..6d4242316 --- /dev/null +++ b/client/src/common/proposed.typeHierarchy.ts @@ -0,0 +1,139 @@ +/* -------------------------------------------------------------------------------------------- + * 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, Position as VPosition, CancellationToken, + TypeHierarchyProvider as VTypeHierarchyProvider, TypeHierarchyItem as VTypeHierarchyItem +} from 'vscode'; + +import { ClientCapabilities, DocumentSelector, ServerCapabilities, Proposed } from 'vscode-languageserver-protocol'; + +import { TextDocumentFeature, BaseLanguageClient, Middleware } from './client'; + +function ensure(target: T, key: K): T[K] { + if (target[key] === void 0) { + target[key] = {} as any; + } + return target[key]; +} + +export interface PrepareTypeHierarchySignature { + (this: void, document: TextDocument, position: VPosition, token: CancellationToken): ProviderResult; +} + +export interface TypeHierarchySupertypesSignature { + (this: void, item: VTypeHierarchyItem, token: CancellationToken): ProviderResult; +} + +export interface TypeHierarchySubtypesSignature { + (this: void, item: VTypeHierarchyItem, token: CancellationToken): ProviderResult; +} + +/** + * Type hierarchy middleware + * + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchyMiddleware { + prepareTypeHierarchy?: (this: void, document: TextDocument, positions: VPosition, token: CancellationToken, next: PrepareTypeHierarchySignature) => ProviderResult; + provideTypeHierarchySupertypes?: (this: void, item: VTypeHierarchyItem, token: CancellationToken, next: TypeHierarchySupertypesSignature) => ProviderResult; + provideTypeHierarchySubtypes?: (this: void, item: VTypeHierarchyItem, token: CancellationToken, next: TypeHierarchySubtypesSignature) => ProviderResult; +} + +class TypeHierarchyProvider implements VTypeHierarchyProvider { + + private middleware: Middleware & TypeHierarchyMiddleware; + + constructor(private client: BaseLanguageClient) { + this.middleware = client.clientOptions.middleware!; + } + + public prepareTypeHierarchy(document: TextDocument, position: VPosition, token: CancellationToken): ProviderResult { + const client = this.client; + const middleware = this.middleware; + const prepareTypeHierarchy: PrepareTypeHierarchySignature = (document, position, token) => { + const params = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); + return client.sendRequest(Proposed.TypeHierarchyPrepareRequest.type, params, token).then( + (result) => { + return client.protocol2CodeConverter.asTypeHierarchyItems(result); + }, + (error) => { + return client.handleFailedRequest(Proposed.TypeHierarchyPrepareRequest.type, token, error, null); + } + ); + }; + return middleware.prepareTypeHierarchy + ? middleware.prepareTypeHierarchy(document, position, token, prepareTypeHierarchy) + : prepareTypeHierarchy(document, position, token); + } + + public provideTypeHierarchySupertypes(item: VTypeHierarchyItem, token: CancellationToken): ProviderResult { + const client = this.client; + const middleware = this.middleware; + const provideTypeHierarchySupertypes: TypeHierarchySupertypesSignature = (item, token) => { + const params: Proposed.TypeHierarchySupertypesParams = { + item: client.code2ProtocolConverter.asTypeHierarchyItem(item) + }; + return client.sendRequest(Proposed.TypeHierarchySupertypesRequest.type, params, token).then( + (result) => { + return client.protocol2CodeConverter.asTypeHierarchyItems(result); + }, + (error) => { + return client.handleFailedRequest(Proposed.TypeHierarchySupertypesRequest.type, token, error, null); + } + ); + }; + return middleware.provideTypeHierarchySupertypes + ? middleware.provideTypeHierarchySupertypes(item, token, provideTypeHierarchySupertypes) + : provideTypeHierarchySupertypes(item, token); + } + + public provideTypeHierarchySubtypes(item: VTypeHierarchyItem, token: CancellationToken): ProviderResult { + const client = this.client; + const middleware = this.middleware; + const provideTypeHierarchySubtypes: TypeHierarchySubtypesSignature = (item, token) => { + const params: Proposed.TypeHierarchySubtypesParams = { + item: client.code2ProtocolConverter.asTypeHierarchyItem(item) + }; + return client.sendRequest(Proposed.TypeHierarchySubtypesRequest.type, params, token).then( + (result) => { + return client.protocol2CodeConverter.asTypeHierarchyItems(result); + }, + (error) => { + return client.handleFailedRequest(Proposed.TypeHierarchySubtypesRequest.type, token, error, null); + } + ); + }; + return middleware.provideTypeHierarchySubtypes + ? middleware.provideTypeHierarchySubtypes(item, token, provideTypeHierarchySubtypes) + : provideTypeHierarchySubtypes(item, token); + } +} + +export class TypeHierarchyFeature extends TextDocumentFeature { + constructor(client: BaseLanguageClient) { + super(client, Proposed.TypeHierarchyPrepareRequest.type); + } + + public fillClientCapabilities(cap: ClientCapabilities): void { + const capabilities: ClientCapabilities & Proposed.TypeHierarchyClientCapabilities = cap as ClientCapabilities & Proposed.TypeHierarchyClientCapabilities; + const capability = ensure(ensure(capabilities, 'textDocument')!, 'typeHierarchy')!; + capability.dynamicRegistration = true; + } + + public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void { + const [id, options] = this.getRegistration(documentSelector, capabilities.typeHierarchyProvider); + if (!id || !options) { + return; + } + this.register({ id: id, registerOptions: options }); + } + + protected registerLanguageProvider(options: Proposed.TypeHierarchyRegistrationOptions): [Disposable, TypeHierarchyProvider] { + const client = this._client; + const provider = new TypeHierarchyProvider(client); + return [Languages.registerTypeHierarchyProvider(options.documentSelector!, provider), provider]; + } +} diff --git a/client/src/common/protocolConverter.ts b/client/src/common/protocolConverter.ts index a7ea54fd1..b3f19bded 100644 --- a/client/src/common/protocolConverter.ts +++ b/client/src/common/protocolConverter.ts @@ -15,6 +15,7 @@ import ProtocolCodeAction from './protocolCodeAction'; import { ProtocolDiagnostic, DiagnosticCode } from './protocolDiagnostic'; import ProtocolCallHierarchyItem from './protocolCallHierarchyItem'; import { AnnotatedTextEdit, ChangeAnnotation, CompletionItemLabelDetails, InsertTextMode } from 'vscode-languageserver-protocol'; +import ProtocolTypeHierarchyItem from './protocolTypeHierarchyItem'; interface InsertReplaceRange { inserting: code.Range; @@ -225,6 +226,14 @@ export interface Converter { asLinkedEditingRanges(value: null | undefined): undefined; asLinkedEditingRanges(value: ls.LinkedEditingRanges): code.LinkedEditingRanges; asLinkedEditingRanges(value: ls.LinkedEditingRanges | null | undefined): code.LinkedEditingRanges | undefined; + + asTypeHierarchyItem(item: null): undefined; + asTypeHierarchyItem(item: ls.TypeHierarchyItem): code.TypeHierarchyItem; + asTypeHierarchyItem(item: ls.TypeHierarchyItem | null): code.TypeHierarchyItem | undefined; + + asTypeHierarchyItems(items: null): undefined; + asTypeHierarchyItems(items: ls.TypeHierarchyItem[]): code.TypeHierarchyItem[]; + asTypeHierarchyItems(items: ls.TypeHierarchyItem[] | null): code.TypeHierarchyItem[] | undefined; } export interface URIConverter { @@ -1201,6 +1210,37 @@ export function createConverter(uriConverter: URIConverter | undefined, trustMar return new RegExp(value); } + //------ Type Hierarchy + function asTypeHierarchyItem(item: null): undefined; + function asTypeHierarchyItem(item: ls.TypeHierarchyItem): code.TypeHierarchyItem; + function asTypeHierarchyItem(item: ls.TypeHierarchyItem | null): code.TypeHierarchyItem | undefined; + function asTypeHierarchyItem(item: ls.TypeHierarchyItem | null): code.TypeHierarchyItem | undefined { + if (item === null) { + return undefined; + } + let result = new ProtocolTypeHierarchyItem( + asSymbolKind(item.kind), + item.name, + item.detail || '', + asUri(item.uri), + asRange(item.range), + asRange(item.selectionRange), + item.data + ); + if (item.tags !== undefined) { result.tags = asSymbolTags(item.tags); } + return result; + } + + function asTypeHierarchyItems(items: null): undefined; + function asTypeHierarchyItems(items: ls.TypeHierarchyItem[]): code.TypeHierarchyItem[]; + function asTypeHierarchyItems(items: ls.TypeHierarchyItem[] | null): code.TypeHierarchyItem[] | undefined; + function asTypeHierarchyItems(items: ls.TypeHierarchyItem[] | null): code.TypeHierarchyItem[] | undefined { + if (items === null) { + return undefined; + } + return items.map(item => asTypeHierarchyItem(item)); + } + return { asUri, asDiagnostics, @@ -1264,6 +1304,8 @@ export function createConverter(uriConverter: URIConverter | undefined, trustMar asCallHierarchyIncomingCalls, asCallHierarchyOutgoingCall, asCallHierarchyOutgoingCalls, - asLinkedEditingRanges: asLinkedEditingRanges + asLinkedEditingRanges: asLinkedEditingRanges, + asTypeHierarchyItem, + asTypeHierarchyItems }; } diff --git a/client/src/common/protocolTypeHierarchyItem.ts b/client/src/common/protocolTypeHierarchyItem.ts new file mode 100644 index 000000000..96b3cda2a --- /dev/null +++ b/client/src/common/protocolTypeHierarchyItem.ts @@ -0,0 +1,16 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as code from 'vscode'; + +export default class ProtocolTypeHierarchyItem extends code.TypeHierarchyItem { + + public data?: unknown; + + constructor(kind: code.SymbolKind, name: string, detail: string, uri: code.Uri, range: code.Range, selectionRange: code.Range, data?: unknown) { + super(kind, name, detail, uri, range, selectionRange); + if (data !== undefined) { this.data = data; } + } +} \ No newline at end of file diff --git a/client/typings/vscode-proposed.d.ts b/client/typings/vscode-proposed.d.ts index e4cc280d2..751c73f67 100644 --- a/client/typings/vscode-proposed.d.ts +++ b/client/typings/vscode-proposed.d.ts @@ -16,4 +16,117 @@ declare module 'vscode' { // todo@API proper event type export const onDidChangeOpenEditors: Event; } + + //#region https://github.com/microsoft/vscode/issues/15533 --- Type hierarchy --- + + /** + * Represents an item of a type hierarchy, like a class or an interface. + */ + export class TypeHierarchyItem { + /** + * The name of this item. + */ + name: string; + + /** + * The kind of this item. + */ + kind: SymbolKind; + + /** + * Tags for this item. + */ + tags?: ReadonlyArray; + + /** + * More detail for this item, e.g. the signature of a function. + */ + detail?: string; + + /** + * The resource identifier of this item. + */ + uri: Uri; + + /** + * The range enclosing this symbol not including leading/trailing whitespace + * but everything else, e.g. comments and code. + */ + range: Range; + + /** + * The range that should be selected and revealed when this symbol is being + * picked, e.g. the name of a class. Must be contained by the {@link TypeHierarchyItem.range range}-property. + */ + selectionRange: Range; + + /** + * Creates a new type hierarchy item. + * + * @param kind The kind of the item. + * @param name The name of the item. + * @param detail The details of the item. + * @param uri The Uri of the item. + * @param range The whole range of the item. + * @param selectionRange The selection range of the item. + */ + constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range); + } + + /** + * The type hierarchy provider interface describes the contract between extensions + * and the type hierarchy feature. + */ + 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. + * @returns A type hierarchy item or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + prepareTypeHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + + /** + * 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 supertypes or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideTypeHierarchySupertypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; + + /** + * 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 subtypes or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideTypeHierarchySubtypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a type hierarchy provider. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A type hierarchy provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerTypeHierarchyProvider(selector: DocumentSelector, provider: TypeHierarchyProvider): Disposable; + } + //#endregion + } \ No newline at end of file diff --git a/protocol/src/common/api.ts b/protocol/src/common/api.ts index c54faa0d6..64532376a 100644 --- a/protocol/src/common/api.ts +++ b/protocol/src/common/api.ts @@ -59,7 +59,7 @@ export namespace LSPErrorCodes { } import * as diag from './proposed.diagnostic'; - +import * as typeh from './proposed.typeHierarchy'; export namespace Proposed { export type DiagnosticClientCapabilities = diag.DiagnosticClientCapabilities; export type $DiagnosticClientCapabilities = diag.$DiagnosticClientCapabilities; @@ -89,4 +89,17 @@ export namespace Proposed { export type WorkspaceDiagnosticReportPartialResult = diag.WorkspaceDiagnosticReportPartialResult; export const WorkspaceDiagnosticRequest: typeof diag.WorkspaceDiagnosticRequest = diag.WorkspaceDiagnosticRequest; export const DiagnosticRefreshRequest: typeof diag.DiagnosticRefreshRequest = diag.DiagnosticRefreshRequest; + + // type hierarchy + export type TypeHierarchyClientCapabilities = typeh.TypeHierarchyClientCapabilities; + export type TypeHierarchyOptions = typeh.TypeHierarchyOptions; + export type TypeHierarchyRegistrationOptions = typeh.TypeHierarchyRegistrationOptions; + export type TypeHierarchyPrepareParams = typeh.TypeHierarchyPrepareParams; + export type TypeHierarchySupertypesParams = typeh.TypeHierarchySupertypesParams; + export type TypeHierarchySubtypesParams = typeh.TypeHierarchySubtypesParams; + + export const TypeHierarchyPrepareRequest: typeof typeh.TypeHierarchyPrepareRequest = typeh.TypeHierarchyPrepareRequest; + export const TypeHierarchySupertypesRequest: typeof typeh.TypeHierarchySupertypesRequest = typeh.TypeHierarchySupertypesRequest; + export const TypeHierarchySubtypesRequest: typeof typeh.TypeHierarchySubtypesRequest = typeh.TypeHierarchySubtypesRequest; + } \ No newline at end of file diff --git a/protocol/src/common/proposed.typeHierarchy.md b/protocol/src/common/proposed.typeHierarchy.md new file mode 100644 index 000000000..b1d979964 --- /dev/null +++ b/protocol/src/common/proposed.typeHierarchy.md @@ -0,0 +1,158 @@ + +#### Prepare Type Hierarchy Request (:leftwards_arrow_with_hook:) + +> *Since version 3.17.0* + +The type hierarchy request is sent from the client to the server to return a type hierarchy for the language element of given text document positions. Will return `null` if the server couldn't infer a valid type from the position. The type hierarchy requests are executed in two steps: + + 1. first a type hierarchy item is prepared for the given text document position. + 1. for a type hierarchy item the supertype or subtype type hierarchy items are resolved. + +_Client Capability_: + +* property name (optional): `textDocument.typeHierarchy` +* property type: `TypeHierarchyClientCapabilities` defined as follows: + +```typescript +interface TypeHierarchyClientCapabilities { + /** + * Whether implementation supports dynamic registration. If this is set to + * `true` the client supports the new `(TextDocumentRegistrationOptions & + * StaticRegistrationOptions)` return value for the corresponding server + * capability as well. + */ + dynamicRegistration?: boolean; +} +``` + +_Server Capability_: + +* property name (optional): `typeHierarchyProvider` +* property type: `boolean | TypeHierarchyOptions | TypeHierarchyRegistrationOptions` where `TypeHierarchyOptions` is defined as follows: + +```typescript +export interface TypeHierarchyOptions extends WorkDoneProgressOptions { +} +``` + +_Registration Options_: `TypeHierarchyRegistrationOptions` defined as follows: + +```typescript +export interface TypeHierarchyRegistrationOptions extends + TextDocumentRegistrationOptions, TypeHierarchyOptions, + StaticRegistrationOptions { +} +``` + +_Request_: + +* method: 'textDocument/prepareTypeHierarchy' +* params: `TypeHierarchyPrepareParams` defined as follows: + +```typescript +export interface TypeHierarchyPrepareParams extends TextDocumentPositionParams, + WorkDoneProgressParams { +} +``` + +_Response_: + +* result: `TypeHierarchyItem[] | null` defined as follows: + +```typescript +export interface TypeHierarchyItem { + /** + * The name of this item. + */ + name: string; + + /** + * The kind of this item. + */ + kind: SymbolKind; + + /** + * Tags for this item. + */ + tags?: SymbolTag[]; + + /** + * More detail for this item, e.g. the signature of a function. + */ + detail?: string; + + /** + * The resource identifier of this item. + */ + uri: DocumentUri; + + /** + * The range enclosing this symbol not including leading/trailing whitespace + * but everything else, e.g. comments and code. + */ + range: Range; + + /** + * The range that should be selected and revealed when this symbol is being + * picked, e.g. the name of a function. Must be contained by the + * [`range`](#TypeHierarchyItem.range). + */ + selectionRange: Range; + + /** + * A data entry field that is preserved between a type hierarchy prepare and + * supertypes or subtypes requests. It could also be used to identify the + * type hierarchy in the server, helping improve the performance on + * resolving supertypes and subtypes. + */ + data?: unknown; +} +``` + +* error: code and message set in case an exception happens during the 'textDocument/prepareTypeHierarchy' request + +#### Type Hierarchy Supertypes(:leftwards_arrow_with_hook:) + +> *Since version 3.17.0* + +The request is sent from the client to the server to resolve the supertypes for a given type hierarchy item. Will return `null` if the server couldn't infer a valid type from `item` in the params. The request doesn't define its own client and server capabilities. It is only issued if a server registers for the [`textDocument/prepareTypeHierarchy` request](#textDocument_prepareTypeHierarchy). + +_Request_: + +* method: 'typeHierarchy/supertypes' +* params: `TypeHierarchySupertypesParams` defined as follows: + +```typescript +export interface TypeHierarchySupertypesParams extends + WorkDoneProgressParams, PartialResultParams { + item: TypeHierarchyItem; +} +``` +_Response_: + +* result: `TypeHierarchyItem[] | null` +* partial result: `TypeHierarchyItem[]` +* error: code and message set in case an exception happens during the 'typeHierarchy/supertypes' request + +#### Type Hierarchy Subtypes(:leftwards_arrow_with_hook:) + +> *Since version 3.17.0* + +The request is sent from the client to the server to resolve the subtypes for a given type hierarchy item. Will return `null` if the server couldn't infer a valid type from `item` in the params. The request doesn't define its own client and server capabilities. It is only issued if a server registers for the [`textDocument/prepareTypeHierarchy` request](#textDocument_prepareTypeHierarchy). + +_Request_: + +* method: 'typeHierarchy/subtypes' +* params: `TypeHierarchySubtypesParams` defined as follows: + +```typescript +export interface TypeHierarchySubtypesParams extends + WorkDoneProgressParams, PartialResultParams { + item: TypeHierarchyItem; +} +``` +_Response_: + +* result: `TypeHierarchyItem[] | null` +* partial result: `TypeHierarchyItem[]` +* error: code and message set in case an exception happens during the 'typeHierarchy/subtypes' request \ No newline at end of file diff --git a/protocol/src/common/proposed.typeHierarchy.ts b/protocol/src/common/proposed.typeHierarchy.ts new file mode 100644 index 000000000..85cbec500 --- /dev/null +++ b/protocol/src/common/proposed.typeHierarchy.ts @@ -0,0 +1,101 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) TypeFox, Microsoft and others. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { RequestHandler } from 'vscode-jsonrpc'; +import { TypeHierarchyItem } from 'vscode-languageserver-types'; + +import { ProtocolRequestType } from './messages'; +import { + TextDocumentRegistrationOptions, StaticRegistrationOptions, TextDocumentPositionParams, PartialResultParams, + WorkDoneProgressParams, WorkDoneProgressOptions +} from './protocol'; + +/** + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchyClientCapabilities { + /** + * Whether implementation supports dynamic registration. If this is set to `true` + * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + dynamicRegistration?: boolean; +} + +/** + * Type hierarchy options used during static registration. + * + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchyOptions extends WorkDoneProgressOptions { +} + +/** + * Type hierarchy options used during static or dynamic registration. + * + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchyRegistrationOptions extends TextDocumentRegistrationOptions, TypeHierarchyOptions, StaticRegistrationOptions { +} + +/** + * The parameter of a `textDocument/prepareTypeHierarchy` request. + * + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchyPrepareParams extends TextDocumentPositionParams, WorkDoneProgressParams { +} + +/** + * A request to result a `TypeHierarchyItem` in a document at a given position. + * Can be used as an input to a subtypes or supertypes type hierarchy. + * + * @since 3.17.0 - proposed state + */ +export namespace TypeHierarchyPrepareRequest { + export const method: 'textDocument/prepareTypeHierarchy' = 'textDocument/prepareTypeHierarchy'; + export const type = new ProtocolRequestType(method); + export type HandlerSignature = RequestHandler; +} + +/** + * The parameter of a `typeHierarchy/supertypes` request. + * + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchySupertypesParams extends WorkDoneProgressParams, PartialResultParams { + item: TypeHierarchyItem; +} + +/** + * A request to resolve the supertypes for a given `TypeHierarchyItem`. + * + * @since 3.17.0 - proposed state + */ +export namespace TypeHierarchySupertypesRequest { + export const method: 'typeHierarchy/supertypes' = 'typeHierarchy/supertypes'; + export const type = new ProtocolRequestType(method); + export type HandlerSignature = RequestHandler; +} + +/** + * The parameter of a `typeHierarchy/subtypes` request. + * + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchySubtypesParams extends WorkDoneProgressParams, PartialResultParams { + item: TypeHierarchyItem; +} + +/** + * A request to resolve the subtypes for a given `TypeHierarchyItem`. + * + * @since 3.17.0 - proposed state + */ +export namespace TypeHierarchySubtypesRequest { + export const method: 'typeHierarchy/subtypes' = 'typeHierarchy/subtypes'; + 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..a3defc8e7 100644 --- a/protocol/src/common/protocol.ts +++ b/protocol/src/common/protocol.ts @@ -51,7 +51,7 @@ import { } from './protocol.callHierarchy'; import { - SemanticTokensPartialResult, SemanticTokensDeltaPartialResult, TokenFormat, SemanticTokensClientCapabilities,SemanticTokensOptions, SemanticTokensRegistrationOptions, + SemanticTokensPartialResult, SemanticTokensDeltaPartialResult, TokenFormat, SemanticTokensClientCapabilities, SemanticTokensOptions, SemanticTokensRegistrationOptions, SemanticTokensParams, SemanticTokensRequest, SemanticTokensDeltaParams, SemanticTokensDeltaRequest, SemanticTokensRangeParams, SemanticTokensRangeRequest, SemanticTokensRefreshRequest, SemanticTokensWorkspaceClientCapabilities, SemanticTokensRegistrationType } from './protocol.semanticTokens'; @@ -75,6 +75,10 @@ import { UniquenessLevel, MonikerKind, Moniker, MonikerClientCapabilities, MonikerOptions, MonikerRegistrationOptions, MonikerParams, MonikerRequest } from './protocol.moniker'; +import { + TypeHierarchyClientCapabilities, TypeHierarchyOptions, TypeHierarchyRegistrationOptions, +} from './proposed.typeHierarchy'; + // @ts-ignore: to avoid inlining LocationLink as dynamic import let __noDynamicImport: LocationLink | undefined; @@ -519,6 +523,13 @@ export interface TextDocumentClientCapabilities { * @since 3.16.0 */ moniker?: MonikerClientCapabilities; + + /** + * Capabilities specific to the various type hierarchy requests. + * + * @since 3.17.0 - proposed state + */ + typeHierarchy?: TypeHierarchyClientCapabilities; } export interface WindowClientCapabilities { @@ -605,7 +616,7 @@ export interface GeneralClientCapabilities { * will retry the request if it receives a * response with error code `ContentModified`` */ - retryOnContentModified: string[]; + retryOnContentModified: string[]; } /** @@ -638,8 +649,8 @@ export interface _ClientCapabilities { textDocument?: TextDocumentClientCapabilities; /** - * Window specific client capabilities. - */ + * Window specific client capabilities. + */ window?: WindowClientCapabilities; /** @@ -897,6 +908,13 @@ export interface _ServerCapabilities { */ monikerProvider?: boolean | MonikerOptions | MonikerRegistrationOptions; + /** + * The server provides type hierarchy support. + * + * @since 3.17.0 - proposed state + */ + typeHierarchyProvider?: boolean | TypeHierarchyOptions | TypeHierarchyRegistrationOptions; + /** * Experimental server capabilities. */ @@ -2524,12 +2542,12 @@ export interface CodeActionClientCapabilities { * * @since 3.16.0 */ - resolveSupport?: { - /** - * The properties that a client can resolve lazily. - */ - properties: string[]; - }; + resolveSupport?: { + /** + * The properties that a client can resolve lazily. + */ + properties: string[]; + }; /** * Whether th client honors the change annotations in @@ -2995,7 +3013,7 @@ export namespace PrepareSupportDefaultBehavior { * The client's default behavior is to select the identifier * according the to language's syntax rule. */ - export const Identifier: 1 = 1; + export const Identifier: 1 = 1; } export type PrepareSupportDefaultBehavior = 1; @@ -3283,8 +3301,7 @@ export { DidRenameFilesNotification, RenameFilesParams, FileRename, WillRenameFilesRequest, DidDeleteFilesNotification, DeleteFilesParams, FileDelete, WillDeleteFilesRequest, // Monikers - UniquenessLevel, MonikerKind, Moniker, MonikerClientCapabilities, MonikerOptions, MonikerRegistrationOptions, MonikerParams, MonikerRequest - + UniquenessLevel, MonikerKind, Moniker, MonikerClientCapabilities, MonikerOptions, MonikerRegistrationOptions, MonikerParams, MonikerRequest, }; // To be backwards compatible diff --git a/server/src/common/proposed.typeHierarchy.ts b/server/src/common/proposed.typeHierarchy.ts new file mode 100644 index 000000000..6ca7fb821 --- /dev/null +++ b/server/src/common/proposed.typeHierarchy.ts @@ -0,0 +1,48 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +import { Proposed, TypeHierarchyItem } from 'vscode-languageserver-protocol'; + +import type { Feature, _Languages, ServerRequestHandler } from './server'; + +/** + * Shape of the type hierarchy feature + * + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchyFeatureShape { + typeHierarchy: { + onPrepare(handler: ServerRequestHandler): void; + onSupertypes(handler: ServerRequestHandler): void; + onSubtypes(handler: ServerRequestHandler): void; + } +} + +export const TypeHierarchyFeature: Feature<_Languages, TypeHierarchyFeatureShape> = (Base) => { + return class extends Base { + public get typeHierarchy() { + return { + onPrepare: (handler: ServerRequestHandler): void => { + this.connection.onRequest(Proposed.TypeHierarchyPrepareRequest.type, (params, cancel) => { + return handler(params, cancel, this.attachWorkDoneProgress(params), undefined); + }); + }, + onSupertypes: (handler: ServerRequestHandler): void => { + const type = Proposed.TypeHierarchySupertypesRequest.type; + this.connection.onRequest(type, (params, cancel) => { + return handler(params, cancel, this.attachWorkDoneProgress(params), this.attachPartialResultProgress(type, params)); + }); + }, + onSubtypes: (handler: ServerRequestHandler): void => { + const type = Proposed.TypeHierarchySubtypesRequest.type; + this.connection.onRequest(type, (params, cancel) => { + return handler(params, cancel, this.attachWorkDoneProgress(params), this.attachPartialResultProgress(type, params)); + }); + } + }; + } + }; +}; \ No newline at end of file diff --git a/server/src/common/server.ts b/server/src/common/server.ts index fbf2a405e..f8fc8ac70 100644 --- a/server/src/common/server.ts +++ b/server/src/common/server.ts @@ -38,6 +38,7 @@ import { ShowDocumentFeatureShape, ShowDocumentFeature } from './showDocument'; import { FileOperationsFeature, FileOperationsFeatureShape } from './fileOperations'; import { LinkedEditingRangeFeature, LinkedEditingRangeFeatureShape } from './linkedEditingRange'; import { MonikerFeature, MonikerFeatureShape } from './moniker'; +import { TypeHierarchyFeature, TypeHierarchyFeatureShape } from './proposed.typeHierarchy'; function null2Undefined(value: T | null): T | undefined { if (value === null) { @@ -1018,8 +1019,8 @@ export class _LanguagesImpl implements Remote, _Languages { } } -export type Languages = _Languages & CallHierarchy & SemanticTokensFeatureShape & LinkedEditingRangeFeatureShape & MonikerFeatureShape; -const LanguagesImpl: new () => Languages = MonikerFeature(LinkedEditingRangeFeature(SemanticTokensFeature(CallHierarchyFeature(_LanguagesImpl)))) as (new () => Languages); +export type Languages = _Languages & CallHierarchy & SemanticTokensFeatureShape & LinkedEditingRangeFeatureShape & MonikerFeatureShape & TypeHierarchyFeatureShape; +const LanguagesImpl: new () => Languages = TypeHierarchyFeature(MonikerFeature(LinkedEditingRangeFeature(SemanticTokensFeature(CallHierarchyFeature(_LanguagesImpl))))) as (new () => Languages); /** * An empty interface for new proposed API. diff --git a/types/src/main.ts b/types/src/main.ts index c5d324348..8e6629baf 100644 --- a/types/src/main.ts +++ b/types/src/main.ts @@ -3425,6 +3425,50 @@ export interface SemanticTokensDelta { edits: SemanticTokensEdit[]; } +/** + * @since 3.17.0 - proposed state + */ +export interface TypeHierarchyItem { + /** + * The name of this item. + */ + name: string; + /** + * The kind of this item. + */ + kind: SymbolKind; + /** + * Tags for this item. + */ + tags?: SymbolTag[]; + /** + * More detail for this item, e.g. the signature of a function. + */ + detail?: string; + /** + * The resource identifier of this item. + */ + uri: DocumentUri; + /** + * The range enclosing this symbol not including leading/trailing whitespace + * but everything else, e.g. comments and code. + */ + range: Range; + /** + * The range that should be selected and revealed when this symbol is being + * picked, e.g. the name of a function. Must be contained by the + * [`range`](#TypeHierarchyItem.range). + */ + selectionRange: Range; + /** + * A data entry field that is preserved between a type hierarchy prepare and + * supertypes or subtypes requests. It could also be used to identify the + * type hierarchy in the server, helping improve the performance on + * resolving supertypes and subtypes. + */ + data?: unknown; +} + export const EOL: string[] = ['\n', '\r\n', '\r']; /**