Merge pull request #70748 from Microsoft/joh/callh

Implement call hierarchy
This commit is contained in:
Johannes Rieken 2019-03-19 13:59:31 +01:00 committed by GitHub
commit 63ce59c390
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 983 additions and 8 deletions

View file

@ -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"

View file

@ -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/extensions/**/*",
"./vs/workbench/contrib/externalTerminal/**/*",
@ -391,4 +392,4 @@
"./typings/require-monaco.d.ts",
"./vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts"
]
}
}

View file

@ -11,6 +11,7 @@ import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { DocumentSymbol, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
export function getDocumentSymbols(model: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
@ -70,8 +71,20 @@ registerLanguageCommand('_executeDocumentSymbolProvider', function (accessor, ar
throw illegalArgument('resource');
}
const model = accessor.get(IModelService).getModel(resource);
if (!model) {
throw illegalArgument('resource');
if (model) {
return getDocumentSymbols(model, false, CancellationToken.None);
}
return getDocumentSymbols(model, false, CancellationToken.None);
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
return new Promise((resolve, reject) => {
try {
const result = getDocumentSymbols(reference.object.textEditorModel, false, CancellationToken.None);
resolve(result);
} catch (err) {
reject(err);
}
}).finally(() => {
reference.dispose();
});
});
});

View file

@ -16,6 +16,46 @@
declare module 'vscode' {
//#region Joh - call hierarchy
export enum CallHierarchyDirection {
CallsFrom = 1,
CallsTo = 2,
}
export class CallHierarchyItem {
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);
}
export interface CallHierarchyItemProvider {
provideCallHierarchyItem(
document: TextDocument,
postion: Position,
token: CancellationToken
): ProviderResult<CallHierarchyItem>;
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 {

View file

@ -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 } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, CodeInsetDto, LinkDto } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, CodeInsetDto, LinkDto, CallHierarchyDto } from '../node/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { IHeapService } from './mainThreadHeapService';
@ -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 * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
@ -114,6 +115,13 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
return <modes.ILink>data;
}
private static _reviveCallHierarchyItemDto(data: CallHierarchyDto | undefined): callh.CallHierarchyItem {
if (data) {
data.uri = URI.revive(data.uri);
}
return data as callh.CallHierarchyItem;
}
//#endregion
// --- outline
@ -471,6 +479,30 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
});
}
// --- call hierarchy
$registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void {
this._registrations[handle] = callh.CallHierarchyProviderRegistry.register(typeConverters.LanguageSelector.from(selector), {
provideCallHierarchyItem: (document, position, token) => {
return this._proxy.$provideCallHierarchyItem(handle, document.uri, position, token).then(MainThreadLanguageFeatures._reviveCallHierarchyItemDto);
},
resolveCallHierarchyItem: (item, direction, token) => {
return this._proxy.$resolveCallHierarchyItem(handle, item, direction, token).then(data => {
if (data) {
for (let i = 0; i < data.length; i++) {
const [item, locations] = data[i];
data[i] = [
MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item),
MainThreadLanguageFeatures._reviveLocationDto(locations)
];
}
}
return data as [callh.CallHierarchyItem, modes.Location[]][];
});
}
});
}
// --- configuration
private static _reviveRegExp(regExp: ISerializedRegExp): RegExp {

View file

@ -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
};
};
}

View file

