diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 6ebd6b365561c..94719d85e57fa 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -2914,4 +2914,95 @@ declare module 'vscode' { export type DetailedCoverage = StatementCoverage | FunctionCoverage; //#endregion + + + //#region https://github.com/microsoft/vscode/issues/15533 --- Type hierarchy --- @eskibear + 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 function. Must be contained by the + * [`range`](#TypeHierarchyItem.range). + */ + selectionRange: Range; + + constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range); + } + + 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 + } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index bd011ed66c3ef..45ee8e795fb7e 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -20,6 +20,7 @@ import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { mixin } from 'vs/base/common/objects'; import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; @@ -147,6 +148,13 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return data as callh.CallHierarchyItem; } + private static _reviveTypeHierarchyItemDto(data: ITypeHierarchyItemDto | undefined): typeh.TypeHierarchyItem { + if (data) { + data.uri = URI.revive(data.uri); + } + return data as typeh.TypeHierarchyItem; + } + //#endregion // --- outline @@ -773,6 +781,43 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } + // --- type hierarchy + + $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void { + this._registrations.set(handle, typeh.TypeHierarchyProviderRegistry.register(selector, { + + prepareTypeHierarchy: async (document, position, token) => { + const items = await this._proxy.$prepareTypeHierarchy(handle, document.uri, position, token); + if (!items) { + return undefined; + } + return { + dispose: () => { + for (const item of items) { + this._proxy.$releaseTypeHierarchy(handle, item._sessionId); + } + }, + roots: items.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto) + }; + }, + + provideSupertypes: async (item, token) => { + const supertypes = await this._proxy.$provideTypeHierarchySupertypes(handle, item._sessionId, item._itemId, token); + if (!supertypes) { + return supertypes; + } + return supertypes.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto); + }, + provideSubtypes: async (item, token) => { + const subtypes = await this._proxy.$provideTypeHierarchySubtypes(handle, item._sessionId, item._itemId, token); + if (!subtypes) { + return subtypes; + } + return subtypes.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto); + } + })); + } + } export class MainThreadDocumentSemanticTokensProvider implements modes.DocumentSemanticTokensProvider { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index dd7808fb806e1..62b83684bc671 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -499,6 +499,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerInlayHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider): vscode.Disposable { checkProposedApiEnabled(extension); return extHostLanguageFeatures.registerInlayHintsProvider(extension, selector, provider); + }, + registerTypeHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.TypeHierarchyProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerTypeHierarchyProvider(extension, selector, provider); } }; @@ -1232,6 +1236,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ThemeIcon: extHostTypes.ThemeIcon, TreeItem: extHostTypes.TreeItem, TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState, + TypeHierarchyItem: extHostTypes.TypeHierarchyItem, UIKind: UIKind, Uri: URI, ViewColumn: extHostTypes.ViewColumn, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ebd88795f41f3..aed2302bcab90 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -59,6 +59,7 @@ import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/que import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { ExtensionRunTestsRequest, ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, RunTestForControllerRequest, RunTestsRequest, ITestIdWithSrc, TestsDiff, IFileCoverage, CoverageDetails } from 'vs/workbench/contrib/testing/common/testCollection'; import { InternalTimelineOptions, Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; +import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ActivationKind, ExtensionHostKind, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -413,6 +414,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $emitFoldingRangeEvent(eventHandle: number, event?: any): void; $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; } @@ -1627,6 +1629,8 @@ export interface IInlineValueContextDto { stoppedLocation: IRange; } +export type ITypeHierarchyItemDto = Dto; + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1677,6 +1681,10 @@ export interface ExtHostLanguageFeaturesShape { $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseCallHierarchy(handle: number, sessionId: string): void; $setWordDefinitions(wordDefinitions: ILanguageWordDefinitionDto[]): void; + $prepareTypeHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $releaseTypeHierarchy(handle: number, sessionId: string): void; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 6aa039d2b3ca2..30e14491adebb 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import type * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto, ITypeHierarchyItemDto } from 'vs/workbench/api/common/extHost.protocol'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -406,6 +406,22 @@ const newCommands: ApiCommand[] = [ ], ApiCommandResult.Void ), + // --- type hierarchy + new ApiCommand( + 'vscode.prepareTypeHierarchy', '_executePrepareTypeHierarchy', 'Prepare type hierarchy at a position inside a document', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult('A TypeHierarchyItem or undefined', v => v.map(typeConverters.TypeHierarchyItem.to)) + ), + new ApiCommand( + 'vscode.provideSupertypes', '_executeProvideSupertypes', 'Compute supertypes for an item', + [ApiCommandArgument.TypeHierarchyItem], + new ApiCommandResult('A TypeHierarchyItem or undefined', v => v.map(typeConverters.TypeHierarchyItem.to)) + ), + new ApiCommand( + 'vscode.provideSubtypes', '_executeProvideSubtypes', 'Compute subtypes for an item', + [ApiCommandArgument.TypeHierarchyItem], + new ApiCommandResult('A TypeHierarchyItem or undefined', v => v.map(typeConverters.TypeHierarchyItem.to)) + ), ]; //#endregion diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 14cb489982407..03bfe95e1948f 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -370,6 +370,7 @@ export class ApiCommandArgument { static readonly String = new ApiCommandArgument('string', '', v => typeof v === 'string', v => v); static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.to); + static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.to); constructor( readonly name: string, diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index d94cfc164de15..8b41c25c6f509 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1429,12 +1429,96 @@ class CallHierarchyAdapter { } } +class TypeHierarchyAdapter { + + private readonly _idPool = new IdGenerator(''); + private readonly _cache = new Map>(); + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.TypeHierarchyProvider + ) { } + + async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { + const doc = this._documents.getDocument(uri); + const pos = typeConvert.Position.to(position); + + const items = await this._provider.prepareTypeHierarchy(doc, pos, token); + if (!items) { + return undefined; + } + + const sessionId = this._idPool.nextId(); + this._cache.set(sessionId, new Map()); + + if (Array.isArray(items)) { + return items.map(item => this._cacheAndConvertItem(sessionId, item)); + } else { + return [this._cacheAndConvertItem(sessionId, items)]; + } + } + + async provideSupertypes(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing type hierarchy item'); + } + const supertypes = await this._provider.provideTypeHierarchySupertypes(item, token); + if (!supertypes) { + return undefined; + } + return supertypes.map(supertype => { + return this._cacheAndConvertItem(sessionId, supertype); + }); + } + + async provideSubtypes(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing type hierarchy item'); + } + const subtypes = await this._provider.provideTypeHierarchySubtypes(item, token); + if (!subtypes) { + return undefined; + } + return subtypes.map(subtype => { + return this._cacheAndConvertItem(sessionId, subtype); + }); + } + + releaseSession(sessionId: string): void { + this._cache.delete(sessionId); + } + + private _cacheAndConvertItem(sessionId: string, item: vscode.TypeHierarchyItem): extHostProtocol.ITypeHierarchyItemDto { + const map = this._cache.get(sessionId)!; + const dto: extHostProtocol.ICallHierarchyItemDto = { + _sessionId: sessionId, + _itemId: map.size.toString(36), + name: item.name, + detail: item.detail, + kind: typeConvert.SymbolKind.from(item.kind), + uri: item.uri, + range: typeConvert.Range.from(item.range), + selectionRange: typeConvert.Range.from(item.selectionRange), + tags: item.tags?.map(typeConvert.SymbolTag.from) + }; + map.set(dto._itemId, item); + return dto; + } + + private _itemFromCache(sessionId: string, itemId: string): vscode.TypeHierarchyItem | undefined { + const map = this._cache.get(sessionId); + return map?.get(itemId); + } +} type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter - | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter + | SelectionRangeAdapter | CallHierarchyAdapter | TypeHierarchyAdapter + | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter; @@ -2043,6 +2127,29 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined); } + // --- type hierarchy + registerTypeHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.TypeHierarchyProvider): vscode.Disposable { + const handle = this._addNewAdapter(new TypeHierarchyAdapter(this._documents, provider), extension); + this._proxy.$registerTypeHierarchyProvider(handle, this._transformDocumentSelector(selector)); + return this._createDisposable(handle); + } + + $prepareTypeHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined); + } + + $provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, TypeHierarchyAdapter, adapter => adapter.provideSupertypes(sessionId, itemId, token), undefined); + } + + $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, TypeHierarchyAdapter, adapter => adapter.provideSubtypes(sessionId, itemId, token), undefined); + } + + $releaseTypeHierarchy(handle: number, sessionId: string): void { + this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined); + } + // --- configuration private static _serializeRegExp(regExp: RegExp): extHostProtocol.IRegExpDto { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 52cf144bb2e1f..cf445654cc9e2 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1780,3 +1780,22 @@ export namespace CodeActionTriggerKind { } } } + +export namespace TypeHierarchyItem { + + export function to(item: extHostProtocol.ITypeHierarchyItemDto): types.TypeHierarchyItem { + const result = new types.TypeHierarchyItem( + SymbolKind.to(item.kind), + item.name, + item.detail || '', + URI.revive(item.uri), + Range.to(item.range), + Range.to(item.selectionRange) + ); + + result._sessionId = item._sessionId; + result._itemId = item._itemId; + + return result; + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2cc35df7c9882..4a399979fe542 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3539,3 +3539,24 @@ export enum PortAutoForwardAction { Ignore = 5, OpenBrowserOnce = 6 } + +export class TypeHierarchyItem { + _sessionId?: string; + _itemId?: string; + + kind: SymbolKind; + name: string; + detail?: string; + uri: URI; + range: Range; + selectionRange: Range; + + constructor(kind: SymbolKind, name: string, detail: string, uri: URI, range: Range, selectionRange: Range) { + this.kind = kind; + this.name = name; + this.detail = detail; + this.uri = uri; + this.range = range; + this.selectionRange = selectionRange; + } +} diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts new file mode 100644 index 0000000000000..574e020912d12 --- /dev/null +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.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. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { Event } from 'vs/base/common/event'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { localize } from 'vs/nls'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { TypeHierarchyProviderRegistry } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; + + +const _ctxHasTypeHierarchyProvider = new RawContextKey('editorHasTypeHierarchyProvider', false, localize('editorHasTypeHierarchyProvider', 'Whether a type hierarchy provider is available')); + +class TypeHierarchyController implements IEditorContribution { + static readonly Id = 'typeHierarchy'; + + static get(editor: ICodeEditor): TypeHierarchyController { + return editor.getContribution(TypeHierarchyController.Id); + } + + private readonly _ctxHasProvider: IContextKey; + private readonly _dispoables = new DisposableStore(); + private readonly _sessionDisposables = new DisposableStore(); + + constructor( + readonly _editor: ICodeEditor, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + ) { + this._ctxHasProvider = _ctxHasTypeHierarchyProvider.bindTo(this._contextKeyService); + this._dispoables.add(Event.any(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, TypeHierarchyProviderRegistry.onDidChange)(() => { + this._ctxHasProvider.set(_editor.hasModel() && TypeHierarchyProviderRegistry.has(_editor.getModel())); + })); + this._dispoables.add(this._sessionDisposables); + } + + dispose(): void { + this._dispoables.dispose(); + } +} + +registerEditorContribution(TypeHierarchyController.Id, TypeHierarchyController); + +// Testing + diff --git a/src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts b/src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts new file mode 100644 index 0000000000000..c11916916a41d --- /dev/null +++ b/src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts @@ -0,0 +1,205 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRange, Range } from 'vs/editor/common/core/range'; +import { SymbolKind, ProviderResult, SymbolTag } from 'vs/editor/common/modes'; +import { ITextModel } from 'vs/editor/common/model'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; +import { URI } from 'vs/base/common/uri'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; + + + +export interface TypeHierarchyItem { + _sessionId: string; + _itemId: string; + name: string; + kind: SymbolKind; + detail?: string; + uri: URI; + range: IRange; + selectionRange: IRange; + tags?: SymbolTag[] +} + +export interface TypeHierarchySession { + roots: TypeHierarchyItem[]; + dispose(): void; +} + +export interface TypeHierarchyProvider { + prepareTypeHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult; + provideSupertypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; + provideSubtypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult; +} + +export const TypeHierarchyProviderRegistry = new LanguageFeatureRegistry(); + +class RefCountedDisposabled { + + constructor( + private readonly _disposable: IDisposable, + private _counter = 1 + ) { } + + acquire() { + this._counter++; + return this; + } + + release() { + if (--this._counter === 0) { + this._disposable.dispose(); + } + return this; + } +} + +export class TypeHierarchyModel { + + static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise { + const [provider] = TypeHierarchyProviderRegistry.ordered(model); + if (!provider) { + return undefined; + } + const session = await provider.prepareTypeHierarchy(model, position, token); + if (!session) { + return undefined; + } + return new TypeHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposabled(session)); + } + + readonly root: TypeHierarchyItem; + + private constructor( + readonly id: string, + readonly provider: TypeHierarchyProvider, + readonly roots: TypeHierarchyItem[], + readonly ref: RefCountedDisposabled, + ) { + this.root = roots[0]; + } + + dispose(): void { + this.ref.release(); + } + + fork(item: TypeHierarchyItem): TypeHierarchyModel { + const that = this; + return new class extends TypeHierarchyModel { + constructor() { + super(that.id, that.provider, [item], that.ref.acquire()); + } + }; + } + + async provideSupertypes(item: TypeHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideSupertypes(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); + } + return []; + } + + async provideSubtypes(item: TypeHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideSubtypes(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); + } + return []; + } +} + +// --- API command support + +const _models = new Map(); + +CommandsRegistry.registerCommand('_executePrepareTypeHierarchy', async (accessor, ...args) => { + const [resource, position] = args; + assertType(URI.isUri(resource)); + assertType(Position.isIPosition(position)); + + const modelService = accessor.get(IModelService); + let textModel = modelService.getModel(resource); + let textModelReference: IDisposable | undefined; + if (!textModel) { + const textModelService = accessor.get(ITextModelService); + const result = await textModelService.createModelReference(resource); + textModel = result.object.textEditorModel; + textModelReference = result; + } + + try { + const model = await TypeHierarchyModel.create(textModel, position, CancellationToken.None); + if (!model) { + return []; + } + + _models.set(model.id, model); + _models.forEach((value, key, map) => { + if (map.size > 10) { + value.dispose(); + _models.delete(key); + } + }); + return [model.root]; + + } finally { + textModelReference?.dispose(); + } +}); + +function isTypeHierarchyItemDto(obj: any): obj is TypeHierarchyItem { + const item = obj as TypeHierarchyItem; + return typeof obj === 'object' + && typeof item.name === 'string' + && typeof item.kind === 'number' + && URI.isUri(item.uri) + && Range.isIRange(item.range) + && Range.isIRange(item.selectionRange); +} + +CommandsRegistry.registerCommand('_executeProvideSupertypes', async (_accessor, ...args) => { + const [item] = args; + assertType(isTypeHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.provideSupertypes(item, CancellationToken.None); +}); + +CommandsRegistry.registerCommand('_executeProvideSubtypes', async (_accessor, ...args) => { + const [item] = args; + assertType(isTypeHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.provideSubtypes(item, CancellationToken.None); +}); + diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 73a268b2b40a5..74a48a35472eb 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -292,6 +292,9 @@ import 'vs/workbench/contrib/welcome/common/newFile.contribution'; // Call Hierarchy import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; +// Type Hierarchy +import 'vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution'; + // Outline import 'vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline'; import 'vs/workbench/contrib/outline/browser/outline.contribution';