Skip to content

Commit

Permalink
GH-707: Introduced the @theia/typehierarchy extension.
Browse files Browse the repository at this point in the history
 - Extended the LSP with the type hierarchy.
 - From now on, a tree node can carry decoration data.
 - Added editor access for the current/active editors.

Closes: #707.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
  • Loading branch information
Akos Kitta committed Mar 5, 2019
1 parent 184ce8d commit 3b726b6
Show file tree
Hide file tree
Showing 27 changed files with 1,295 additions and 17 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,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
Expand Down
1 change: 1 addition & 0 deletions examples/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@theia/terminal": "^0.4.0",
"@theia/textmate-grammars": "^0.4.0",
"@theia/tslint": "^0.4.0",
"@theia/typehierarchy": "^0.4.0",
"@theia/typescript": "^0.4.0",
"@theia/userstorage": "^0.4.0",
"@theia/variable-resolver": "^0.4.0",
Expand Down
1 change: 1 addition & 0 deletions examples/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@theia/terminal": "^0.4.0",
"@theia/textmate-grammars": "^0.4.0",
"@theia/tslint": "^0.4.0",
"@theia/typehierarchy": "^0.4.0",
"@theia/typescript": "^0.4.0",
"@theia/userstorage": "^0.4.0",
"@theia/variable-resolver": "^0.4.0",
Expand Down
25 changes: 24 additions & 1 deletion packages/core/src/browser/tree/tree-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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;
}

}

}
11 changes: 7 additions & 4 deletions packages/core/src/browser/tree/tree-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -617,11 +617,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<K extends keyof TreeDecoration.Data>(node: TreeNode, key: K): TreeDecoration.Data[K][] {
Expand Down
7 changes: 6 additions & 1 deletion packages/editor/src/browser/editor-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -62,4 +62,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);
});
99 changes: 97 additions & 2 deletions packages/editor/src/browser/editor-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Range>;
Expand Down Expand Up @@ -139,3 +140,97 @@ export class EditorManager extends NavigatableWidgetOpenHandler<EditorWidget> {
}

}

/**
* 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';
}
4 changes: 2 additions & 2 deletions packages/editor/src/browser/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
* 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';
import { Saveable } from '@theia/core/lib/browser';
import { EditorDecoration } from './decorations';

export {
Position, Range
Position, Range, Location
};

export const TextEditorProvider = Symbol('TextEditorProvider');
Expand Down
6 changes: 4 additions & 2 deletions packages/java/src/browser/java-client-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ 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,
) {
super(workspace, languages, languageClientFactory);
}
Expand All @@ -78,7 +78,9 @@ 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),
]);
return client;
}

Expand Down
7 changes: 5 additions & 2 deletions packages/languages/src/browser/language-client-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
ILanguageClient, LanguageClientOptions, MonacoLanguageClient,
createConnection, LanguageContribution
} from './language-client-services';
import { TypeHierarchyFeature } from './typehierarchy/typehierarchy-feature';

@injectable()
export class LanguageClientFactory {
Expand Down Expand Up @@ -64,7 +65,7 @@ export class LanguageClientFactory {
}
const initializationFailedHandler = clientOptions.initializationFailedHandler;
clientOptions.initializationFailedHandler = e => !!initializationFailedHandler && initializationFailedHandler(e);
return this.patch4085(new MonacoLanguageClient({
const client = new MonacoLanguageClient({
id: contribution.id,
name: contribution.name,
clientOptions,
Expand All @@ -74,7 +75,9 @@ export class LanguageClientFactory {
return createConnection(connection, errorHandler, closeHandler);
}
}
}));
});
client.registerFeature(new TypeHierarchyFeature(client));
return this.patch4085(client);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/********************************************************************************
* Copyright (C) 2019 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 {
Disposable,
ILanguageClient,
DocumentSelector,
ClientCapabilities,
ServerCapabilities,
TextDocumentFeature,
TextDocumentRegistrationOptions
} from '../language-client-services';
import { TypeHierarchyRequest } 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

/**
* Text document feature for handling super- and subtype hierarchies through the LSP.
*/
export class TypeHierarchyFeature extends TextDocumentFeature<TextDocumentRegistrationOptions> {

constructor(readonly client: ILanguageClient) {
super(client, TypeHierarchyRequest.type);
}

fillClientCapabilities(capabilities: ClientCapabilities): void {
if (!capabilities.textDocument) {
capabilities.textDocument = {};
}
// tslint:disable-next-line:no-any
(capabilities.textDocument as any).typeHierarchy = {
dynamicRegistration: true
};
}

initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {
if (!documentSelector) {
return;
}
const capabilitiesExt: ServerCapabilities & { typeHierarchyProvider?: boolean } = capabilities;
if (capabilitiesExt.typeHierarchyProvider) {
const id = v4();
this.register(this.messages, {
id,
registerOptions: Object.assign({}, { documentSelector: documentSelector }, capabilitiesExt.typeHierarchyProvider)
});
}
}

dispose(): void {
super.dispose();
}

protected registerLanguageProvider(): Disposable {
return Disposable.create(() => { /* NOOP */ });
}

}

/**
* 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}.`);
}
}

}
Loading

0 comments on commit 3b726b6

Please sign in to comment.