@ -46,6 +46,7 @@ import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityReso
import { ExtensionIdentifier, IExtensionDescription } 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;
@ -337,6 +338,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;
}
@ -920,6 +922,16 @@ export interface CodeLensDto extends ObjectIdentifier {
export type CodeInsetDto = ObjectIdentifier & codeInset.ICodeInsetSymbol;
export interface CallHierarchyDto {
_id: number;
kind: modes.SymbolKind;
name: string;
detail?: string;
uri: UriComponents;
range: IRange;
selectionRange: IRange;
}
export interface ExtHostLanguageFeaturesShape {
$provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise<modes.DocumentSymbol[] | undefined>;
$provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise<CodeLensDto[]>;
@ -952,6 +964,8 @@ export interface ExtHostLanguageFeaturesShape {
$provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise<modes.IColorPresentation[] | undefined>;
$provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise<modes.FoldingRange[] | undefined>;
$provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise<modes.SelectionRange[][]>;
$provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<CallHierarchyDto | undefined>;
$resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[CallHierarchyDto, modes.Location[]][]>;
}
export interface ExtHostQuickOpenShape {

View file

@ -28,6 +28,8 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
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
@ -963,11 +965,66 @@ class SelectionRangeAdapter {
}
}
class CallHierarchyAdapter {
// todo@joh keep object (heap service, lifecycle)
private readonly _cache = new LRUCache<number, vscode.CallHierarchyItem>(1000, 0.8);
private _idPool = 0;
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.CallHierarchyItemProvider
) { }
provideCallHierarchyItem(resource: URI, pos: IPosition, token: CancellationToken): Promise<undefined | callHierarchy.CallHierarchyItem> {
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 = <callHierarchy.CallHierarchyItem>{
_id,
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),
};
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(
@ -1415,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<undefined | callHierarchy.CallHierarchyItem> {
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 {

View file

@ -883,7 +883,6 @@ export namespace TextDocumentSaveReason {
}
}
export namespace EndOfLine {
export function from(eol: vscode.EndOfLine): EndOfLineSequence | undefined {

View file

@ -1111,6 +1111,29 @@ export class SelectionRange {
}
export enum CallHierarchyDirection {
CallsFrom = 1,
CallsTo = 2,
}
export class CallHierarchyItem {
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;
}
}
@es5ClassCompat
export class CodeLens {

View file

@ -0,0 +1,160 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { CallHierarchyProviderRegistry, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { CallHierarchyTreePeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek';
import { Event } from 'vs/base/common/event';
import { registerEditorContribution, registerEditorAction, EditorAction, registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
const _ctxHasCompletionItemProvider = new RawContextKey<boolean>('editorHasCallHierarchyProvider', false);
const _ctxCallHierarchyVisible = new RawContextKey<boolean>('callHierarchyVisible', false);
class CallHierarchyController extends Disposable implements IEditorContribution {
static Id = 'callHierarchy';
static get(editor: ICodeEditor): CallHierarchyController {
return editor.getContribution<CallHierarchyController>(CallHierarchyController.Id);
}
private readonly _ctxHasProvider: IContextKey<boolean>;
private readonly _ctxIsVisible: IContextKey<boolean>;
private _sessionDispose: IDisposable[] = [];
constructor(
private readonly _editor: ICodeEditor,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super();
this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService);
this._ctxHasProvider = _ctxHasCompletionItemProvider.bindTo(this._contextKeyService);
this._register(Event.any<any>(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, CallHierarchyProviderRegistry.onDidChange)(() => {
this._ctxHasProvider.set(_editor.hasModel() && CallHierarchyProviderRegistry.has(_editor.getModel()));
}));
this._register({ dispose: () => dispose(this._sessionDispose) });
}
dispose(): void {
this._ctxHasProvider.reset();
this._ctxIsVisible.reset();
super.dispose();
}
getId(): string {
return CallHierarchyController.Id;
}
async startCallHierarchy(): Promise<void> {
this._sessionDispose = dispose(this._sessionDispose);
if (!this._editor.hasModel()) {
return;
}
const model = this._editor.getModel();
const position = this._editor.getPosition();
const [provider] = CallHierarchyProviderRegistry.ordered(model);
if (!provider) {
return;
}
Event.any<any>(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDispose);
const widget = this._instantiationService.createInstance(
CallHierarchyTreePeekWidget,
this._editor,
position,
provider,
CallHierarchyDirection.CallsTo
);
widget.showLoading();
this._ctxIsVisible.set(true);
const cancel = new CancellationTokenSource();
this._sessionDispose.push(widget.onDidClose(() => this.endCallHierarchy()));
this._sessionDispose.push({ dispose() { cancel.cancel(); } });
this._sessionDispose.push(widget);
Promise.resolve(provider.provideCallHierarchyItem(model, position, cancel.token)).then(item => {
if (cancel.token.isCancellationRequested) {
return;
}
if (!item) {
widget.showMessage(localize('no.item', "No results"));
return;
}
widget.showItem(item);
});
}
endCallHierarchy(): void {
this._sessionDispose = dispose(this._sessionDispose);
this._ctxIsVisible.set(false);
this._editor.focus();
}
}
registerEditorContribution(CallHierarchyController);
registerEditorAction(class extends EditorAction {
constructor() {
super({
id: 'editor.showCallHierarchy',
label: localize('title', "Call Hierarchy"),
alias: 'Call Hierarchy',
menuOpts: {
group: 'navigation',
order: 111
},
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H
},
precondition: _ctxHasCompletionItemProvider
});
}
async run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise<void> {
return CallHierarchyController.get(editor).startCallHierarchy();
}
});
registerEditorCommand(new class extends EditorCommand {
constructor() {
super({
id: 'editor.closeCallHierarchy',
kbOpts: {
weight: KeybindingWeight.WorkbenchContrib + 10,
primary: KeyCode.Escape
},
precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, ContextKeyExpr.not('config.editor.stablePeek'))
});
}
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void {
return CallHierarchyController.get(editor).endCallHierarchy();
}
});

View file

@ -0,0 +1,425 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/callHierarchy';
import { PeekViewWidget } from 'vs/editor/contrib/referenceSearch/peekViewWidget';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CallHierarchyItem, CallHierarchyProvider, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { FuzzyScore } from 'vs/base/common/filters';
import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree';
import { IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
import { localize } from 'vs/nls';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { IRange, Range } from 'vs/editor/common/core/range';
import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { Dimension, addClass } from 'vs/base/browser/dom';
import { Event } from 'vs/base/common/event';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model';
import { registerThemingParticipant, themeColorFromId, IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import * as referencesWidget from 'vs/editor/contrib/referenceSearch/referencesWidget';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IPosition } from 'vs/editor/common/core/position';
import { Action } from 'vs/base/common/actions';
import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ILabelService } from 'vs/platform/label/common/label';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Color } from 'vs/base/common/color';
const enum State {
Loading = 'loading',
Message = 'message',
Data = 'data'
}
class ToggleHierarchyDirectionAction extends Action {
constructor(public direction: () => CallHierarchyDirection, callback: () => void) {
super('toggle.dir', undefined, 'call-hierarchy-toggle', true, () => {
callback();
this._update();
return Promise.resolve();
});
this._update();
}
private _update() {
if (this.direction() === CallHierarchyDirection.CallsFrom) {
this.label = localize('toggle.from', "Calls From...");
this.checked = true;
} else {
this.label = localize('toggle.to', "Calls To...");
this.checked = false;
}
}
}
class LayoutInfo {
static store(info: LayoutInfo, storageService: IStorageService): void {
storageService.store('callHierarchyPeekLayout', JSON.stringify(info), StorageScope.GLOBAL);
}
static retrieve(storageService: IStorageService): LayoutInfo {
const value = storageService.get('callHierarchyPeekLayout', StorageScope.GLOBAL, '{}');
const defaultInfo: LayoutInfo = { ratio: 0.7, height: 17 };
try {
return { ...defaultInfo, ...JSON.parse(value) };
} catch {
return defaultInfo;
}
}
constructor(
public ratio: number,
public height: number
) { }
}
export class CallHierarchyTreePeekWidget extends PeekViewWidget {
private _toggleDirection: ToggleHierarchyDirectionAction;
private _parent: HTMLElement;
private _message: HTMLElement;
private _splitView: SplitView;
private _tree: WorkbenchAsyncDataTree<CallHierarchyItem, callHTree.Call, FuzzyScore>;
private _editor: EmbeddedCodeEditorWidget;
private _dim: Dimension;
private _layoutInfo: LayoutInfo;
constructor(
editor: ICodeEditor,
private readonly _where: IPosition,
private readonly _provider: CallHierarchyProvider,
private _direction: CallHierarchyDirection,
@IThemeService themeService: IThemeService,
@IEditorService private readonly _editorService: IEditorService,
@ITextModelService private readonly _textModelService: ITextModelService,
@ILabelService private readonly _labelService: ILabelService,
@IStorageService private readonly _storageService: IStorageService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super(editor, { showFrame: true, showArrow: true, isResizeable: true, isAccessible: true });
this.create();
this._applyTheme(themeService.getTheme());
themeService.onThemeChange(this._applyTheme, this, this._disposables);
}
dispose(): void {
LayoutInfo.store(this._layoutInfo, this._storageService);
this._splitView.dispose();
this._tree.dispose();
this._editor.dispose();
super.dispose();
}
private _applyTheme(theme: ITheme) {
const borderColor = theme.getColor(referencesWidget.peekViewBorder) || Color.transparent;
this.style({
arrowColor: borderColor,
frameColor: borderColor,
headerBackgroundColor: theme.getColor(referencesWidget.peekViewTitleBackground) || Color.transparent,
primaryHeadingColor: theme.getColor(referencesWidget.peekViewTitleForeground),
secondaryHeadingColor: theme.getColor(referencesWidget.peekViewTitleInfoForeground)
});
}
protected _getActionBarOptions(): IActionBarOptions {
return {
orientation: ActionsOrientation.HORIZONTAL_REVERSE
};
}
protected _fillBody(parent: HTMLElement): void {
this._layoutInfo = LayoutInfo.retrieve(this._storageService);
this._dim = { height: 0, width: 0 };
this._parent = parent;
addClass(parent, 'call-hierarchy');
const message = document.createElement('div');
addClass(message, 'message');
parent.appendChild(message);
this._message = message;
const container = document.createElement('div');
addClass(container, 'results');
parent.appendChild(container);
this._splitView = new SplitView(container, { orientation: Orientation.HORIZONTAL });
// editor stuff
const editorContainer = document.createElement('div');
addClass(editorContainer, 'editor');
container.appendChild(editorContainer);
let editorOptions: IEditorOptions = {
scrollBeyondLastLine: false,
scrollbar: {
verticalScrollbarSize: 14,
horizontal: 'auto',
useShadows: true,
verticalHasArrows: false,
horizontalHasArrows: false
},
overviewRulerLanes: 2,
fixedOverflowWidgets: true,
minimap: {
enabled: false
}
};
this._editor = this._instantiationService.createInstance(
EmbeddedCodeEditorWidget,
editorContainer,
editorOptions,
this.editor
);
// tree stuff
const treeContainer = document.createElement('div');
addClass(treeContainer, 'tree');
container.appendChild(treeContainer);
const options: IAsyncDataTreeOptions<callHTree.Call, FuzzyScore> = {
identityProvider: new callHTree.IdentityProvider(),
ariaLabel: localize('tree.aria', "Call Hierarchy"),
expandOnlyOnTwistieClick: true,
};
this._tree = <any>this._instantiationService.createInstance(
WorkbenchAsyncDataTree,
treeContainer,
new callHTree.VirtualDelegate(),
[this._instantiationService.createInstance(callHTree.CallRenderer)],
new callHTree.SingleDirectionDataSource(this._provider, () => this._direction),
options
);
// split stuff
this._splitView.addView({
onDidChange: Event.None,
element: editorContainer,
minimumSize: 200,
maximumSize: Number.MAX_VALUE,
layout: (width) => {
this._editor.layout({ height: this._dim.height, width });
}
}, Sizing.Distribute);
this._splitView.addView({
onDidChange: Event.None,
element: treeContainer,
minimumSize: 100,
maximumSize: Number.MAX_VALUE,
layout: (width) => {
this._tree.layout(this._dim.height, width);
}
}, Sizing.Distribute);
this._splitView.onDidSashChange(() => {
if (this._dim.width) {
this._layoutInfo.ratio = this._splitView.getViewSize(0) / this._dim.width;
}
}, undefined, this._disposables);
// session state
let localDispose: IDisposable[] = [];
this._disposables.push({ dispose() { dispose(localDispose); } });
// update editor
this._tree.onDidChangeFocus(e => {
const [element] = e.elements;
if (element && isNonEmptyArray(element.locations)) {
localDispose = dispose(localDispose);
const options: IModelDecorationOptions = {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'call-decoration',
overviewRuler: {
color: themeColorFromId(referencesWidget.peekViewEditorMatchHighlight),
position: OverviewRulerLane.Center
},
};
let decorations: IModelDeltaDecoration[] = [];
let fullRange: IRange | undefined;
for (const { range } of element.locations) {
decorations.push({ range, options });
fullRange = !fullRange ? range : Range.plusRange(range, fullRange);
}
this._textModelService.createModelReference(element.item.uri).then(value => {
this._editor.setModel(value.object.textEditorModel);
this._editor.revealRangeInCenter(fullRange!, ScrollType.Smooth);
this._editor.revealLine(element.item.range.startLineNumber, ScrollType.Smooth);
const ids = this._editor.deltaDecorations([], decorations);
localDispose.push({ dispose: () => this._editor.deltaDecorations(ids, []) });
localDispose.push(value);
});
}
}, undefined, this._disposables);
this._editor.onMouseDown(e => {
const { event, target } = e;
if (event.detail !== 2) {
return;
}
const [focus] = this._tree.getFocus();
if (!focus) {
return;
}
this.dispose();
this._editorService.openEditor({
resource: focus.item.uri,
options: { selection: target.range! }
});
}, undefined, this._disposables);
this._tree.onMouseDblClick(e => {
if (e.element && isNonEmptyArray(e.element.locations)) {
this.dispose();
this._editorService.openEditor({
resource: e.element.item.uri,
options: { selection: e.element.locations[0].range }
});
}
}, undefined, this._disposables);
this._tree.onDidChangeSelection(e => {
const [element] = e.elements;
// don't close on click
if (element && !(e.browserEvent instanceof MouseEvent)) {
this.dispose();
this._editorService.openEditor({
resource: element.item.uri,
options: { selection: element.locations[0].range }
});
}
});
}
showLoading(): void {
this._parent.dataset['state'] = State.Loading;
this.setTitle(localize('title.loading', "Loading..."));
this._show();
}
showMessage(message: string): void {
this._parent.dataset['state'] = State.Message;
this.setTitle('');
this.setMetaTitle('');
this._message.innerText = message;
this._show();
}
showItem(item: CallHierarchyItem) {
this._parent.dataset['state'] = State.Data;
this._show();
this._tree.setInput(item).then(() => {
if (!this._tree.getFirstElementChild(item)) {
//
this.showMessage(this._direction === CallHierarchyDirection.CallsFrom
? localize('empt.callsFrom', "No calls from '{0}'", item.name)
: localize('empt.callsTo', "No calls to '{0}'", item.name));
} else {
this._tree.domFocus();
this._tree.focusFirst();
this.setTitle(
item.name,
item.detail || this._labelService.getUriLabel(item.uri, { relative: true }),
);
this.setMetaTitle(this._direction === CallHierarchyDirection.CallsFrom
? localize('title.from', " calls from '{0}'", item.name)
: localize('title.to', " calls to '{0}'", item.name));
}
});
if (!this._toggleDirection) {
this._toggleDirection = new ToggleHierarchyDirectionAction(
() => this._direction,
() => {
let newDirection = this._direction === CallHierarchyDirection.CallsFrom ? CallHierarchyDirection.CallsTo : CallHierarchyDirection.CallsFrom;
this._direction = newDirection;
this.showItem(item);
}
);
this._actionbarWidget.push(this._toggleDirection, { label: false, icon: true });
this._disposables.push(this._toggleDirection);
}
}
private _show() {
if (!this._isShowing) {
this.editor.revealLineInCenterIfOutsideViewport(this._where.lineNumber, ScrollType.Smooth);
super.show(Range.fromPositions(this._where), this._layoutInfo.height);
}
}
protected _onWidth(width: number) {
if (this._dim) {
this._doLayoutBody(this._dim.height, width);
}
}
protected _doLayoutBody(height: number, width: number): void {
super._doLayoutBody(height, width);
this._dim = { height, width };
this._layoutInfo.height = this._viewZone ? this._viewZone.heightInLines : this._layoutInfo.height;
this._splitView.layout(width);
this._splitView.resizeView(0, width * this._layoutInfo.ratio);
}
}
registerThemingParticipant((theme, collector) => {
const referenceHighlightColor = theme.getColor(referencesWidget.peekViewEditorMatchHighlight);
if (referenceHighlightColor) {
collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { background-color: ${referenceHighlightColor}; }`);
}
const referenceHighlightBorder = theme.getColor(referencesWidget.peekViewEditorMatchHighlightBorder);
if (referenceHighlightBorder) {
collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { border: 2px solid ${referenceHighlightBorder}; box-sizing: border-box; }`);
}
const resultsBackground = theme.getColor(referencesWidget.peekViewResultsBackground);
if (resultsBackground) {
collector.addRule(`.monaco-editor .call-hierarchy .tree { background-color: ${resultsBackground}; }`);
}
const resultsMatchForeground = theme.getColor(referencesWidget.peekViewResultsFileForeground);
if (resultsMatchForeground) {
collector.addRule(`.monaco-editor .call-hierarchy .tree { color: ${resultsMatchForeground}; }`);
}
const resultsSelectedBackground = theme.getColor(referencesWidget.peekViewResultsSelectionBackground);
if (resultsSelectedBackground) {
collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`);
}
const resultsSelectedForeground = theme.getColor(referencesWidget.peekViewResultsSelectionForeground);
if (resultsSelectedForeground) {
collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`);
}
const editorBackground = theme.getColor(referencesWidget.peekViewEditorBackground);
if (editorBackground) {
collector.addRule(
`.monaco-editor .call-hierarchy .editor .monaco-editor .monaco-editor-background,` +
`.monaco-editor .call-hierarchy .editor .monaco-editor .inputarea.ime-input {` +
` background-color: ${editorBackground};` +
`}`
);
}
const editorGutterBackground = theme.getColor(referencesWidget.peekViewEditorGutterBackground);
if (editorGutterBackground) {
collector.addRule(
`.monaco-editor .call-hierarchy .editor .monaco-editor .margin {` +
` background-color: ${editorGutterBackground};` +
`}`
);
}
});

View file

@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyProvider } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { symbolKindToCssClass, Location } from 'vs/editor/common/modes';
import { ILabelService } from 'vs/platform/label/common/label';
export class Call {
constructor(
readonly direction: CallHierarchyDirection,
readonly item: CallHierarchyItem,
readonly locations: Location[]
) { }
}
export class SingleDirectionDataSource implements IAsyncDataSource<CallHierarchyItem, Call> {
constructor(
public provider: CallHierarchyProvider,
public direction: () => CallHierarchyDirection
) { }
hasChildren(_element: CallHierarchyItem): boolean {
return true;
}
async getChildren(element: CallHierarchyItem | Call): Promise<Call[]> {
if (element instanceof Call) {
element = element.item;
}
const direction = this.direction();
const calls = await this.provider.resolveCallHierarchyItem(element, direction, CancellationToken.None);
return calls
? calls.map(([item, locations]) => new Call(direction, item, locations))
: [];
}
}
export class IdentityProvider implements IIdentityProvider<Call> {
getId(element: Call): { toString(): string; } {
return element.item._id;
}
}
class CallRenderingTemplate {
iconLabel: IconLabel;
}
export class CallRenderer implements ITreeRenderer<Call, FuzzyScore, CallRenderingTemplate> {
static id = 'CallRenderer';
templateId: string = CallRenderer.id;
constructor(@ILabelService private readonly _labelService: ILabelService) { }
renderTemplate(container: HTMLElement): CallRenderingTemplate {
const iconLabel = new IconLabel(container, { supportHighlights: true });
return { iconLabel };
}
renderElement(node: ITreeNode<Call, FuzzyScore>, _index: number, template: CallRenderingTemplate): void {
const { element, filterData } = node;
const detail = element.item.detail || this._labelService.getUriLabel(element.item.uri, { relative: true });
template.iconLabel.setLabel(
element.item.name,
detail,
{
labelEscapeNewLines: true,
matches: createMatches(filterData),
extraClasses: [symbolKindToCssClass(element.item.kind, true)]
}
);
}
disposeTemplate(template: CallRenderingTemplate): void {
template.iconLabel.dispose();
}
}
export class VirtualDelegate implements IListVirtualDelegate<Call> {
getHeight(_element: Call): number {
return 22;
}
getTemplateId(_element: Call): string {
return CallRenderer.id;
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;} .icon-vs-action-blue{fill:#00539C;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M15.989 8l-2 2h2v4h-2l2 2h-6l-3.989-3.989v2.287l-.497.289c-.464.27-.983.413-1.503.413-1.654 0-3-1.346-3-3v-6c0-1.654 1.346-3 3-3 .52 0 1.039.143 1.503.413l.497.289v4.298h-2v2h2v1.989l3.989-3.989h-3l2-2h-2v-4h2l-2-2h6l3 3 .011 1.989-3.011 3.011h3z" id="outline"/><path class="icon-vs-bg" d="M4 4c.366 0 .705.105 1 .277v2.723h-2v4h2v2.723c-.295.171-.634.277-1 .277-1.104 0-2-.896-2-2v-6c0-1.104.896-2 2-2z" id="iconBg"/><path class="icon-vs-action-blue" d="M11.989 1h-2l2 2h-4v2h4l-2 2h2l3-3-3-3zm-4 11l3 3h2l-2-2h4v-2h-4l2-2h-2l-3 3z" id="colorAction"/></svg>

After

Width:  |  Height:  |  Size: 898 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M15.989 8l-2 2h2v4h-2l2 2h-6L6 12.011v2.287l-.497.289C5.039 14.857 4.52 15 4 15c-1.654 0-3-1.346-3-3V6c0-1.654 1.346-3 3-3 .52 0 1.039.143 1.503.413L6 3.702V8H4v2h2v1.989L9.989 8h-3l2-2h-2V2h2l-2-2h6l3 3L16 4.989 12.989 8h3z" id="outline"/><path class="icon-vs-bg" d="M4 4c.366 0 .705.105 1 .277V7H3v4h2v2.723A1.987 1.987 0 0 1 4 14a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z" id="iconBg"/><path class="icon-vs-action-blue" d="M11.989 1h-2l2 2h-4v2h4l-2 2h2l3-3-3-3zm-4 11l3 3h2l-2-2h4v-2h-4l2-2h-2l-3 3z" id="colorAction"/></svg>

After

Width:  |  Height:  |  Size: 831 B

View file

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .call-hierarchy .results,
.monaco-workbench .call-hierarchy .message {
display: none;
}
.monaco-workbench .call-hierarchy[data-state="data"] .results {
display: inherit;
}
.monaco-workbench .call-hierarchy[data-state="message"] .message {
display: inherit;
text-align: center;
padding-top: 3em;
}
.monaco-workbench .call-hierarchy-toggle {
background-image: url(CallerOrCalleeView_16x.svg);
background-size: 14px 14px;
background-repeat: no-repeat;
background-position: left center;
}
.vs-dark .monaco-workbench .call-hierarchy-toggle,
.hc-dark .monaco-workbench .call-hierarchy-toggle {
background-image: url(CallerOrCalleeView_16x_dark.svg);
}
.monaco-workbench .call-hierarchy .monaco-split-view2.horizontal > .split-view-container > .split-view-view{
/* this is a little bizare.. */
height: unset;
}
.monaco-workbench .call-hierarchy .tree{
height: 100%;
}

View file

@ -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 { 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';
import { URI } from 'vs/base/common/uri';
export const enum CallHierarchyDirection {
CallsFrom = 1,
CallsTo = 2
}
export interface CallHierarchyItem {
_id: number;
kind: SymbolKind;
name: string;
detail?: string;
uri: URI;
range: IRange;
selectionRange: IRange;
}
export interface CallHierarchyProvider {
provideCallHierarchyItem(
document: ITextModel,
postion: IPosition,
token: CancellationToken
): ProviderResult<CallHierarchyItem>;
resolveCallHierarchyItem(
item: CallHierarchyItem,
direction: CallHierarchyDirection,
token: CancellationToken
): ProviderResult<[CallHierarchyItem, Location[]][]>;
}
export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry<CallHierarchyProvider>();

View file

@ -309,6 +309,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';