diff --git a/.travis.yml b/.travis.yml index f1daa91257d9a..031b97546213e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,6 +52,7 @@ cache: - packages/terminal/node_modules - packages/textmate-grammars/node_modules - packages/tslint/node_modules + - packages/typehierarchy/node_modules - packages/typescript/node_modules - packages/userstorage/node_modules - packages/variable-resolver/node_modules diff --git a/examples/browser/package.json b/examples/browser/package.json index 141e49633c52a..5a031b77520fb 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -40,6 +40,7 @@ "@theia/textmate-grammars": "^0.3.15", "@theia/tslint": "^0.3.15", "@theia/typescript": "^0.3.15", + "@theia/typehierarchy": "^0.3.15", "@theia/userstorage": "^0.3.15", "@theia/variable-resolver": "^0.3.15", "@theia/workspace": "^0.3.15" diff --git a/examples/electron/package.json b/examples/electron/package.json index a5428e219bc34..4fae0c192d8a7 100644 --- a/examples/electron/package.json +++ b/examples/electron/package.json @@ -43,6 +43,7 @@ "@theia/textmate-grammars": "^0.3.15", "@theia/tslint": "^0.3.15", "@theia/typescript": "^0.3.15", + "@theia/typehierarchy": "^0.3.15", "@theia/userstorage": "^0.3.15", "@theia/variable-resolver": "^0.3.15", "@theia/workspace": "^0.3.15" diff --git a/packages/core/src/browser/tree/tree-decorator.ts b/packages/core/src/browser/tree/tree-decorator.ts index 603a918eee45a..41f780a96c381 100644 --- a/packages/core/src/browser/tree/tree-decorator.ts +++ b/packages/core/src/browser/tree/tree-decorator.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; -import { Tree } from './tree'; +import { Tree, TreeNode } from './tree'; import { Event, Emitter, Disposable, DisposableCollection, MaybePromise } from '../../common'; /** @@ -501,4 +501,27 @@ export namespace TreeDecoration { } + /** + * Tree node that can be decorated explicitly, without the tree decorators. + */ + export interface DecoratedTreeNode extends TreeNode { + + /** + * The additional tree decoration data attached to the tree node itself. + */ + readonly decorationData: Data; + + } + + export namespace DecoratedTreeNode { + + /** + * Type-guard for decorated tree nodes. + */ + export function is(node: TreeNode | undefined): node is DecoratedTreeNode { + return !!node && 'decorationData' in node; + } + + } + } diff --git a/packages/core/src/browser/tree/tree-widget.tsx b/packages/core/src/browser/tree/tree-widget.tsx index 2da90ade758a2..10ec7d228e1a3 100644 --- a/packages/core/src/browser/tree/tree-widget.tsx +++ b/packages/core/src/browser/tree/tree-widget.tsx @@ -592,11 +592,14 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } protected getDecorations(node: TreeNode): TreeDecoration.Data[] { - const decorations = this.decorations.get(node.id); - if (decorations) { - return decorations.sort(TreeDecoration.Data.comparePriority); + const decorations: TreeDecoration.Data[] = []; + if (TreeDecoration.DecoratedTreeNode.is(node)) { + decorations.push(node.decorationData); + } + if (this.decorations.has(node.id)) { + decorations.push(...this.decorations.get(node.id)); } - return []; + return decorations.sort(TreeDecoration.Data.comparePriority); } protected getDecorationData(node: TreeNode, key: K): TreeDecoration.Data[K][] { diff --git a/packages/editor/src/browser/editor-frontend-module.ts b/packages/editor/src/browser/editor-frontend-module.ts index 149fe73baf8b2..aad3387c04cd2 100644 --- a/packages/editor/src/browser/editor-frontend-module.ts +++ b/packages/editor/src/browser/editor-frontend-module.ts @@ -18,7 +18,7 @@ import { ContainerModule } from 'inversify'; import { CommandContribution, MenuContribution } from '@theia/core/lib/common'; import { OpenHandler, WidgetFactory, FrontendApplicationContribution, KeybindingContext, KeybindingContribution } from '@theia/core/lib/browser'; import { VariableContribution } from '@theia/variable-resolver/lib/browser'; -import { EditorManager } from './editor-manager'; +import { EditorManager, EditorAccess, ActiveEditorAccess, CurrentEditorAccess } from './editor-manager'; import { EditorContribution } from './editor-contribution'; import { EditorMenuContribution } from './editor-menu'; import { EditorCommandContribution } from './editor-command'; @@ -61,4 +61,9 @@ export default new ContainerModule(bind => { bind(VariableContribution).to(EditorVariableContribution).inSingletonScope(); bind(SemanticHighlightingService).toSelf().inSingletonScope(); + + bind(CurrentEditorAccess).toSelf().inSingletonScope(); + bind(ActiveEditorAccess).toSelf().inSingletonScope(); + bind(EditorAccess).to(CurrentEditorAccess).inSingletonScope().whenTargetNamed(EditorAccess.CURRENT); + bind(EditorAccess).to(ActiveEditorAccess).inSingletonScope().whenTargetNamed(EditorAccess.ACTIVE); }); diff --git a/packages/editor/src/browser/editor-manager.ts b/packages/editor/src/browser/editor-manager.ts index 6410da21d3a74..5cb98c475636f 100644 --- a/packages/editor/src/browser/editor-manager.ts +++ b/packages/editor/src/browser/editor-manager.ts @@ -14,13 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, postConstruct, } from 'inversify'; +import { injectable, postConstruct, inject } from 'inversify'; import URI from '@theia/core/lib/common/uri'; import { RecursivePartial, Emitter, Event } from '@theia/core/lib/common'; import { WidgetOpenerOptions, NavigatableWidgetOpenHandler } from '@theia/core/lib/browser'; import { EditorWidget } from './editor-widget'; -import { Range, Position } from './editor'; +import { Range, Position, Location } from './editor'; import { EditorWidgetFactory } from './editor-widget-factory'; +import { TextEditor } from './editor'; export interface EditorOpenerOptions extends WidgetOpenerOptions { selection?: RecursivePartial; @@ -134,3 +135,97 @@ export class EditorManager extends NavigatableWidgetOpenHandler { } } + +/** + * Provides direct access to the underlying text editor. + */ +@injectable() +export abstract class EditorAccess { + + @inject(EditorManager) + protected readonly editorManager: EditorManager; + + /** + * The URI of the underlying document from the editor. + */ + get uri(): string | undefined { + const editor = this.editor; + if (editor) { + return editor.uri.toString(); + } + return undefined; + } + + /** + * The selection location from the text editor. + */ + get selection(): Location | undefined { + const editor = this.editor; + if (editor) { + const uri = editor.uri.toString(); + const range = editor.selection; + return { + range, + uri + }; + } + return undefined; + } + + /** + * The unique identifier of the language the current editor belongs to. + */ + get languageId(): string | undefined { + const editor = this.editor; + if (editor) { + return editor.document.languageId; + } + return undefined; + } + + /** + * The text editor. + */ + get editor(): TextEditor | undefined { + const editorWidget = this.editorWidget(); + if (editorWidget) { + return editorWidget.editor; + } + return undefined; + } + + /** + * The editor widget, or `undefined` if not applicable. + */ + protected abstract editorWidget(): EditorWidget | undefined; + +} + +/** + * Provides direct access to the currently active text editor. + */ +@injectable() +export class CurrentEditorAccess extends EditorAccess { + + protected editorWidget(): EditorWidget | undefined { + return this.editorManager.currentEditor; + } + +} + +/** + * Provides access to the active text editor. + */ +@injectable() +export class ActiveEditorAccess extends EditorAccess { + + protected editorWidget(): EditorWidget | undefined { + return this.editorManager.activeEditor; + } + +} + +export namespace EditorAccess { + export const CURRENT = 'current-editor-access'; + export const ACTIVE = 'active-editor-access'; +} diff --git a/packages/editor/src/browser/editor.ts b/packages/editor/src/browser/editor.ts index 3c50f3aa2ea0f..c3c209aa7dd66 100644 --- a/packages/editor/src/browser/editor.ts +++ b/packages/editor/src/browser/editor.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Position, Range } from 'vscode-languageserver-types'; +import { Position, Range, Location } from 'vscode-languageserver-types'; import * as lsp from 'vscode-languageserver-types'; import URI from '@theia/core/lib/common/uri'; import { Event, Disposable } from '@theia/core/lib/common'; @@ -22,7 +22,7 @@ import { Saveable } from '@theia/core/lib/browser'; import { EditorDecoration } from './decorations'; export { - Position, Range + Position, Range, Location }; export const TextEditorProvider = Symbol('TextEditorProvider'); diff --git a/packages/java/package.json b/packages/java/package.json index 0d7708579efba..78e97ccdd3222 100644 --- a/packages/java/package.json +++ b/packages/java/package.json @@ -7,6 +7,7 @@ "@theia/editor": "^0.3.15", "@theia/languages": "^0.3.15", "@theia/monaco": "^0.3.15", + "@theia/typehierarchy": "^0.3.15", "@types/glob": "^5.0.30", "@types/tar": "4.0.0", "glob": "^7.1.2", @@ -58,6 +59,6 @@ "extends": "../../configs/nyc.json" }, "ls": { - "downloadUrl": "https://github.com/kittaakos/eclipse.jdt.ls-gh-715/raw/master/jdt-language-server-latest.tar.gz" + "downloadUrl": "https://github.com/kittaakos/eclipse.jdt.ls-gh-28/raw/master/jdt-language-server-latest.tar.gz" } } diff --git a/packages/java/src/browser/java-client-contribution.ts b/packages/java/src/browser/java-client-contribution.ts index a919c429215da..83a1e6c738469 100644 --- a/packages/java/src/browser/java-client-contribution.ts +++ b/packages/java/src/browser/java-client-contribution.ts @@ -18,6 +18,7 @@ import { injectable, inject } from 'inversify'; import { MessageConnection } from 'vscode-jsonrpc'; import { CommandService } from '@theia/core/lib/common'; import { StatusBar, StatusBarEntry, StatusBarAlignment } from '@theia/core/lib/browser'; +import { TypeHierarchyService } from '@theia/typehierarchy/lib/browser/typehierarchy-service'; import { SemanticHighlightingService } from '@theia/editor/lib/browser/semantic-highlight/semantic-highlighting-service'; import { Window, @@ -50,7 +51,8 @@ export class JavaClientContribution extends BaseLanguageClientContribution { @inject(Window) protected readonly window: Window, @inject(CommandService) protected readonly commandService: CommandService, @inject(StatusBar) protected readonly statusBar: StatusBar, - @inject(SemanticHighlightingService) protected readonly semanticHighlightingService: SemanticHighlightingService + @inject(SemanticHighlightingService) protected readonly semanticHighlightingService: SemanticHighlightingService, + @inject(TypeHierarchyService) protected readonly typeHierarchyService: TypeHierarchyService ) { super(workspace, languages, languageClientFactory); } @@ -71,7 +73,10 @@ export class JavaClientContribution extends BaseLanguageClientContribution { protected createLanguageClient(connection: MessageConnection): ILanguageClient { const client: ILanguageClient & Readonly<{ languageId: string }> = Object.assign(super.createLanguageClient(connection), { languageId: this.id }); - client.registerFeature(SemanticHighlightingService.createNewFeature(this.semanticHighlightingService, client)); + client.registerFeatures([ + SemanticHighlightingService.createNewFeature(this.semanticHighlightingService, client), + ...TypeHierarchyService.createNewFeatures(this.typeHierarchyService, client) + ]); return client; } diff --git a/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-feature.ts b/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-feature.ts index 4ccc27613fc1e..ce2114a57b3c0 100644 --- a/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-feature.ts +++ b/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-feature.ts @@ -24,7 +24,7 @@ import { ServerCapabilities, Disposable, DocumentSelector -} from '../'; +} from '../index'; import { SemanticHighlight, SemanticHighlightingParams } from './semantic-highlighting-protocol'; // NOTE: This module can be removed, or at least can be simplified once the semantic highlighting will become the part of the LSP. diff --git a/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-protocol.ts b/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-protocol.ts index 9b34e7185872c..156dbe5321b09 100644 --- a/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-protocol.ts +++ b/packages/languages/src/browser/semantic-highlighting/semantic-highlighting-protocol.ts @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { NotificationType } from 'vscode-jsonrpc'; -import { VersionedTextDocumentIdentifier } from '..'; +import { NotificationType, VersionedTextDocumentIdentifier } from '../index'; // NOTE: This module can be removed, once the semantic highlighting will become the part of the LSP. // https://github.com/Microsoft/vscode-languageserver-node/issues/368 diff --git a/packages/languages/src/browser/typehierarchy/typehierarchy-feature.ts b/packages/languages/src/browser/typehierarchy/typehierarchy-feature.ts new file mode 100644 index 0000000000000..a7005bf988e37 --- /dev/null +++ b/packages/languages/src/browser/typehierarchy/typehierarchy-feature.ts @@ -0,0 +1,156 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { v4 } from 'uuid'; +import { Emitter, Event } from '@theia/core/lib/common/event'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { + Disposable, + ILanguageClient, + DocumentSelector, + ClientCapabilities, + ServerCapabilities, + TextDocumentFeature, + TextDocumentPositionParams, + TextDocumentRegistrationOptions +} from '../index'; +import { TypeHierarchyMessageType, DocumentSymbolExt, SubTypesRequest, SuperTypesRequest } from './typehierarchy-protocol'; + +// NOTE: This module can be removed, or at least can be simplified once the type hierarchy will become the part of the LSP. +// https://github.com/Microsoft/language-server-protocol/issues/582 +// https://github.com/Microsoft/vscode-languageserver-node/pull/346#discussion_r221659062 + +/** + * Abstract text document feature for handling super- and subtype hierarchies through the LSP. + */ +export abstract class TypeHierarchyFeature extends TextDocumentFeature { + + readonly languageId: string; + + protected readonly onInitializedEmitter = new Emitter(); + protected readonly onDisposedEmitter = new Emitter(); + protected readonly toDispose = new DisposableCollection(this.onInitializedEmitter, this.onDisposedEmitter); + + protected constructor( + readonly client: ILanguageClient & Readonly<{ languageId: string }>, + readonly type: TypeHierarchyType, + protected readonly messageType: TypeHierarchyMessageType) { + + super(client, messageType); + this.languageId = client.languageId; + } + + fillClientCapabilities(capabilities: ClientCapabilities): void { + if (!capabilities.textDocument) { + capabilities.textDocument = {}; + } + // tslint:disable-next-line:no-any + (capabilities.textDocument as any).typeHierarchyCapabilities = { + typeHierarchy: true + }; + } + + initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void { + if (!documentSelector) { + return; + } + const capabilitiesExt: ServerCapabilities & { typeHierarchy?: boolean } = capabilities; + if (capabilitiesExt.typeHierarchy) { + this.onInitializedEmitter.fire(undefined); + const id = v4(); + this.register(this.messages, { + id, + registerOptions: Object.assign({}, { documentSelector: documentSelector }, capabilitiesExt.typeHierarchy) + }); + } + } + + dispose(): void { + this.onDisposedEmitter.fire(undefined); + super.dispose(); + } + + /** + * Performs the `textDocument/subTypes`/`textDocument/superTypes` LSP method invocations. + */ + async get(params: TextDocumentPositionParams): Promise { + const symbol = await this._client.sendRequest(this.messageType, params); + return DocumentSymbolExt.is(symbol) ? symbol : undefined; + } + + /** + * Called when the feature is disposed. + */ + get onDisposed(): Event { + return this.onDisposedEmitter.event; + } + + /** + * Called when the feature is initialized. The event fires only when the server set the `capabilitiesExt.typeHierarchy` to `true`. + */ + get onInitialized(): Event { + return this.onInitializedEmitter.event; + } + + protected registerLanguageProvider(): Disposable { + return Disposable.create(() => this.toDispose.dispose()); + } + +} + +/** + * Enumeration of available type hierarchy types. + */ +export enum TypeHierarchyType { + SUBTYPE = 'subtype', + SUPERTYPE = 'supertype' +} + +export namespace TypeHierarchyType { + + /** + * Returns the counterpart of the argument. For `subtype`, it returns `supertype` and vice versa. + */ + export function flip(type: TypeHierarchyType): TypeHierarchyType { + switch (type) { + case TypeHierarchyType.SUBTYPE: return TypeHierarchyType.SUPERTYPE; + case TypeHierarchyType.SUPERTYPE: return TypeHierarchyType.SUBTYPE; + default: throw new Error(`Unexpected type hierarchy type: ${type}.`); + } + } + +} + +/** + * Text document feature for supertype hierarchies. + */ +export class SuperTypeHierarchyFeature extends TypeHierarchyFeature { + + constructor(readonly client: ILanguageClient & Readonly<{ languageId: string }>) { + super(client, TypeHierarchyType.SUPERTYPE, SuperTypesRequest.type); + } + +} + +/** + * Text document feature for subtype hierarchies. + */ +export class SubTypeHierarchyFeature extends TypeHierarchyFeature { + + constructor(readonly client: ILanguageClient & Readonly<{ languageId: string }>) { + super(client, TypeHierarchyType.SUBTYPE, SubTypesRequest.type); + } +} diff --git a/packages/languages/src/browser/typehierarchy/typehierarchy-protocol.ts b/packages/languages/src/browser/typehierarchy/typehierarchy-protocol.ts new file mode 100644 index 0000000000000..45d72832b20f4 --- /dev/null +++ b/packages/languages/src/browser/typehierarchy/typehierarchy-protocol.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { RequestType, TextDocumentPositionParams, DocumentSymbol } from '../index'; + +// NOTE: This module can be removed, once the type hierarchy will become the part of the LSP. +// https://github.com/Microsoft/language-server-protocol/issues/582 +// https://github.com/Microsoft/vscode-languageserver-node/pull/346#discussion_r221659062 + +/** + * Yet another document symbol with a `DocumentUri` `string`. + */ +export interface DocumentSymbolExt extends DocumentSymbol { + + /** + * The URI of the text document this symbol belongs to. + * + * If not defined, it can be inferred from the context of the request. For example, when calling the `textDocument/documentSymbol` + * method, the `DocumentUri` (`string`) can be inferred from the request parameter: `DocumentSymbolParams.textDocument.uri`. + */ + readonly uri: string; + +} + +export namespace DocumentSymbolExt { + + /** + * Type-guard for `DocumentSymbol`s with an additional `uri` property. + */ + export function is(symbol: DocumentSymbol | undefined): symbol is DocumentSymbolExt { + // tslint:disable-next-line:no-any + return !!symbol && 'uri' in symbol && typeof (symbol as any)['uri'] === 'string'; + } + +} + +/** + * The RPC message type for super- and subtype requests. + */ +export type TypeHierarchyMessageType = RequestType; + +/** + * The `textDocument/subTypes` request is sent from the client to the server to collect subtype information for a type under the given `TextDocumentPositionParams`. + * If no symbols can be found under the given position, returns with `undefined`. + */ +export namespace SubTypesRequest { + export const type: TypeHierarchyMessageType = new RequestType('textDocument/subTypes'); +} + +/** + * The `textDocument/super` request is sent from the client to the server to collect supertype information for a type under the given `TextDocumentPositionParams`. + * If no symbols can be found under the given position, returns with `undefined`. + */ +export namespace SuperTypesRequest { + export const type: TypeHierarchyMessageType = new RequestType('textDocument/superTypes'); +} diff --git a/packages/typehierarchy/README.md b/packages/typehierarchy/README.md new file mode 100644 index 0000000000000..7b7130f2bde8b --- /dev/null +++ b/packages/typehierarchy/README.md @@ -0,0 +1,8 @@ +# Theia - Type Hierarchy Extension + +Supports super- and subtype information for document symbols. + +## License + +- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/) +- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp) diff --git a/packages/typehierarchy/compile.tsconfig.json b/packages/typehierarchy/compile.tsconfig.json new file mode 100644 index 0000000000000..18b90d15df3c5 --- /dev/null +++ b/packages/typehierarchy/compile.tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../configs/base.tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ] +} diff --git a/packages/typehierarchy/package.json b/packages/typehierarchy/package.json new file mode 100644 index 0000000000000..a62d80d52d6c7 --- /dev/null +++ b/packages/typehierarchy/package.json @@ -0,0 +1,51 @@ +{ + "name": "@theia/typehierarchy", + "version": "0.3.15", + "description": "Theia - Type Hierarchy Extension", + "dependencies": { + "@theia/core": "^0.3.15", + "@theia/editor": "^0.3.15", + "@theia/languages": "^0.3.15", + "@theia/monaco": "^0.3.15", + "@types/uuid": "^3.4.3", + "uuid": "^3.2.1" + }, + "publishConfig": { + "access": "public" + }, + "theiaExtensions": [ + { + "frontend": "lib/browser/typehierarchy-frontend-module" + } + ], + "keywords": [ + "theia-extension" + ], + "license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0", + "repository": { + "type": "git", + "url": "https://github.com/theia-ide/theia.git" + }, + "bugs": { + "url": "https://github.com/theia-ide/theia/issues" + }, + "homepage": "https://github.com/theia-ide/theia", + "files": [ + "lib", + "src" + ], + "scripts": { + "prepare": "yarn run clean && yarn run build", + "clean": "theiaext clean", + "build": "theiaext build", + "watch": "theiaext watch", + "test": "theiaext test", + "docs": "theiaext docs" + }, + "devDependencies": { + "@theia/ext-scripts": "^0.3.15" + }, + "nyc": { + "extends": "../../configs/nyc.json" + } +} diff --git a/packages/typehierarchy/src/browser/style/index.css b/packages/typehierarchy/src/browser/style/index.css new file mode 100644 index 0000000000000..f29a11e618119 --- /dev/null +++ b/packages/typehierarchy/src/browser/style/index.css @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.theia-type-hierarchy-tree { + font-size: var(--theia-ui-font-size0); + color: var(--theia-ui-font-color1); +} + +.theia-caption-suffix { + padding-left: 10px !important; +} + +.theia-caption-prefix { + padding-right: 5px !important; + padding-left: 1px !important; +} diff --git a/packages/typehierarchy/src/browser/tree/typehierarchy-tree-container.ts b/packages/typehierarchy/src/browser/tree/typehierarchy-tree-container.ts new file mode 100644 index 0000000000000..2a1c35c67bdfc --- /dev/null +++ b/packages/typehierarchy/src/browser/tree/typehierarchy-tree-container.ts @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { interfaces, Container } from 'inversify'; +import { createTreeContainer, Tree, TreeImpl, TreeModel, TreeModelImpl, TreeWidget } from '@theia/core/lib/browser/tree'; +import { TypeHierarchyTree } from './typehierarchy-tree'; +import { TypeHierarchyTreeModel } from './typehierarchy-tree-model'; +import { TypeHierarchyTreeWidget } from './typehierarchy-tree-widget'; + +function createHierarchyTreeContainer(parent: interfaces.Container): Container { + const child = createTreeContainer(parent); + + child.unbind(TreeImpl); + child.bind(TypeHierarchyTree).toSelf(); + child.rebind(Tree).toDynamicValue(ctx => ctx.container.get(TypeHierarchyTree)); + + child.unbind(TreeModelImpl); + child.bind(TypeHierarchyTreeModel).toSelf(); + child.rebind(TreeModel).toDynamicValue(ctx => ctx.container.get(TypeHierarchyTreeModel)); + + child.bind(TypeHierarchyTreeWidget).toSelf(); + child.rebind(TreeWidget).toDynamicValue(ctx => ctx.container.get(TypeHierarchyTreeWidget)); + + return child; +} + +export function createHierarchyTreeWidget(parent: interfaces.Container): TypeHierarchyTreeWidget { + return createHierarchyTreeContainer(parent).get(TypeHierarchyTreeWidget); +} diff --git a/packages/typehierarchy/src/browser/tree/typehierarchy-tree-model.ts b/packages/typehierarchy/src/browser/tree/typehierarchy-tree-model.ts new file mode 100644 index 0000000000000..dacadf0d2ff93 --- /dev/null +++ b/packages/typehierarchy/src/browser/tree/typehierarchy-tree-model.ts @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { TreeNode } from '@theia/core/lib/browser/tree/tree'; +import { TreeModelImpl } from '@theia/core/lib/browser/tree/tree-model'; +import { Location } from '@theia/editor/lib/browser/editor'; +import { DocumentSymbolExt } from '@theia/languages/lib/browser/typehierarchy/typehierarchy-protocol'; +import { TypeHierarchyType } from '@theia/languages/lib/browser/typehierarchy/typehierarchy-feature'; +import { TypeHierarchyService } from '../typehierarchy-service'; +import { TypeHierarchyTree } from './typehierarchy-tree'; + +@injectable() +export class TypeHierarchyTreeModel extends TreeModelImpl { + + @inject(TypeHierarchyService) + protected readonly typeHierarchyService: TypeHierarchyService; + + protected doOpenNode(node: TreeNode): void { + // do nothing (in particular do not expand the node) + } + + /** + * Initializes the tree by calculating and setting a new tree root node. + */ + async initialize(options: TypeHierarchyTree.InitOptions): Promise { + this.tree.root = undefined; + const { location, languageId, type } = options; + if (languageId && location) { + const symbol = await this.symbol(languageId, type, location); + if (symbol) { + const root = TypeHierarchyTree.RootNode.create(symbol, languageId, type); + root.expanded = true; + this.tree.root = root; + } + } + } + + /** + * If the tree root is set, it resets it with the inverse type hierarchy direction. + */ + async flipDirection(): Promise { + const { root } = this.tree; + if (TypeHierarchyTree.RootNode.is(root)) { + const { type, location, languageId } = root; + this.initialize({ + type: TypeHierarchyType.flip(type), + location, + languageId + }); + } + } + + /** + * Returns with the super- or subtypes for the argument. + */ + protected async symbol(languageId: string, type: TypeHierarchyType, location: Location): Promise { + switch (type) { + case TypeHierarchyType.SUBTYPE: return this.typeHierarchyService.subTypes(languageId, location); + case TypeHierarchyType.SUPERTYPE: return this.typeHierarchyService.superTypes(languageId, location); + default: throw new Error(`Unexpected type hierarchy type: ${type}.`); + } + } + +} diff --git a/packages/typehierarchy/src/browser/tree/typehierarchy-tree-widget.tsx b/packages/typehierarchy/src/browser/tree/typehierarchy-tree-widget.tsx new file mode 100644 index 0000000000000..1fb161581f69b --- /dev/null +++ b/packages/typehierarchy/src/browser/tree/typehierarchy-tree-widget.tsx @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as React from 'react'; +import { inject, injectable } from 'inversify'; +import { DockPanel } from '@phosphor/widgets'; +import URI from '@theia/core/lib/common/uri'; +import { SymbolKind, Range } from '@theia/languages/lib/browser'; +import { TreeNode } from '@theia/core/lib/browser/tree/tree'; +import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; +import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer'; +import { TreeWidget, TreeProps } from '@theia/core/lib/browser/tree/tree-widget'; +import { TypeHierarchyTreeModel } from './typehierarchy-tree-model'; +import { TypeHierarchyTree } from './typehierarchy-tree'; + +@injectable() +export class TypeHierarchyTreeWidget extends TreeWidget { + + // tslint:disable-next-line:no-any + protected readonly icons = new Map(Array.from(Object.keys(SymbolKind)).map(key => [(SymbolKind as any)[key], key.toLocaleLowerCase()] as [number, string])); + + constructor( + @inject(TreeProps) readonly props: TreeProps, + @inject(TypeHierarchyTreeModel) readonly model: TypeHierarchyTreeModel, + @inject(ContextMenuRenderer) readonly contextMenuRenderer: ContextMenuRenderer, + @inject(EditorManager) readonly editorManager: EditorManager + ) { + super(props, model, contextMenuRenderer); + this.id = TypeHierarchyTreeWidget.WIDGET_ID; + this.title.label = TypeHierarchyTreeWidget.WIDGET_LABEL; + this.title.caption = TypeHierarchyTreeWidget.WIDGET_LABEL; + this.addClass(TypeHierarchyTreeWidget.Styles.TYPE_HIERARCHY_TREE_CLASS); + this.title.closable = true; + this.title.iconClass = 'fa fa-sitemap'; + this.toDispose.push(this.model.onSelectionChanged(selection => { + const node = selection[0]; + if (node) { + this.openEditor(node, true); + } + })); + this.toDispose.push(this.model.onOpenNode(node => this.openEditor(node))); + } + + /** + * Initializes the widget with the new input. + */ + async initialize(options: TypeHierarchyTree.InitOptions): Promise { + await this.model.initialize(options); + } + + /** + * See: `TreeWidget#renderIcon`. + */ + protected renderIcon(node: TreeNode): React.ReactNode { + if (TypeHierarchyTree.Node.is(node)) { + return
; + } + // tslint:disable-next-line:no-null-keyword + return null; + } + + /** + * Opens up the node in the editor. On demand (`keepFocus`) it reveals the location in the editor. + */ + protected async openEditor(node: TreeNode, keepFocus: boolean = false): Promise { + if (TypeHierarchyTree.Node.is(node)) { + const { location } = node; + const editorWidget = await this.editorManager.open(new URI(location.uri), { + mode: keepFocus ? 'reveal' : 'activate', + selection: Range.create(location.range.start, location.range.end) + }); + if (editorWidget.parent instanceof DockPanel) { + editorWidget.parent.selectWidget(editorWidget); + } + } + + } + +} + +export namespace TypeHierarchyTreeWidget { + + export const WIDGET_ID = 'theia-typehierarchy'; + export const WIDGET_LABEL = 'Type Hierarchy'; + + /** + * CSS styles for the `Type Hierarchy` widget. + */ + export namespace Styles { + + export const TYPE_HIERARCHY_TREE_CLASS = 'theia-type-hierarchy-tree'; + + } +} diff --git a/packages/typehierarchy/src/browser/tree/typehierarchy-tree.ts b/packages/typehierarchy/src/browser/tree/typehierarchy-tree.ts new file mode 100644 index 0000000000000..0388d581abead --- /dev/null +++ b/packages/typehierarchy/src/browser/tree/typehierarchy-tree.ts @@ -0,0 +1,198 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { v4 } from 'uuid'; +import URI from '@theia/core/lib/common/uri'; +import { Location } from '@theia/editor/lib/browser/editor'; +import { SymbolKind } from '@theia/languages/lib/browser'; +import { TreeDecoration } from '@theia/core/lib/browser/tree/tree-decorator'; +import { TreeImpl, TreeNode, CompositeTreeNode, ExpandableTreeNode, SelectableTreeNode } from '@theia/core/lib/browser/tree'; +import { DocumentSymbolExt } from '@theia/languages/lib/browser/typehierarchy/typehierarchy-protocol'; +import { TypeHierarchyType } from '@theia/languages/lib/browser/typehierarchy/typehierarchy-feature'; +import { TypeHierarchyService } from '../typehierarchy-service'; + +@injectable() +export class TypeHierarchyTree extends TreeImpl { + + @inject(TypeHierarchyService) + protected readonly typeHierarchyService: TypeHierarchyService; + + async resolveChildren(parent: CompositeTreeNode): Promise { + if (TypeHierarchyTree.Node.is(parent)) { + await this.ensureResolved(parent); + if (parent.children.length === 0) { + delete parent.children; + delete parent.expanded; + return []; + } + return parent.children.slice(); + } + return []; + } + + /** + * Extracts the language ID from the root node. + */ + protected get languageId(): string | undefined { + if (TypeHierarchyTree.RootNode.is(this.root)) { + return this.root.languageId; + } + return undefined; + } + + /** + * Returns with the type hierarchy type attached to the root node. `undefined` if the root is not set. + */ + protected get type(): TypeHierarchyType | undefined { + if (TypeHierarchyTree.RootNode.is(this.root)) { + return this.root.type; + } + return undefined; + } + + /** + * Makes sure, the node and its children are resolved. Resolves it on demand. + */ + protected async ensureResolved(node: TypeHierarchyTree.Node): Promise { + if (!node.resolved) { + const { languageId, type } = this; + if (languageId && type) { + const { location } = node; + const resolvedSymbol = await (TypeHierarchyType.SUBTYPE === type + ? this.typeHierarchyService.subTypes(languageId, location) + : this.typeHierarchyService.superTypes(languageId, location)); + + if (resolvedSymbol) { + node.resolved = true; + if (resolvedSymbol.children) { + node.children = resolvedSymbol.children.filter(DocumentSymbolExt.is).map(child => TypeHierarchyTree.Node.create(child, type)); + } else { + node.children = []; + } + } + } + } + } + +} + +export namespace TypeHierarchyTree { + + export interface InitOptions { + readonly type: TypeHierarchyType; + readonly location: Location | undefined; + readonly languageId: string | undefined; + } + + export interface RootNode extends Node { + readonly type: TypeHierarchyType; + readonly languageId: string; + } + + export namespace RootNode { + + export function is(node: TreeNode | undefined): node is RootNode { + if (Node.is(node) && 'type' in node && 'languageId' in node) { + // tslint:disable-next-line:no-any + const { type, languageId } = (node as any); + return typeof languageId === 'string' && (type === TypeHierarchyType.SUBTYPE || type === TypeHierarchyType.SUPERTYPE); + } + return false; + } + + export function create(symbol: DocumentSymbolExt, languageId: string, type: TypeHierarchyType): RootNode { + return { + ...Node.create(symbol, type, true), + type, + languageId + }; + } + + } + + export interface Node extends CompositeTreeNode, ExpandableTreeNode, SelectableTreeNode, TreeDecoration.DecoratedTreeNode { + readonly location: Location; + readonly kind: SymbolKind; + resolved: boolean; + } + + export namespace Node { + + export function is(node: TreeNode | undefined): node is Node { + if (!!node && 'resolved' in node && 'location' in node && 'kind' in node) { + // tslint:disable-next-line:no-any + const { resolved, location, kind } = (node as any); + return Location.is(location) && typeof resolved === 'boolean' && typeof kind === 'number'; + } + return false; + } + + export function create(symbol: DocumentSymbolExt, type: TypeHierarchyType, resolved: boolean = true): Node { + const node = { + id: v4(), + name: symbol.name, + description: symbol.detail, + parent: undefined, + location: Location.create(symbol.uri, symbol.selectionRange), + resolved, + children: symbol.children ? symbol.children.filter(DocumentSymbolExt.is).map(child => create(child, type, false)) : [], + expanded: false, + visible: true, + selected: false, + kind: symbol.kind, + decorationData: decorationData(symbol, type) + }; + // Trick: if the node is `resolved` and have zero `children`, make the node non-expandable. + if (resolved && node.children.length === 0) { + delete node.expanded; + } + return node; + } + + function decorationData(symbol: DocumentSymbolExt, type: TypeHierarchyType): TreeDecoration.Data { + const captionSuffixes: TreeDecoration.CaptionAffix[] = [{ + data: new URI(symbol.uri).displayName, + fontData: { + color: 'var(--theia-ui-font-color2)', + } + }]; + if (symbol.detail) { + captionSuffixes.unshift({ + data: symbol.detail, + fontData: { + color: 'var(--theia-accent-color0)', + style: 'italic' + } + }); + } + const data = `${TypeHierarchyType.SUBTYPE === type ? '▼' : '▲'}`; + const color = `var(${TypeHierarchyType.SUBTYPE === type ? '--theia-error-color2' : '--theia-success-color2'})`; + return { + captionSuffixes, + captionPrefixes: [{ + data, + fontData: { + color, + style: 'bold' + } + }] + }; + } + + } + +} diff --git a/packages/typehierarchy/src/browser/typehierarchy-contribution.ts b/packages/typehierarchy/src/browser/typehierarchy-contribution.ts new file mode 100644 index 0000000000000..a015da54a81ce --- /dev/null +++ b/packages/typehierarchy/src/browser/typehierarchy-contribution.ts @@ -0,0 +1,161 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject, named } from 'inversify'; +import { MenuModelRegistry } from '@theia/core/lib/common/menu'; +import { ApplicationShell } from '@theia/core/lib/browser/shell'; +import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; +import { Command, CommandRegistry } from '@theia/core/lib/common/command'; +import { EDITOR_CONTEXT_MENU } from '@theia/editor/lib/browser/editor-menu'; +import { EditorAccess } from '@theia/editor/lib/browser/editor-manager'; +import { TypeHierarchyType } from '@theia/languages/lib/browser/typehierarchy/typehierarchy-feature'; +import { AbstractViewContribution, OpenViewArguments } from '@theia/core/lib/browser/shell/view-contribution'; +import { TypeHierarchyTree } from './tree/typehierarchy-tree'; +import { TypeHierarchyTreeWidget } from './tree/typehierarchy-tree-widget'; +import { TypeHierarchyService } from './typehierarchy-service'; + +@injectable() +export class TypeHierarchyContribution extends AbstractViewContribution { + + @inject(EditorAccess) + @named(EditorAccess.CURRENT) + protected readonly editorAccess: EditorAccess; + + @inject(TypeHierarchyService) + protected readonly typeHierarchyService: TypeHierarchyService; + + @inject(ApplicationShell) + protected readonly shell: ApplicationShell; + + constructor() { + super({ + widgetId: TypeHierarchyTreeWidget.WIDGET_ID, + widgetName: TypeHierarchyTreeWidget.WIDGET_LABEL, + defaultWidgetOptions: { + area: 'bottom' + }, + toggleCommandId: TypeHierarchyCommands.TOGGLE_VIEW.id, + toggleKeybinding: 'ctrlcmd+alt+a' + }); + } + + async openView(args?: Partial): Promise { + const widget = await super.openView(args); + const { selection, languageId } = this.editorAccess; + const type = this.getType(args); + await widget.initialize({ location: selection, languageId, type }); + return widget; + } + + registerCommands(commands: CommandRegistry): void { + super.registerCommands(commands); + commands.registerCommand(TypeHierarchyCommands.OPEN_SUBTYPE, { + execute: () => this.openViewOrFlipHierarchyDirection(TypeHierarchyType.SUBTYPE), + isEnabled: this.isEnabled.bind(this) + }); + commands.registerCommand(TypeHierarchyCommands.OPEN_SUPERTYPE, { + execute: () => this.openViewOrFlipHierarchyDirection(TypeHierarchyType.SUPERTYPE), + isEnabled: this.isEnabled.bind(this) + }); + } + + registerMenus(menus: MenuModelRegistry): void { + super.registerMenus(menus); + const menuPath = [...EDITOR_CONTEXT_MENU, 'navigation']; + menus.registerMenuAction(menuPath, { + commandId: TypeHierarchyCommands.OPEN_SUBTYPE.id + }); + menus.registerMenuAction(menuPath, { + commandId: TypeHierarchyCommands.OPEN_SUPERTYPE.id + }); + } + + registerKeybindings(keybindings: KeybindingRegistry): void { + super.registerKeybindings(keybindings); + keybindings.registerKeybinding({ + command: TypeHierarchyCommands.OPEN_SUBTYPE.id, + keybinding: 'ctrlcmd+alt+h' + }); + } + + /** + * Flips the hierarchy direction in the `Type Hierarchy` view, if it is active and has a valid root. + * Otherwise, calculates the type hierarchy based on the selection of the current editor. + */ + protected async openViewOrFlipHierarchyDirection(type: TypeHierarchyType): Promise { + if (this.isEnabled()) { + const { activeWidget } = this.shell; + if (activeWidget instanceof TypeHierarchyTreeWidget && TypeHierarchyTree.RootNode.is(activeWidget.model.root)) { + await activeWidget.model.flipDirection(); + } else { + await this.openView({ + toggle: false, + activate: true, + type + }); + } + } + } + + /** + * `true` if the super- and subtype hierarchy support is enabled. + * Enabled if: + * - the current text editor supports super- and subtype hierarchies. + * - the `Type Hierarchy` view is the active one and it has an input. + */ + protected isEnabled(languageId: string | undefined = this.editorAccess.languageId): boolean { + if (this.typeHierarchyService.isEnabledFor(languageId)) { + return true; + } + const { activeWidget } = this.shell; + return activeWidget instanceof TypeHierarchyTreeWidget; + } + + /** + * Extracts the type hierarchy type from the argument. If the hierarchy type cannot be extracted, returns with the `subtype` as the default type. + */ + protected getType(args?: Partial): TypeHierarchyType { + return !!args && !!args.type ? args.type : TypeHierarchyType.SUBTYPE; + } + +} + +export interface TypeHierarchyOpenViewArguments extends OpenViewArguments { + + /** + * The type hierarchy type for the view argument. + */ + readonly type: TypeHierarchyType; + +} + +export namespace TypeHierarchyCommands { + + export const TOGGLE_VIEW: Command = { + id: 'typehierarchy:toggle' + }; + + export const OPEN_SUBTYPE: Command = { + id: 'typehierarchy:open-subtype', + label: 'Subtype Hierarchy' + }; + + export const OPEN_SUPERTYPE: Command = { + id: 'typehierarchy:open-supertype', + label: 'Supertype Hierarchy' + }; + +} diff --git a/packages/typehierarchy/src/browser/typehierarchy-frontend-module.ts b/packages/typehierarchy/src/browser/typehierarchy-frontend-module.ts new file mode 100644 index 0000000000000..4d056b3cf8882 --- /dev/null +++ b/packages/typehierarchy/src/browser/typehierarchy-frontend-module.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; +import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; +import { TypeHierarchyService } from './typehierarchy-service'; +import { TypeHierarchyContribution } from './typehierarchy-contribution'; +import { TypeHierarchyTreeWidget } from './tree/typehierarchy-tree-widget'; +import { createHierarchyTreeWidget } from './tree/typehierarchy-tree-container'; + +import '../../src/browser/style/index.css'; + +export default new ContainerModule(bind => { + bind(TypeHierarchyService).toSelf().inSingletonScope(); + bindViewContribution(bind, TypeHierarchyContribution); + bind(WidgetFactory).toDynamicValue(context => ({ + id: TypeHierarchyTreeWidget.WIDGET_ID, + createWidget: () => createHierarchyTreeWidget(context.container) + })); +}); diff --git a/packages/typehierarchy/src/browser/typehierarchy-service.ts b/packages/typehierarchy/src/browser/typehierarchy-service.ts new file mode 100644 index 0000000000000..fc9c139edcdbf --- /dev/null +++ b/packages/typehierarchy/src/browser/typehierarchy-service.ts @@ -0,0 +1,156 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; +import { ILanguageClient, TextDocumentPositionParams, TextDocumentIdentifier, Position, Location, DocumentSymbol } from '@theia/languages/lib/browser'; +import { DocumentSymbolExt } from '@theia/languages/lib/browser/typehierarchy/typehierarchy-protocol'; +import { SubTypeHierarchyFeature, SuperTypeHierarchyFeature, TypeHierarchyFeature, TypeHierarchyType } from '@theia/languages/lib/browser/typehierarchy/typehierarchy-feature'; + +@injectable() +export class TypeHierarchyService implements Disposable { + + protected readonly subTypeFeatures = new Map(); + protected readonly superTypeFeatures = new Map(); + + /** + * Registers the `newFeature` for the given language into this service. The `newFeature` will be removed from this service, when the feature is disposed. + * This method also makes sure that existing features for the given language (`languageId`) will be disposed before registering the new one. + */ + register(newFeature: T): void { + const { type, languageId } = newFeature; + const toDisposeOnFeatureDispose = new DisposableCollection(); + toDisposeOnFeatureDispose.push(newFeature.onInitialized(() => { + const features = this.features(type); + const oldFeature = features.get(languageId); + if (oldFeature) { + oldFeature.dispose(); + } + features.set(languageId, newFeature); + })); + toDisposeOnFeatureDispose.push(newFeature.onDisposed(() => { + const features = this.features(type); + if (features.has(languageId)) { + features.get(languageId)!.dispose(); + } + toDisposeOnFeatureDispose.dispose(); + })); + } + + /** + * Disposes the service. + */ + dispose(): void { + [this.subTypeFeatures, this.superTypeFeatures].forEach(map => map.forEach(feature => feature.dispose())); + [this.subTypeFeatures, this.superTypeFeatures].forEach(map => map.clear()); + } + + /** + * `true` if the type hierarchy is supported for the given language. Otherwise, `false`. + * It is always `false` for a given language unless the connection between the language client and the server has been established. + */ + isEnabledFor(languageId: string | undefined): boolean { + return !!languageId && this.subTypeFeatures.has(languageId) && this.superTypeFeatures.has(languageId); + } + + /** + * Returns with the document symbol and its supertypes for the given argument. + */ + async superTypes(languageId: string, symbol: DocumentSymbolExt): Promise; + async superTypes(languageId: string, location: Location): Promise; + async superTypes(languageId: string, params: TextDocumentPositionParams): Promise; + async superTypes(languageId: string, arg: DocumentSymbolExt | TextDocumentPositionParams | Location): Promise { + return this.types(this.superTypeFeatures.get(languageId), arg); + } + + /** + * Returns with the document symbol and its subtypes for the given argument. + */ + async subTypes(languageId: string, symbol: DocumentSymbolExt): Promise; + async subTypes(languageId: string, params: TextDocumentPositionParams): Promise; + async subTypes(languageId: string, location: Location): Promise; + async subTypes(languageId: string, arg: DocumentSymbolExt | TextDocumentPositionParams | Location): Promise { + return this.types(this.subTypeFeatures.get(languageId), arg); + } + + /** + * Performs the `textDocument/subTypes` and `textDocument/superTypes` LSP method invocations. + */ + protected async types(feature: TypeHierarchyFeature | undefined, arg: DocumentSymbolExt | TextDocumentPositionParams | Location): Promise { + if (feature) { + const params = this.toTextDocumentPositionParams(arg); + return feature.get(params); + } + return undefined; + } + + /** + * Converts the argument into a text document position parameter. Returns with the argument if it was a text document position parameter. + */ + protected toTextDocumentPositionParams(arg: DocumentSymbolExt | TextDocumentPositionParams | Location): TextDocumentPositionParams { + if (this.isTextDocumentPositionParams(arg)) { + return arg; + } + const position = DocumentSymbol.is(arg) ? arg.selectionRange.start : arg.range.start; + const { uri } = arg; + return { + position, + textDocument: { + uri + } + }; + } + + /** + * Returns with the features map for the give hierarchy type argument. + */ + protected features(type: TypeHierarchyType): Map { + switch (type) { + case TypeHierarchyType.SUBTYPE: return this.subTypeFeatures; + case TypeHierarchyType.SUPERTYPE: return this.superTypeFeatures; + default: throw new Error(`Unknown type hierarchy type: ${type}.`); + } + } + + // tslint:disable-next-line:no-any + protected isTextDocumentPositionParams(args: any): args is TextDocumentPositionParams { + return !!args + && 'position' in args + && 'textDocument' in args + && Position.is(args['position']) + && TextDocumentIdentifier.is(args['textDocument']); + } + +} + +export namespace TypeHierarchyService { + + /** + * Creates two new language features for handling the subtype and supertype hierarchies via the LSP. + * The new features will be registered into the type hierarchy `service`. + */ + export function createNewFeatures( + service: TypeHierarchyService, + client: ILanguageClient & Readonly<{ languageId: string }>): [SubTypeHierarchyFeature, SuperTypeHierarchyFeature] { + + const subtypeFeature = new SubTypeHierarchyFeature(client); + const supertypeFeature = new SuperTypeHierarchyFeature(client); + service.register(subtypeFeature); + service.register(supertypeFeature); + return [subtypeFeature, supertypeFeature]; + } + +} diff --git a/packages/typehierarchy/src/package.spec.ts b/packages/typehierarchy/src/package.spec.ts new file mode 100644 index 0000000000000..00b73e46364eb --- /dev/null +++ b/packages/typehierarchy/src/package.spec.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (C) 2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* note: this bogus test file is required so that + we are able to run mocha unit tests on this + package, without having any actual unit tests in it. + This way a coverage report will be generated, + showing 0% coverage, instead of no report. + This file can be removed once we have real unit + tests in place. */ + +describe('typehierarchy package', () => { + + it('support code coverage statistics', () => true); + +}); diff --git a/tsconfig.json b/tsconfig.json index fb3b576956548..5c4b00aec4ea9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -91,6 +91,9 @@ "@theia/callhierarchy/lib/*": [ "packages/callhierarchy/src/*" ], + "@theia/typehierarchy/lib/*": [ + "packages/typehierarchy/src/*" + ], "@theia/variable-resolver/lib/*": [ "packages/variable-resolver/src/*" ], @@ -114,4 +117,4 @@ "dev-packages/*/src", "packages/*/src" ] -} +} \ No newline at end of file