Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

peek type hierarchy #130922

Merged
merged 1 commit into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

.monaco-workbench .type-hierarchy .results,
.monaco-workbench .type-hierarchy .message {
display: none;
}

.monaco-workbench .type-hierarchy[data-state="data"] .results {
display: inherit;
height: 100%;
}

.monaco-workbench .type-hierarchy[data-state="message"] .message {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}

.monaco-workbench .type-hierarchy .editor,
.monaco-workbench .type-hierarchy .tree {
height: 100%;
}

.monaco-workbench .type-hierarchy .tree .typehierarchy-element {
display: flex;
flex: 1;
flex-flow: row nowrap;
align-items: center;
}

.monaco-workbench .type-hierarchy .tree .typehierarchy-element .monaco-icon-label {
padding-left: 4px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,38 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { Event } from 'vs/base/common/event';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
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 { EditorAction2, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { PeekContext } from 'vs/editor/contrib/peekView/peekView';
import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { TypeHierarchyProviderRegistry } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { TypeHierarchyTreePeekWidget } from 'vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek';
import { TypeHierarchyDirection, TypeHierarchyModel, TypeHierarchyProviderRegistry } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';


const _ctxHasTypeHierarchyProvider = new RawContextKey<boolean>('editorHasTypeHierarchyProvider', false, localize('editorHasTypeHierarchyProvider', 'Whether a type hierarchy provider is available'));
const _ctxTypeHierarchyVisible = new RawContextKey<boolean>('typeHierarchyVisible', false, localize('typeHierarchyVisible', 'Whether type hierarchy peek is currently showing'));
const _ctxTypeHierarchyDirection = new RawContextKey<string>('typeHierarchyDirection', undefined, { type: 'string', description: localize('typeHierarchyDirection', 'whether type hierarchy shows super types or subtypes') });

function sanitizedDirection(candidate: string): TypeHierarchyDirection {
return candidate === TypeHierarchyDirection.Subtypes || candidate === TypeHierarchyDirection.Supertypes
? candidate
: TypeHierarchyDirection.Subtypes;
}

class TypeHierarchyController implements IEditorContribution {
static readonly Id = 'typeHierarchy';
Expand All @@ -22,26 +43,257 @@ class TypeHierarchyController implements IEditorContribution {
return editor.getContribution<TypeHierarchyController>(TypeHierarchyController.Id);
}

private static readonly _storageDirectionKey = 'typeHierarchy/defaultDirection';

private readonly _ctxHasProvider: IContextKey<boolean>;
private readonly _dispoables = new DisposableStore();
private readonly _ctxIsVisible: IContextKey<boolean>;
private readonly _ctxDirection: IContextKey<string>;
private readonly _disposables = new DisposableStore();
private readonly _sessionDisposables = new DisposableStore();

private _widget?: TypeHierarchyTreePeekWidget;

constructor(
readonly _editor: ICodeEditor,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IStorageService private readonly _storageService: IStorageService,
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
this._ctxHasProvider = _ctxHasTypeHierarchyProvider.bindTo(this._contextKeyService);
this._dispoables.add(Event.any<any>(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, TypeHierarchyProviderRegistry.onDidChange)(() => {
this._ctxIsVisible = _ctxTypeHierarchyVisible.bindTo(this._contextKeyService);
this._ctxDirection = _ctxTypeHierarchyDirection.bindTo(this._contextKeyService);
this._disposables.add(Event.any<any>(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, TypeHierarchyProviderRegistry.onDidChange)(() => {
this._ctxHasProvider.set(_editor.hasModel() && TypeHierarchyProviderRegistry.has(_editor.getModel()));
}));
this._dispoables.add(this._sessionDisposables);
this._disposables.add(this._sessionDisposables);
}

dispose(): void {
this._dispoables.dispose();
this._disposables.dispose();
}

// Peek
async startTypeHierarchyFromEditor(): Promise<void> {
this._sessionDisposables.clear();

if (!this._editor.hasModel()) {
return;
}

const document = this._editor.getModel();
const position = this._editor.getPosition();
if (!TypeHierarchyProviderRegistry.has(document)) {
return;
}

const cts = new CancellationTokenSource();
const model = TypeHierarchyModel.create(document, position, cts.token);
const direction = sanitizedDirection(this._storageService.get(TypeHierarchyController._storageDirectionKey, StorageScope.GLOBAL, TypeHierarchyDirection.Subtypes));

this._showTypeHierarchyWidget(position, direction, model, cts);
}

private _showTypeHierarchyWidget(position: Position, direction: TypeHierarchyDirection, model: Promise<TypeHierarchyModel | undefined>, cts: CancellationTokenSource) {

this._ctxIsVisible.set(true);
this._ctxDirection.set(direction);
Event.any<any>(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endTypeHierarchy, this, this._sessionDisposables);
this._widget = this._instantiationService.createInstance(TypeHierarchyTreePeekWidget, this._editor, position, direction);
this._widget.showLoading();
this._sessionDisposables.add(this._widget.onDidClose(() => {
this.endTypeHierarchy();
this._storageService.store(TypeHierarchyController._storageDirectionKey, this._widget!.direction, StorageScope.GLOBAL, StorageTarget.USER);
}));
this._sessionDisposables.add({ dispose() { cts.dispose(true); } });
this._sessionDisposables.add(this._widget);

model.then(model => {
if (cts.token.isCancellationRequested) {
return; // nothing
}
if (model) {
this._sessionDisposables.add(model);
this._widget!.showModel(model);
}
else {
this._widget!.showMessage(localize('no.item', "No results"));
}
}).catch(e => {
this._widget!.showMessage(localize('error', "Failed to show type hierarchy"));
console.error(e);
});
}

async startTypeHierarchyFromTypeHierarchy(): Promise<void> {
if (!this._widget) {
return;
}
const model = this._widget.getModel();
const typeItem = this._widget.getFocused();
if (!typeItem || !model) {
return;
}
const newEditor = await this._editorService.openCodeEditor({ resource: typeItem.item.uri }, this._editor);
if (!newEditor) {
return;
}
const newModel = model.fork(typeItem.item);
this._sessionDisposables.clear();

TypeHierarchyController.get(newEditor)._showTypeHierarchyWidget(
Range.lift(newModel.root.selectionRange).getStartPosition(),
this._widget.direction,
Promise.resolve(newModel),
new CancellationTokenSource()
);
}

showSupertypes(): void {
this._widget?.updateDirection(TypeHierarchyDirection.Supertypes);
this._ctxDirection.set(TypeHierarchyDirection.Supertypes);
}

showSubtypes(): void {
this._widget?.updateDirection(TypeHierarchyDirection.Subtypes);
this._ctxDirection.set(TypeHierarchyDirection.Subtypes);
}

endTypeHierarchy(): void {
this._sessionDisposables.clear();
this._ctxIsVisible.set(false);
this._editor.focus();
}
}

registerEditorContribution(TypeHierarchyController.Id, TypeHierarchyController);

// Testing
// Peek
registerAction2(class extends EditorAction2 {

constructor() {
super({
id: 'editor.showTypeHierarchy',
title: { value: localize('title', "Peek Type Hierarchy"), original: 'Peek Type Hierarchy' },
menu: {
id: MenuId.EditorContextPeek,
group: 'navigation',
order: 1000,
when: ContextKeyExpr.and(
_ctxHasTypeHierarchyProvider,
PeekContext.notInPeekEditor
),
},
keybinding: {
when: EditorContextKeys.editorTextFocus,
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H // TODO: to specify a proper keybinding?
},
precondition: ContextKeyExpr.and(
_ctxHasTypeHierarchyProvider,
PeekContext.notInPeekEditor
)
});
}

async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
return TypeHierarchyController.get(editor).startTypeHierarchyFromEditor();
}
});

// actions for peek widget
registerAction2(class extends EditorAction2 {

constructor() {
super({
id: 'editor.showSupertypes',
title: { value: localize('title.supertypes', "Show Supertypes"), original: 'Show Supertypes' },
icon: Codicon.typeHierarchySuper,
precondition: ContextKeyExpr.and(_ctxTypeHierarchyVisible, _ctxTypeHierarchyDirection.isEqualTo(TypeHierarchyDirection.Subtypes)),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H,
},
menu: {
id: TypeHierarchyTreePeekWidget.TitleMenu,
when: _ctxTypeHierarchyDirection.isEqualTo(TypeHierarchyDirection.Subtypes),
order: 1,
}
});
}

runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
return TypeHierarchyController.get(editor).showSupertypes();
}
});

registerAction2(class extends EditorAction2 {

constructor() {
super({
id: 'editor.showSubtypes',
title: { value: localize('title.subtypes', "Show Subtypes"), original: 'Show Subtypes' },
icon: Codicon.typeHierarchySub,
precondition: ContextKeyExpr.and(_ctxTypeHierarchyVisible, _ctxTypeHierarchyDirection.isEqualTo(TypeHierarchyDirection.Supertypes)),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H,
},
menu: {
id: TypeHierarchyTreePeekWidget.TitleMenu,
when: _ctxTypeHierarchyDirection.isEqualTo(TypeHierarchyDirection.Supertypes),
order: 1,
}
});
}

runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
return TypeHierarchyController.get(editor).showSubtypes();
}
});

registerAction2(class extends EditorAction2 {

constructor() {
super({
id: 'editor.refocusTypeHierarchy',
title: { value: localize('title.refocusTypeHierarchy', "Refocus Type Hierarchy"), original: 'Refocus Type Hierarchy' },
precondition: _ctxTypeHierarchyVisible,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift + KeyCode.Enter
}
});
}

async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
return TypeHierarchyController.get(editor).startTypeHierarchyFromTypeHierarchy();
}
});

registerAction2(class extends EditorAction2 {

constructor() {
super({
id: 'editor.closeTypeHierarchy',
title: localize('close', 'Close'),
icon: Codicon.close,
precondition: ContextKeyExpr.and(
_ctxTypeHierarchyVisible,
ContextKeyExpr.not('config.editor.stablePeek')
),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib + 10,
primary: KeyCode.Escape
},
menu: {
id: TypeHierarchyTreePeekWidget.TitleMenu,
order: 1000
}
});
}

runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void {
return TypeHierarchyController.get(editor).endTypeHierarchy();
}
});
Loading