diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 424fd364c57..ac557db625e 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -42,6 +42,10 @@ "name": "vs/workbench/contrib/codeinset", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/callHierarchy", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 44acade03b1..d05200c18bd 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -13,6 +13,7 @@ "./vs/workbench/common/**/*", "./vs/workbench/browser/**/*", "./vs/workbench/electron-browser/**/*", + "./vs/workbench/contrib/callHierarchy/**/*", "./vs/workbench/contrib/emmet/**/*", "./vs/workbench/contrib/externalTerminal/**/*", "./vs/workbench/contrib/scm/**/*.ts", @@ -460,4 +461,4 @@ "./typings/require-monaco.d.ts", "./vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts" ] -} \ No newline at end of file +} diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b57455215b5..2a845ca6e68 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,6 +16,45 @@ declare module 'vscode' { + //#region Joh - call hierarchy + + export enum CallHierarchyDirection { + CallsFrom = 1, + CallsTo = 2, + } + + export class CallHierarchyItem { + kind: SymbolKind; + name: string; + detail?: string; + range: Range; + selectionRange: Range; + + constructor(kind: SymbolKind, name: string, detail: string, range: Range, selectionRange: Range); + } + + export interface CallHierarchyItemProvider { + + provideCallHierarchyItem( + document: TextDocument, + postion: Position, + token: CancellationToken + ): ProviderResult; + + resolveCallHierarchyItem( + item: CallHierarchyItem, + direction: CallHierarchyDirection, + token: CancellationToken + ): ProviderResult<[CallHierarchyItem, Location[]][]>; + } + + export namespace languages { + export function registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyItemProvider): Disposable; + } + + //#endregion + + //#region Alex - resolvers export class ResolvedAuthority { diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index d735e82055c..bf0ca33f5ba 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -22,6 +22,7 @@ import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { CallHierarchyProviderRegistry } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -471,6 +472,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }); } + // --- call hierarchy + + $registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + this._registrations[handle] = CallHierarchyProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { + provideCallHierarchyItem: (document, position, token) => { + return this._proxy.$provideCallHierarchyItem(handle, document.uri, position, token); + }, + resolveCallHierarchyItem: (item, direction, token) => { + return this._proxy.$resolveCallHierarchyItem(handle, item, direction, token); + } + }); + } + // --- configuration private static _reviveRegExp(regExp: ISerializedRegExp): RegExp { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 661f5cf5e86..ed31eb3fed8 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -357,6 +357,10 @@ export function createApiFactory( registerSelectionRangeProvider(selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable { return extHostLanguageFeatures.registerSelectionRangeProvider(extension, selector, provider); }, + registerCallHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.CallHierarchyItemProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerCallHierarchyProvider(extension, selector, provider); + }, setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => { return extHostLanguageFeatures.setLanguageConfiguration(language, configuration); } @@ -831,7 +835,9 @@ export function createApiFactory( Uri: URI, ViewColumn: extHostTypes.ViewColumn, WorkspaceEdit: extHostTypes.WorkspaceEdit, - // functions + // proposed + CallHierarchyDirection: extHostTypes.CallHierarchyDirection, + CallHierarchyItem: extHostTypes.CallHierarchyItem }; }; } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 73b71bbc084..119d5239ec4 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -46,6 +46,7 @@ import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityReso import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IRemoteConsoleLog } from 'vs/base/node/console'; import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; +import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -335,6 +336,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; + $registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $setLanguageConfiguration(handle: number, languageId: string, configuration: ISerializedLanguageConfiguration): void; } @@ -951,6 +953,8 @@ export interface ExtHostLanguageFeaturesShape { $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise; + $provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[callHierarchy.CallHierarchyItem, modes.Location[]][]>; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 19cf2adb014..d4e784e37df 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -29,6 +29,8 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtHostWebview } from 'vs/workbench/api/node/extHostWebview'; import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import { generateUuid } from 'vs/base/common/uuid'; +import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { LRUCache } from 'vs/base/common/map'; // --- adapter @@ -964,11 +966,65 @@ class SelectionRangeAdapter { } } +class CallHierarchyAdapter { + + // todo@joh keep object (heap service, lifecycle) + private readonly _cache = new LRUCache(1000, 0.8); + private _idPool = 0; + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.CallHierarchyItemProvider + ) { } + + provideCallHierarchyItem(resource: URI, pos: IPosition, token: CancellationToken): Promise { + const document = this._documents.getDocument(resource); + const position = typeConvert.Position.to(pos); + + return asPromise(() => this._provider.provideCallHierarchyItem(document, position, token)).then(item => { + if (!item) { + return undefined; + } + return this._fromItem(item); + }); + } + + resolveCallHierarchyItem(item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[callHierarchy.CallHierarchyItem, modes.Location[]][]> { + return asPromise(() => this._provider.resolveCallHierarchyItem( + this._cache.get(item._id), + direction as number, token) // todo@joh proper convert + ).then(data => { + if (!data) { + return []; + } + return data.map(tuple => { + return <[callHierarchy.CallHierarchyItem, modes.Location[]]>[ + this._fromItem(tuple[0]), + tuple[1].map(typeConvert.location.from) + ]; + }); + }); + } + + private _fromItem(item: vscode.CallHierarchyItem, _id: number = this._idPool++): callHierarchy.CallHierarchyItem { + const res = { + _id, + name: item.name, + detail: item.detail, + kind: typeConvert.SymbolKind.from(item.kind), + range: typeConvert.Range.from(item.range), + selectionRange: typeConvert.Range.from(item.selectionRange), + }; + this._cache.set(_id, item); + return res; + } +} + type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter - | ColorProviderAdapter | FoldingProviderAdapter | CodeInsetAdapter | DeclarationAdapter | SelectionRangeAdapter; + | ColorProviderAdapter | FoldingProviderAdapter | CodeInsetAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; class AdapterData { constructor( @@ -1416,6 +1472,22 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, SelectionRangeAdapter, adapter => adapter.provideSelectionRanges(URI.revive(resource), positions, token)); } + // --- call hierarchy + + registerCallHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CallHierarchyItemProvider): vscode.Disposable { + const handle = this._addNewAdapter(new CallHierarchyAdapter(this._documents, provider), extension); + this._proxy.$registerCallHierarchyProvider(handle, this._transformDocumentSelector(selector)); + return this._createDisposable(handle); + } + + $provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallHierarchyItem(URI.revive(resource), position, token)); + } + + $resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[callHierarchy.CallHierarchyItem, modes.Location[]][]> { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.resolveCallHierarchyItem(item, direction, token)); + } + // --- configuration private static _serializeRegExp(regExp: RegExp): ISerializedRegExp { diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index ebc2cf411c9..078f2c16699 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -883,7 +883,6 @@ export namespace TextDocumentSaveReason { } } - export namespace EndOfLine { export function from(eol: vscode.EndOfLine): EndOfLineSequence | undefined { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 21fb78b59f8..9a694d6638c 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1111,6 +1111,27 @@ export class SelectionRange { } +export enum CallHierarchyDirection { + CallsFrom = 1, + CallsTo = 2, +} + +export class CallHierarchyItem { + kind: SymbolKind; + name: string; + detail?: string; + range: Range; + selectionRange: Range; + + constructor(kind: SymbolKind, name: string, detail: string | undefined = undefined, range: Range, selectionRange: Range) { + this.kind = kind; + this.name = name; + this.detail = detail; + this.range = range; + this.selectionRange = selectionRange; + } +} + @es5ClassCompat export class CodeLens { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts new file mode 100644 index 00000000000..d1110eef7d7 --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerAction, MenuId } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { CallHierarchyProviderRegistry, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +registerAction({ + id: 'editor.showCallHierarchy', + title: { + value: localize('title', "Show Call Hierarchy"), + original: 'Show Call Hierarchy' + }, + menu: { + menuId: MenuId.CommandPalette + }, + handler: async function (accessor) { + const editor = accessor.get(ICodeEditorService).getActiveCodeEditor(); + + if (!editor || !editor.hasModel()) { + console.log('bad editor'); + return; + } + + const [provider] = CallHierarchyProviderRegistry.ordered(editor.getModel()); + if (!provider) { + console.log('no provider'); + return; + } + + const data = await provider.provideCallHierarchyItem(editor.getModel(), editor.getPosition(), CancellationToken.None); + if (!data) { + console.log('no data'); + return; + } + + const callsTo = await provider.resolveCallHierarchyItem(data, CallHierarchyDirection.CallsTo, CancellationToken.None); + console.log(data); + console.log(callsTo); + } +}); diff --git a/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts new file mode 100644 index 00000000000..cd7816bd146 --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IPosition } from 'vs/editor/common/core/position'; +import { IRange } from 'vs/editor/common/core/range'; +import { SymbolKind, ProviderResult, Location } 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'; + +export const enum CallHierarchyDirection { + CallsFrom = 1, + CallsTo = 2 +} + +export interface CallHierarchyItem { + _id: number; + kind: SymbolKind; + name: string; + detail?: string; + range: IRange; + selectionRange: IRange; +} + +export interface CallHierarchyProvider { + + provideCallHierarchyItem( + document: ITextModel, + postion: IPosition, + token: CancellationToken + ): ProviderResult; + + resolveCallHierarchyItem( + item: CallHierarchyItem, + direction: CallHierarchyDirection, + token: CancellationToken + ): ProviderResult<[CallHierarchyItem, Location[]][]>; +} + +export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry(); + diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index e64a09824fa..4ab6635add5 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -305,6 +305,9 @@ import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStar import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; +// Call Hierarchy +import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; + // Outline import 'vs/workbench/contrib/outline/browser/outline.contribution';