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

CallHierarchyService Plugin API #3765 #6924

Merged
merged 1 commit into from
Jan 29, 2020
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
CallHierarchyService Plugin API #3765
Issue #3765

Signed-off-by: Victor Rubezhny <vrubezhny@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
  • Loading branch information
vrubezhny authored and tsmaeder committed Jan 28, 2020
commit a982e5674a8190c0838d33a12e094c7b673089d0
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Breaking changes:
- `theiaPluginDir`: to specify the folder in which to download plugins, in respect to your `package.json`
- `theiaPlugins`: to specify the list of plugins in the form of `"id": "url"`
- [core] renamed method `registerComositionEventListeners()` to `registerCompositionEventListeners()` [#6961](https://github.com/eclipse-theia/theia/pull/6961)
- [callhierarchy] changed CallHierarchyService to align with VS Code API:
- Use LanaguageSelector instead of language id
- Use position instead of range for lookup of root symbol
- Changed data structures to be like VS Code API
- [core] removed `virtual-renderer`. `react-renderer` should be used instead [#6885](https://github.com/eclipse-theia/theia/pull/6885)
- [core] removed `virtual-widget`. `react-widget` should be used instead [#6885](https://github.com/eclipse-theia/theia/pull/6885)
- [task] renamed method `getStrigifiedTaskSchema()` has been renamed to `getStringifiedTaskSchema()` [#6780](https://github.com/eclipse-theia/theia/pull/6780)
Expand Down
12 changes: 5 additions & 7 deletions packages/callhierarchy/src/browser/callhierarchy-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { ILanguageClient } from '@theia/languages/lib/browser';
import {
ReferencesRequest, DocumentSymbolRequest, DefinitionRequest, TextDocumentPositionParams,
TextDocumentIdentifier, SymbolInformation, Location, Position, DocumentSymbol, ReferenceParams, LocationLink
TextDocumentIdentifier, SymbolInformation, Location, Position, DocumentSymbol, ReferenceParams, LocationLink, DocumentUri
} from 'monaco-languageclient/lib/services';
import * as utils from './utils';
import { ILogger, Disposable } from '@theia/core';
Expand Down Expand Up @@ -53,25 +53,23 @@ export class CallHierarchyContext implements Disposable {
return model;
}

async getDefinitionLocation(location: Location): Promise<Location | undefined> {
const uri = location.uri;
const { line, character } = location.range.start;
async getDefinitionLocation(uri: DocumentUri, position: Position): Promise<Location | undefined> {

// Definition can be null
// eslint-disable-next-line no-null/no-null
let locations: Location | Location[] | LocationLink[] | null = null;
try {
locations = await this.languageClient.sendRequest(DefinitionRequest.type, <TextDocumentPositionParams>{
position: Position.create(line, character),
position: position,
textDocument: { uri }
});
} catch (error) {
this.logger.error(`Error from definitions request: ${uri}#${line}/${character}`, error);
this.logger.error(`Error from definitions request: ${uri}#${position.line}/${position.character}`, error);
}
if (!locations) {
return undefined;
}
const targetLocation = Array.isArray(locations) ? locations[0] : locations;
const targetLocation = Array.isArray(locations) ? locations[0] : locations;
return LocationLink.is(targetLocation) ? {
uri: targetLocation.targetUri,
range: targetLocation.targetSelectionRange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { CallHierarchyTreeWidget } from './callhierarchy-tree/callhierarchy-tree
import { CALLHIERARCHY_ID } from './callhierarchy';
import { CurrentEditorAccess } from './current-editor-access';
import { CallHierarchyServiceProvider } from './callhierarchy-service';
import URI from '@theia/core/lib/common/uri';

export const CALL_HIERARCHY_TOGGLE_COMMAND_ID = 'callhierachy:toggle';
export const CALL_HIERARCHY_LABEL = 'Call Hierarchy';
Expand Down Expand Up @@ -53,7 +54,7 @@ export class CallHierarchyContribution extends AbstractViewContribution<CallHier
protected isCallHierarchyAvailable(): boolean {
const selection = this.editorAccess.getSelection();
const languageId = this.editorAccess.getLanguageId();
return !!selection && !!languageId && !!this.callHierarchyServiceProvider.get(languageId);
return !!selection && !!languageId && !!this.callHierarchyServiceProvider.get(languageId, new URI(selection.uri));
}

async openView(args?: Partial<OpenViewArguments>): Promise<CallHierarchyTreeWidget> {
Expand Down
20 changes: 12 additions & 8 deletions packages/callhierarchy/src/browser/callhierarchy-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
********************************************************************************/

import { injectable, inject } from 'inversify';
import { LanguageSelector } from '@theia/languages/lib/common/language-selector';
import { LanguageClientProvider } from '@theia/languages/lib/browser/language-client-provider';
import {
SymbolInformation, Location, Position, Range, SymbolKind, DocumentSymbol
SymbolInformation, Location, Position, Range, SymbolKind, DocumentSymbol, DocumentUri
} from 'monaco-languageclient/lib/services';
import * as utils from './utils';
import { Definition, Caller } from './callhierarchy';
Expand All @@ -30,19 +31,22 @@ export type ExtendedDocumentSymbol = DocumentSymbol & Location & { containerName

@injectable()
export abstract class AbstractDefaultCallHierarchyService implements CallHierarchyService {

@inject(LanguageClientProvider) readonly languageClientProvider: LanguageClientProvider;
@inject(ILogger) readonly logger: ILogger;
@inject(MonacoTextModelService) readonly textModelService: MonacoTextModelService;

abstract get languageId(): string;

get selector(): LanguageSelector {
return this.languageId;
}

/**
* Returns root definition of caller hierarchy.
*/
public async getRootDefinition(location: Location): Promise<Definition | undefined> {
public async getRootDefinition(uri: DocumentUri, position: Position): Promise<Definition | undefined> {
return this.withContext(async services => {
const definitionLocation = await services.getDefinitionLocation(location);
const definitionLocation = await services.getDefinitionLocation(uri, position);
if (!definitionLocation) {
return undefined;
}
Expand Down Expand Up @@ -119,8 +123,8 @@ export abstract class AbstractDefaultCallHierarchyService implements CallHierarc
return result;
}

protected toCaller(callerDefinition: Definition, references: Location[]): Caller {
return <Caller>{ callerDefinition, references };
protected toCaller(def: Definition, references: Location[]): Caller {
return <Caller>{ callerDefinition: def, references: references.map(ref => ref.range) };
}

protected async toDefinition(symbol: ExtendedDocumentSymbol | SymbolInformation, context: CallHierarchyContext): Promise<Definition | undefined> {
Expand All @@ -131,9 +135,9 @@ export abstract class AbstractDefaultCallHierarchyService implements CallHierarc
const symbolName = symbol.name;
const symbolKind = symbol.kind;
const containerName = symbol.containerName;
return <Definition>{ location, symbolName, symbolKind, containerName };
const selectionRange = location.range;
return { location, selectionRange, symbolName, symbolKind, containerName };
}

/**
* Override this to configure the callables of your language.
*/
Expand Down
49 changes: 41 additions & 8 deletions packages/callhierarchy/src/browser/callhierarchy-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,24 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject, named } from 'inversify';
import { Location } from 'vscode-languageserver-types';
import { Definition, Caller } from './callhierarchy';
import { injectable, inject, named, postConstruct } from 'inversify';
import { Position, DocumentUri } from 'vscode-languageserver-types';
import { Definition, Caller, Callee } from './callhierarchy';
import { ContributionProvider } from '@theia/core/lib/common';
import { LanguageSelector, score } from '@theia/languages/lib/common/language-selector';
import URI from '@theia/core/lib/common/uri';
import { Disposable } from '@theia/core/lib/common';
import { CancellationToken } from '@theia/core';

export const CallHierarchyService = Symbol('CallHierarchyService');

export interface CallHierarchyService {
readonly languageId: string
getRootDefinition(location: Location): Promise<Definition | undefined>
getCallers(definition: Definition): Promise<Caller[] | undefined>

readonly selector: LanguageSelector;

getRootDefinition(uri: DocumentUri, position: Position, cancellationToken: CancellationToken): Promise<Definition | undefined>
getCallers(definition: Definition, cancellationToken: CancellationToken): Promise<Caller[] | undefined>
getCallees?(definition: Definition, cancellationToken: CancellationToken): Promise<Callee[] | undefined>
}

@injectable()
Expand All @@ -33,7 +40,33 @@ export class CallHierarchyServiceProvider {
@inject(ContributionProvider) @named(CallHierarchyService)
protected readonly contributions: ContributionProvider<CallHierarchyService>;

get(languageId: string): CallHierarchyService | undefined {
return this.contributions.getContributions().find(service => languageId === service.languageId);
private services: CallHierarchyService[] = [];

@postConstruct()
init(): void {
this.services = this.services.concat(this.contributions.getContributions());
benoitf marked this conversation as resolved.
Show resolved Hide resolved
}

get(languageId: string, uri: URI): CallHierarchyService | undefined {

return this.services.sort(
(left, right) =>
score(right.selector, uri.scheme, uri.path.toString(), languageId, true) - score(left.selector, uri.scheme, uri.path.toString(), languageId, true))[0];
}

add(service: CallHierarchyService): Disposable {
this.services.push(service);
const that = this;
return {
dispose: () => {
that.remove(service);
}
};
}

private remove(service: CallHierarchyService): boolean {
const length = this.services.length;
this.services = this.services.filter(value => value !== service);
return length !== this.services.length;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,36 @@ import { injectable, inject } from 'inversify';
import { TreeModelImpl, TreeNode } from '@theia/core/lib/browser';
import { CallHierarchyTree, DefinitionNode } from './callhierarchy-tree';
import { CallHierarchyServiceProvider } from '../callhierarchy-service';
import { Location } from 'vscode-languageserver-types';
import { Position } from 'vscode-languageserver-types';
import URI from '@theia/core/lib/common/uri';
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';

@injectable()
export class CallHierarchyTreeModel extends TreeModelImpl {

private _languageId: string | undefined;

@inject(CallHierarchyTree) protected readonly tree: CallHierarchyTree;
@inject(CallHierarchyServiceProvider) protected readonly callHierarchyServiceProvider: CallHierarchyServiceProvider;

getTree(): CallHierarchyTree {
return this.tree;
}

async initializeCallHierarchy(languageId: string | undefined, location: Location | undefined): Promise<void> {
get languageId(): string | undefined {
return this._languageId;
}

async initializeCallHierarchy(languageId: string | undefined, uri: string | undefined, position: Position | undefined): Promise<void> {
this.tree.root = undefined;
this.tree.callHierarchyService = undefined;
if (languageId && location) {
const callHierarchyService = this.callHierarchyServiceProvider.get(languageId);
this._languageId = languageId;
if (languageId && uri && position) {
const callHierarchyService = this.callHierarchyServiceProvider.get(languageId, new URI(uri));
if (callHierarchyService) {
this.tree.callHierarchyService = callHierarchyService;
const rootDefinition = await callHierarchyService.getRootDefinition(location);
const cancellationSource = new CancellationTokenSource();
const rootDefinition = await callHierarchyService.getRootDefinition(uri, position, cancellationSource.token);
if (rootDefinition) {
const rootNode = DefinitionNode.create(rootDefinition, undefined);
this.tree.root = rootNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { DefinitionNode, CallerNode } from './callhierarchy-tree';
import { CallHierarchyTreeModel } from './callhierarchy-tree-model';
import { CALLHIERARCHY_ID, Definition, Caller } from '../callhierarchy';
import URI from '@theia/core/lib/common/uri';
import { Location, Range, SymbolKind } from 'vscode-languageserver-types';
import { Location, Range, SymbolKind, DocumentUri } from 'vscode-languageserver-types';
import { EditorManager } from '@theia/editor/lib/browser';
import * as React from 'react';

Expand Down Expand Up @@ -65,7 +65,7 @@ export class CallHierarchyTreeWidget extends TreeWidget {
}

initializeModel(selection: Location | undefined, languageId: string | undefined): void {
this.model.initializeCallHierarchy(languageId, selection);
this.model.initializeCallHierarchy(languageId, selection ? selection.uri : undefined, selection ? selection.range.start : undefined);
}

protected createNodeClassNames(node: TreeNode, props: NodeProps): string[] {
Expand Down Expand Up @@ -165,33 +165,35 @@ export class CallHierarchyTreeWidget extends TreeWidget {
}

private openEditor(node: TreeNode, keepFocus: boolean): void {
let location: Location | undefined;

if (DefinitionNode.is(node)) {
location = node.definition.location;
const def = node.definition;
this.doOpenEditor(node.definition.location.uri, def.selectionRange ? def.selectionRange : def.location.range, keepFocus);
}
if (CallerNode.is(node)) {
location = node.caller.references[0];
}
if (location) {
this.editorManager.open(
new URI(location.uri), {
mode: keepFocus ? 'reveal' : 'activate',
selection: Range.create(location.range.start, location.range.end)
}
).then(editorWidget => {
if (editorWidget.parent instanceof DockPanel) {
editorWidget.parent.selectWidget(editorWidget);
}
});
this.doOpenEditor(node.caller.callerDefinition.location.uri, node.caller.references[0], keepFocus);
}
}

private doOpenEditor(uri: DocumentUri, range: Range, keepFocus: boolean): void {
this.editorManager.open(
new URI(uri), {
mode: keepFocus ? 'reveal' : 'activate',
selection: range
}
).then(editorWidget => {
if (editorWidget.parent instanceof DockPanel) {
editorWidget.parent.selectWidget(editorWidget);
}
});
}

storeState(): object {
const callHierarchyService = this.model.getTree().callHierarchyService;
if (this.model.root && callHierarchyService) {
return {
root: this.deflateForStorage(this.model.root),
languageId: callHierarchyService.languageId,
languageId: this.model.languageId,
};
} else {
return {};
Expand All @@ -202,9 +204,9 @@ export class CallHierarchyTreeWidget extends TreeWidget {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((oldState as any).root && (oldState as any).languageId) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.model.root = this.inflateFromStorage((oldState as any).root);
const root = this.inflateFromStorage((oldState as any).root) as DefinitionNode;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.model.initializeCallHierarchy((oldState as any).languageId, (this.model.root as DefinitionNode).definition.location);
this.model.initializeCallHierarchy((oldState as any).languageId, root.definition.location.uri, root.definition.location.range.start);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Definition, Caller } from '../callhierarchy';
import { CallHierarchyService } from '../callhierarchy-service';

import { Md5 } from 'ts-md5/dist/md5';
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';

@injectable()
export class CallHierarchyTree extends TreeImpl {
Expand Down Expand Up @@ -49,7 +50,8 @@ export class CallHierarchyTree extends TreeImpl {
definition = parent.caller.callerDefinition;
}
if (definition) {
const callers = await this.callHierarchyService.getCallers(definition);
const cancellationSource = new CancellationTokenSource();
const callers = await this.callHierarchyService.getCallers(definition, cancellationSource.token);
if (!callers) {
return Promise.resolve([]);
}
Expand Down
13 changes: 9 additions & 4 deletions packages/callhierarchy/src/browser/callhierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,24 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { Location, SymbolKind } from 'vscode-languageserver-types';
import { Range, SymbolKind, Location } from 'vscode-languageserver-types';

export const CALLHIERARCHY_ID = 'callhierarchy';

export interface Definition {
location: Location,
selectionRange: Range,
symbolName: string,
symbolKind: SymbolKind,
containerName: string,
callers: Caller[] | undefined
containerName: string | undefined
}

export interface Caller {
callerDefinition: Definition,
references: Location[]
references: Range[]
}

export interface Callee {
calleeDefinition: Definition,
references: Range[]
}
Loading