Skip to content

Commit

Permalink
Add definition provider API
Browse files Browse the repository at this point in the history
Signed-off-by: Mykola Morhun <mmorhun@redhat.com>
  • Loading branch information
mmorhun committed Sep 20, 2018
1 parent 7c678cc commit 5c8402d
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 11 deletions.
18 changes: 18 additions & 0 deletions packages/plugin-ext/src/api/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,21 @@ export interface Hover {
export interface HoverProvider {
provideHover(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Hover | undefined | Thenable<Hover | undefined>;
}

export interface Location {
uri: UriComponents;
range: Range;
}

export type Definition = Location | Location[];

export interface DefinitionLink {
uri: UriComponents;
range: Range;
origin?: Range;
selectionRange?: Range;
}

export interface DefinitionProvider {
provideDefinition(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Definition | DefinitionLink[] | undefined;
}
9 changes: 6 additions & 3 deletions packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import {
CompletionResultDto,
MarkerData,
SignatureHelp,
Hover
Hover,
Definition,
DefinitionLink
} from './model';

export interface PluginInitData {
Expand Down Expand Up @@ -640,7 +642,7 @@ export interface LanguagesExt {
$provideCompletionItems(handle: number, resource: UriComponents, position: Position, context: CompletionContext): Promise<CompletionResultDto | undefined>;
$resolveCompletionItem(handle: number, resource: UriComponents, position: Position, completion: Completion): Promise<Completion>;
$releaseCompletionItems(handle: number, id: number): void;

$provideDefinition(handle: number, resource: UriComponents, position: Position): Promise<Definition | DefinitionLink[] | undefined>;
$provideSignatureHelp(handle: number, resource: UriComponents, position: Position): Promise<SignatureHelp | undefined>;
$provideHover(handle: number, resource: UriComponents, position: Position): Promise<Hover | undefined>;
}
Expand All @@ -650,7 +652,8 @@ export interface LanguagesMain {
$setLanguageConfiguration(handle: number, languageId: string, configuration: SerializedLanguageConfiguration): void;
$unregister(handle: number): void;
$registerCompletionSupport(handle: number, selector: SerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void;
$registerSignatureHelpSupport(handle: number, selector: SerializedDocumentFilter[], triggerCharacters: string[]): void;
$registerDefinitionProvider(handle: number, selector: SerializedDocumentFilter[]): void;
$registerSignatureHelpProvider(handle: number, selector: SerializedDocumentFilter[], triggerCharacters: string[]): void;
$registerHoverProvider(handle: number, selector: SerializedDocumentFilter[]): void;

$clearDiagnostics(id: string): void;
Expand Down
44 changes: 43 additions & 1 deletion packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,19 @@ export class LanguagesMainImpl implements LanguagesMain {
}));
}

$registerSignatureHelpSupport(handle: number, selector: SerializedDocumentFilter[], triggerCharacters: string[]): void {
$registerDefinitionProvider(handle: number, selector: SerializedDocumentFilter[]): void {
const languageSelector = fromLanguageSelector(selector);
const definitionProvider = this.createDefinitionProvider(handle, languageSelector);
const disposable = new DisposableCollection();
for (const language of getLanguages()) {
if (this.matchLanguage(languageSelector, language)) {
disposable.push(monaco.languages.registerDefinitionProvider(language, definitionProvider));
}
}
this.disposables.set(handle, disposable);
}

$registerSignatureHelpProvider(handle: number, selector: SerializedDocumentFilter[], triggerCharacters: string[]): void {
const languageSelector = fromLanguageSelector(selector);
const signatureHelpProvider = this.createSignatureHelpProvider(handle, languageSelector, triggerCharacters);
const disposable = new DisposableCollection();
Expand Down Expand Up @@ -142,6 +154,36 @@ export class LanguagesMainImpl implements LanguagesMain {
};
}

protected createDefinitionProvider(handle: number, selector: LanguageSelector | undefined): monaco.languages.DefinitionProvider {
return {
provideDefinition: (model, position, token) => {
if (!this.matchModel(selector, MonacoModelIdentifier.fromModel(model))) {
return undefined!;
}
return this.proxy.$provideDefinition(handle, model.uri, position).then(result => {
if (!result) {
return undefined!;
}

if (Array.isArray(result)) {
// using DefinitionLink because Location is mandatory part of DefinitionLink
const definitionLinks: monaco.languages.DefinitionLink[] = [];
for (const item of result) {
definitionLinks.push({ ...item, uri: monaco.Uri.revive(item.uri) });
}
return definitionLinks;
} else {
// single Location
return <monaco.languages.Location>{
uri: monaco.Uri.revive(result.uri),
range: result.range
};
}
});
}
};
}

protected createSignatureHelpProvider(handle: number, selector: LanguageSelector | undefined, triggerCharacters: string[]): monaco.languages.SignatureHelpProvider {
return {
signatureHelpTriggerCharacters: triggerCharacters,
Expand Down
21 changes: 18 additions & 3 deletions packages/plugin-ext/src/plugin/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@ import {
Completion,
SerializedDocumentFilter,
SignatureHelp,
Hover
Hover,
Definition,
DefinitionLink
} from '../api/model';
import { CompletionAdapter } from './languages/completion';
import { Diagnostics } from './languages/diagnostics';
import { SignatureHelpAdapter } from './languages/signature';
import { HoverAdapter } from './languages/hover';
import { DefinitionAdapter } from './languages/definition';

type Adapter = CompletionAdapter | SignatureHelpAdapter | HoverAdapter;
type Adapter = CompletionAdapter | SignatureHelpAdapter | HoverAdapter | DefinitionAdapter;

export class LanguagesExtImpl implements LanguagesExt {

Expand Down Expand Up @@ -165,14 +168,26 @@ export class LanguagesExtImpl implements LanguagesExt {
}
// ### Completion end

// ### Definition provider begin
$provideDefinition(handle: number, resource: UriComponents, position: Position): Promise<Definition | DefinitionLink[] | undefined> {
return this.withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(URI.revive(resource), position));
}

registerDefinitionProvider(selector: theia.DocumentSelector, provider: theia.DefinitionProvider): theia.Disposable {
const callId = this.addNewAdapter(new DefinitionAdapter(provider, this.documents));
this.proxy.$registerDefinitionProvider(callId, this.transformDocumentSelector(selector));
return this.createDisposable(callId);
}
// ### Definition provider end

// ### Signature help begin
$provideSignatureHelp(handle: number, resource: UriComponents, position: Position): Promise<SignatureHelp | undefined> {
return this.withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position));
}

registerSignatureHelpProvider(selector: theia.DocumentSelector, provider: theia.SignatureHelpProvider, ...triggerCharacters: string[]): theia.Disposable {
const callId = this.addNewAdapter(new SignatureHelpAdapter(provider, this.documents));
this.proxy.$registerSignatureHelpSupport(callId, this.transformDocumentSelector(selector), triggerCharacters);
this.proxy.$registerSignatureHelpProvider(callId, this.transformDocumentSelector(selector), triggerCharacters);
return this.createDisposable(callId);
}
// ### Signature help end
Expand Down
82 changes: 82 additions & 0 deletions packages/plugin-ext/src/plugin/languages/definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/********************************************************************************
* Copyright (C) 2018 Red Hat, Inc. 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 URI from 'vscode-uri/lib/umd';
import * as theia from '@theia/plugin';
import { DocumentsExtImpl } from '../documents';
import * as types from '../types-impl';
import * as Converter from '../type-converters';
import { Position } from '../../api/plugin-api';
import { Definition, DefinitionLink, Location } from '../../api/model';

export class DefinitionAdapter {

constructor(
private readonly delegate: theia.DefinitionProvider,
private readonly documents: DocumentsExtImpl) {

}

provideDefinition(resource: URI, position: Position): Promise<Definition | DefinitionLink[] | undefined> {
const documentData = this.documents.getDocumentData(resource);
if (!documentData) {
return Promise.reject(new Error(`There is no document for ${resource}`));
}

const document = documentData.document;
const zeroBasedPosition = Converter.toPosition(position);

return Promise.resolve(this.delegate.provideDefinition(document, zeroBasedPosition, undefined)).then(definition => {
if (!definition) {
return undefined;
}

if (definition instanceof types.Location) {
return Converter.fromLocation(definition);
}

if (isLocationArray(definition)) {
const locations: Location[] = [];

for (const location of definition) {
locations.push(Converter.fromLocation(location));
}

return locations;
}

if (isDefinitionLinkArray(definition)) {
const definitionLinks: DefinitionLink[] = [];

for (const definitionLink of definition) {
definitionLinks.push(Converter.fromDefinitionLink(definitionLink));
}

return definitionLinks;
}
});
}
}

/* tslint:disable-next-line:no-any */
function isLocationArray(array: any): array is types.Location[] {
return Array.isArray(array) && array.length > 0 && array[0] instanceof types.Location;
}

/* tslint:disable-next-line:no-any */
function isDefinitionLinkArray(array: any): array is theia.DefinitionLink[] {
return Array.isArray(array) && array.length > 0 && array[0].hasOwnProperty('targetUri') && array[0].hasOwnProperty('targetRange');
}
4 changes: 2 additions & 2 deletions packages/plugin-ext/src/plugin/languages/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import URI from 'vscode-uri/lib/umd';
import * as theia from '@theia/plugin';
import * as types from '../types-impl';
import { DocumentsExtImpl } from '../documents';
import * as Converter from '../type-converters';
import { Position } from '../../api/plugin-api';
import { SignatureHelp } from '../../api/model';

Expand All @@ -36,7 +36,7 @@ export class SignatureHelpAdapter {
}

const document = documentData.document;
const zeroBasedPosition = new types.Position(position.lineNumber - 1, position.column - 1);
const zeroBasedPosition = Converter.toPosition(position);

return Promise.resolve(this.delegate.provideSignatureHelp(document, zeroBasedPosition, undefined));
}
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ export function createAPIFactory(rpc: RPCProtocol, pluginManager: PluginManager)
registerCompletionItemProvider(selector: theia.DocumentSelector, provider: theia.CompletionItemProvider, ...triggerCharacters: string[]): theia.Disposable {
return languagesExt.registerCompletionItemProvider(selector, provider, triggerCharacters);
},
registerDefinitionProvider(selector: theia.DocumentSelector, provider: theia.DefinitionProvider): theia.Disposable {
return languagesExt.registerDefinitionProvider(selector, provider);
},
registerSignatureHelpProvider(selector: theia.DocumentSelector, provider: theia.SignatureHelpProvider, ...triggerCharacters: string[]): theia.Disposable {
return languagesExt.registerSignatureHelpProvider(selector, provider, ...triggerCharacters);
},
Expand Down
42 changes: 41 additions & 1 deletion packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,17 @@
********************************************************************************/

import { EditorPosition, Selection, Position, DecorationOptions } from '../api/plugin-api';
import { Range, Hover, MarkdownString, CompletionType, SingleEditOperation, MarkerData, RelatedInformation } from '../api/model';
import {
Range,
Hover,
MarkdownString,
CompletionType,
SingleEditOperation,
MarkerData,
RelatedInformation,
Location,
DefinitionLink
} from '../api/model';
import * as theia from '@theia/plugin';
import * as types from './types-impl';
import { LanguageSelector, LanguageFilter, RelativePattern } from './languages';
Expand Down Expand Up @@ -75,6 +85,20 @@ export function fromRange(range: theia.Range | undefined): Range | undefined {
};
}

// TODO make this primary converter, see https://github.com/theia-ide/theia/issues/2910
export function fromRange_(range: theia.Range | undefined): Range | undefined {
if (!range) {
return undefined;
}
const { start, end } = range;
return {
startLineNumber: start.line + 1,
startColumn: start.character + 1,
endLineNumber: end.line + 1,
endColumn: end.character + 1
};
}

export function fromPosition(position: types.Position): Position {
return { lineNumber: position.line + 1, column: position.character + 1 };
}
Expand Down Expand Up @@ -353,3 +377,19 @@ export function fromHover(hover: theia.Hover): Hover {
contents: fromManyMarkdown(hover.contents)
};
}

export function fromLocation(location: theia.Location): Location {
return <Location>{
uri: location.uri,
range: fromRange_(location.range)
};
}

export function fromDefinitionLink(definitionLink: theia.DefinitionLink): DefinitionLink {
return <DefinitionLink>{
uri: definitionLink.targetUri,
range: fromRange_(definitionLink.targetRange),
origin: definitionLink.originSelectionRange ? fromRange_(definitionLink.originSelectionRange) : undefined,
selectionRange: definitionLink.targetSelectionRange ? fromRange_(definitionLink.targetSelectionRange) : undefined
};
}
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,3 +859,5 @@ export class Hover {
this.range = range;
}
}

export type Definition = Location | Location[];
21 changes: 21 additions & 0 deletions packages/plugin/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,24 @@ theia.languages.registerHoverProvider({scheme: 'file'}, {
}
});
```
#### Definition provider
It is possible to provide definition source for a symbol from within plugin.
To do this one should register corresponding provider. For example:
```typescript
const documentsSelector: theia.DocumentSelector = { scheme: 'file', language: 'typescript' };
const handler: theia.DefinitionProvider = { provideDefinition: provideDefinitionHandler };

const disposable = theia.languages.registerDefinitionProvider(documentsSelector, handler);

...

function provideDefinitionHandler(document: theia.TextDocument, position: theia.Position): theia.ProviderResult<theia.Definition | theia.DefinitionLink[]> {
// code here
}
```
The handler will be invoked each time when a user executes `Go To Definition` command.
It is possible to return a few sources, but for most cases only one is enough. Return `undefined` to provide nothing.
Loading

0 comments on commit 5c8402d

Please sign in to comment.