Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type hierarchy support #779

Merged
merged 3 commits into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion client-node-tests/src/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ suite('Client integration', () => {
identifier: 'da348dc5-c30a-4515-9d98-31ff3be38d14',
interFileDependencies: true,
workspaceDiagnostics: true
}
},
typeHierarchyProvider: true
},
customResults: {
'hello': 'world'
Expand Down Expand Up @@ -1121,6 +1122,46 @@ suite('Client integration', () => {
assert.strictEqual(middlewareCalled, true);
});

test('Type Hierarchy', async () => {
const provider = client.getFeature(lsclient.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'),
Expand Down
2 changes: 1 addition & 1 deletion client-node-tests/src/runTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function go() {
* Basic usage
*/
await runTests({
version: '1.58.0',
version: '1.59.0',
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [
Expand Down
27 changes: 26 additions & 1 deletion client-node-tests/src/servers/testServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<null, null, never, any, any>('testing/sendSampleProgress'),
async (_, __) => {
Expand Down
12 changes: 9 additions & 3 deletions client/src/common/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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, TypeHierarchyPrepareRequest
} from 'vscode-languageserver-protocol';

import { toJSONObject } from './configuration';
Expand All @@ -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';
Expand Down Expand Up @@ -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[];
Expand Down Expand Up @@ -3265,6 +3266,10 @@ export abstract class BaseLanguageClient {

private doInitialize(connection: Connection, initParams: InitializeParams): Promise<InitializeResult> {
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;
Expand Down Expand Up @@ -3622,6 +3627,7 @@ export abstract class BaseLanguageClient {
public getFeature(request: typeof CallHierarchyPrepareRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CallHierarchyProvider>;
public getFeature(request: typeof SemanticTokensRegistrationType.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SemanticTokensProviders>;
public getFeature(request: typeof LinkedEditingRangeRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<LinkedEditingRangeProvider>;
public getFeature(request: typeof TypeHierarchyPrepareRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<VTypeHierarchyProvider>;
public getFeature(request: typeof Proposed.DocumentDiagnosticRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DiagnosticFeatureProvider>;

public getFeature(request: typeof WorkspaceSymbolRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<WorkspaceSymbolProvider>;
Expand Down
22 changes: 21 additions & 1 deletion client/src/common/codeConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -841,6 +860,7 @@ export function createConverter(uriConverter?: URIConverter): Converter {
asCodeLensParams,
asDocumentLink,
asDocumentLinkParams,
asCallHierarchyItem
asCallHierarchyItem,
asTypeHierarchyItem
};
}
6 changes: 4 additions & 2 deletions client/src/common/commonClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>)[] {
let result: (StaticFeature | DynamicFeature<any>)[] = [
new pd.DiagnosticFeature(_client)
new pd.DiagnosticFeature(_client),
new pt.TypeHierarchyFeature(_client)
];
return result;
}
}
}
139 changes: 139 additions & 0 deletions client/src/common/proposed.typeHierarchy.ts
Original file line number Diff line number Diff line change
@@ -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, TypeHierarchyClientCapabilities, TypeHierarchyOptions, TypeHierarchyPrepareRequest, TypeHierarchyRegistrationOptions, TypeHierarchySubtypesParams, TypeHierarchySubtypesRequest, TypeHierarchySupertypesParams, TypeHierarchySupertypesRequest } from 'vscode-languageserver-protocol';

import { TextDocumentFeature, BaseLanguageClient, Middleware } from './client';

function ensure<T, K extends keyof T>(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<VTypeHierarchyItem[]>;
}

export interface TypeHierarchySupertypesSignature {
(this: void, item: VTypeHierarchyItem, token: CancellationToken): ProviderResult<VTypeHierarchyItem[]>;
}

export interface TypeHierarchySubtypesSignature {
(this: void, item: VTypeHierarchyItem, token: CancellationToken): ProviderResult<VTypeHierarchyItem[]>;
}

/**
* Type hierarchy middleware
*
* @since 3.17.0 - proposed state
*/
export interface TypeHierarchyMiddleware {
prepareTypeHierarchy?: (this: void, document: TextDocument, positions: VPosition, token: CancellationToken, next: PrepareTypeHierarchySignature) => ProviderResult<VTypeHierarchyItem[]>;
provideTypeHierarchySupertypes?: (this: void, item: VTypeHierarchyItem, token: CancellationToken, next: TypeHierarchySupertypesSignature) => ProviderResult<VTypeHierarchyItem[]>;
provideTypeHierarchySubtypes?: (this: void, item: VTypeHierarchyItem, token: CancellationToken, next: TypeHierarchySubtypesSignature) => ProviderResult<VTypeHierarchyItem[]>;
}

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<VTypeHierarchyItem[]> {
const client = this.client;
const middleware = this.middleware;
const prepareTypeHierarchy: PrepareTypeHierarchySignature = (document, position, token) => {
const params = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
return client.sendRequest(TypeHierarchyPrepareRequest.type, params, token).then(
(result) => {
return client.protocol2CodeConverter.asTypeHierarchyItems(result);
},
(error) => {
return client.handleFailedRequest(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<VTypeHierarchyItem[]> {
const client = this.client;
const middleware = this.middleware;
const provideTypeHierarchySupertypes: TypeHierarchySupertypesSignature = (item, token) => {
const params: TypeHierarchySupertypesParams = {
item: client.code2ProtocolConverter.asTypeHierarchyItem(item)
};
return client.sendRequest(TypeHierarchySupertypesRequest.type, params, token).then(
(result) => {
return client.protocol2CodeConverter.asTypeHierarchyItems(result);
},
(error) => {
return client.handleFailedRequest(TypeHierarchySupertypesRequest.type, token, error, null);
}
);
};
return middleware.provideTypeHierarchySupertypes
? middleware.provideTypeHierarchySupertypes(item, token, provideTypeHierarchySupertypes)
: provideTypeHierarchySupertypes(item, token);
}

public provideTypeHierarchySubtypes(item: VTypeHierarchyItem, token: CancellationToken): ProviderResult<VTypeHierarchyItem[]> {
const client = this.client;
const middleware = this.middleware;
const provideTypeHierarchySubtypes: TypeHierarchySubtypesSignature = (item, token) => {
const params: TypeHierarchySubtypesParams = {
item: client.code2ProtocolConverter.asTypeHierarchyItem(item)
};
return client.sendRequest(TypeHierarchySubtypesRequest.type, params, token).then(
(result) => {
return client.protocol2CodeConverter.asTypeHierarchyItems(result);
},
(error) => {
return client.handleFailedRequest(TypeHierarchySubtypesRequest.type, token, error, null);
}
);
};
return middleware.provideTypeHierarchySubtypes
? middleware.provideTypeHierarchySubtypes(item, token, provideTypeHierarchySubtypes)
: provideTypeHierarchySubtypes(item, token);
}
}

export class TypeHierarchyFeature extends TextDocumentFeature<boolean | TypeHierarchyOptions, TypeHierarchyRegistrationOptions, TypeHierarchyProvider> {
constructor(client: BaseLanguageClient) {
super(client, TypeHierarchyPrepareRequest.type);
}

public fillClientCapabilities(cap: ClientCapabilities): void {
const capabilities: ClientCapabilities & TypeHierarchyClientCapabilities = cap as ClientCapabilities & 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: TypeHierarchyRegistrationOptions): [Disposable, TypeHierarchyProvider] {
const client = this._client;
const provider = new TypeHierarchyProvider(client);
return [Languages.registerTypeHierarchyProvider(options.documentSelector!, provider), provider];
}
}
Loading