Skip to content

Commit

Permalink
[plugin] Implement registerDeclarationProvider
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Tugarev <[email protected]>
  • Loading branch information
AlexTugarev committed Sep 13, 2019
1 parent 6580940 commit 4360a39
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 2 deletions.
3 changes: 1 addition & 2 deletions packages/monaco/src/typings/monaco/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,6 @@ declare module monaco.services {
readonly languageIdentifier: LanguageIdentifier;
}


export interface ServiceCollection {
set<T>(id: any, instanceOrDescriptor: T): T;
}
Expand Down Expand Up @@ -958,6 +957,6 @@ declare module monaco.languages {
export function registerCompletionItemProvider(selector: monaco.modes.LanguageSelector, provider: CompletionItemProvider): IDisposable;
export function registerColorProvider(selector: monaco.modes.LanguageSelector, provider: DocumentColorProvider): IDisposable;
export function registerFoldingRangeProvider(selector: monaco.modes.LanguageSelector, provider: FoldingRangeProvider): IDisposable;
export function registerDeclarationProvider(selector: monaco.modes.LanguageSelector, provider: FoldingRangeProvider): IDisposable;
export function registerDeclarationProvider(selector: monaco.modes.LanguageSelector, provider: DeclarationProvider): IDisposable;
export function registerSelectionRangeProvider(selector: monaco.modes.LanguageSelector, provider: SelectionRangeProvider): IDisposable;
}
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ export interface DefinitionProvider {
provideDefinition(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Definition | DefinitionLink[] | undefined;
}

export interface DeclarationProvider {
provideDeclaration(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Definition | DefinitionLink[] | undefined;
}

/**
* Value-object that contains additional information when
* requesting references.
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,7 @@ export interface LanguagesExt {
$provideImplementation(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<Definition | DefinitionLink[] | undefined>;
$provideTypeDefinition(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<Definition | DefinitionLink[] | undefined>;
$provideDefinition(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<Definition | DefinitionLink[] | undefined>;
$provideDeclaration(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<Definition | DefinitionLink[] | undefined>;
$provideReferences(handle: number, resource: UriComponents, position: Position, context: ReferenceContext, token: CancellationToken): Promise<Location[] | undefined>;
$provideSignatureHelp(
handle: number, resource: UriComponents, position: Position, context: SignatureHelpContext, token: CancellationToken
Expand Down Expand Up @@ -1149,6 +1150,7 @@ export interface LanguagesMain {
$registerImplementationProvider(handle: number, selector: SerializedDocumentFilter[]): void;
$registerTypeDefinitionProvider(handle: number, selector: SerializedDocumentFilter[]): void;
$registerDefinitionProvider(handle: number, selector: SerializedDocumentFilter[]): void;
$registerDeclarationProvider(handle: number, selector: SerializedDocumentFilter[]): void;
$registerReferenceProvider(handle: number, selector: SerializedDocumentFilter[]): void;
$registerSignatureHelpProvider(handle: number, selector: SerializedDocumentFilter[], metadata: theia.SignatureHelpProviderMetadata): void;
$registerHoverProvider(handle: number, selector: SerializedDocumentFilter[]): void;
Expand Down
34 changes: 34 additions & 0 deletions packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ export class LanguagesMainImpl implements LanguagesMain {
this.disposables.set(handle, disposable);
}

$registerDeclarationProvider(handle: number, selector: SerializedDocumentFilter[]): void {
const languageSelector = fromLanguageSelector(selector);
const declarationProvider = this.createDeclarationProvider(handle);
const disposable = new DisposableCollection();
disposable.push(monaco.languages.registerDeclarationProvider(languageSelector, declarationProvider));
this.disposables.set(handle, disposable);
}

$registerReferenceProvider(handle: number, selector: SerializedDocumentFilter[]): void {
const languageSelector = fromLanguageSelector(selector);
const referenceProvider = this.createReferenceProvider(handle);
Expand Down Expand Up @@ -425,6 +433,32 @@ export class LanguagesMainImpl implements LanguagesMain {
};
}

protected createDeclarationProvider(handle: number): monaco.languages.DeclarationProvider {
return {
provideDeclaration: (model, position, token) =>
this.proxy.$provideDeclaration(handle, model.uri, position, token).then(result => {
if (!result) {
return undefined;
}

if (Array.isArray(result)) {
// using DefinitionLink because Location is mandatory part of DefinitionLink
const definitionLinks: monaco.languages.LocationLink[] = [];
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, metadata: theia.SignatureHelpProviderMetadata): monaco.languages.SignatureHelpProvider {
return {
signatureHelpTriggerCharacters: metadata.triggerCharacters,
Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-ext/src/plugin/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import { ColorProviderAdapter } from './languages/color';
import { RenameAdapter } from './languages/rename';
import { Event } from '@theia/core/lib/common/event';
import { CommandRegistryImpl } from './command-registry';
import { DeclarationAdapter } from './languages/declaration';

type Adapter = CompletionAdapter |
SignatureHelpAdapter |
Expand All @@ -89,6 +90,7 @@ type Adapter = CompletionAdapter |
RangeFormattingAdapter |
OnTypeFormattingAdapter |
DefinitionAdapter |
DeclarationAdapter |
ImplementationAdapter |
TypeDefinitionAdapter |
LinkProviderAdapter |
Expand Down Expand Up @@ -248,6 +250,18 @@ export class LanguagesExtImpl implements LanguagesExt {
}
// ### Definition provider end

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

registerDeclarationProvider(selector: theia.DocumentSelector, provider: theia.DeclarationProvider): theia.Disposable {
const callId = this.addNewAdapter(new DeclarationAdapter(provider, this.documents));
this.proxy.$registerDeclarationProvider(callId, this.transformDocumentSelector(selector));
return this.createDisposable(callId);
}
// ### Declaration provider end

// ### Signature help begin
$provideSignatureHelp(
handle: number, resource: UriComponents, position: Position, context: SignatureHelpContext, token: theia.CancellationToken
Expand Down
72 changes: 72 additions & 0 deletions packages/plugin-ext/src/plugin/languages/declaration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/********************************************************************************
* 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 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 '../../common/plugin-api-rpc';
import { Definition, DefinitionLink, Location } from '../../common/plugin-api-rpc-model';
import { isDefinitionLinkArray, isLocationArray } from './util';

export class DeclarationAdapter {

constructor(
private readonly provider: theia.DeclarationProvider,
private readonly documents: DocumentsExtImpl) {
}

provideDeclaration(resource: URI, position: Position, token: theia.CancellationToken): 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.provider.provideDeclaration(document, zeroBasedPosition, token)).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;
}
});
}
}
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 @@ -548,6 +548,9 @@ export function createAPIFactory(
registerDefinitionProvider(selector: theia.DocumentSelector, provider: theia.DefinitionProvider): theia.Disposable {
return languagesExt.registerDefinitionProvider(selector, provider);
},
registerDeclarationProvider(selector: theia.DocumentSelector, provider: theia.DeclarationProvider): theia.Disposable {
return languagesExt.registerDeclarationProvider(selector, provider);
},
registerSignatureHelpProvider(
selector: theia.DocumentSelector, provider: theia.SignatureHelpProvider, first?: string | theia.SignatureHelpProviderMetadata, ...remaining: string[]
): theia.Disposable {
Expand Down
21 changes: 21 additions & 0 deletions packages/plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,27 @@ function provideDefinitionHandler(document: theia.TextDocument, position: theia.
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.
#### Declaration provider
It is possible to provide a declaration for a symbol from within a plugin.
To do this one may register corresponding provider. For example:
```typescript
const documentsSelector: theia.DocumentSelector = { scheme: 'file', language: 'typescript' };
const handler: theia.DeclarationProvider = { provideDeclaration: provideDeclarationHandler };

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

...

function provideDeclarationHandler(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 Declaration` command.
It is possible to return a few sources, but for most cases only one is enough. Return `undefined` to provide nothing.
#### Implementation provider
It is possible to provide implementation source for a symbol from within plugin.
Expand Down
31 changes: 31 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,24 @@ declare module '@theia/plugin' {
provideDefinition(document: TextDocument, position: Position, token: CancellationToken | undefined): ProviderResult<Definition | DefinitionLink[]>;
}

/**
* The declaration provider interface defines the contract between extensions and
* the [go to declaration](https://code.visualstudio.com/api/references/vscode-api#DeclarationProvider)
* feature.
*/
export interface DeclarationProvider {
/**
* Provide the declaration of the symbol at the given position and document.
*
* @param document The document in which the command was invoked.
* @param position The position at which the command was invoked.
* @param token A cancellation token.
* @return A declaration or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
provideDeclaration(document: TextDocument, position: Position, token: CancellationToken | undefined): ProviderResult<Definition | DefinitionLink[]>;
}

/**
* The implementation provider interface defines the contract between extensions and
* the go to implementation feature.
Expand Down Expand Up @@ -6825,6 +6843,19 @@ declare module '@theia/plugin' {
*/
export function registerDefinitionProvider(selector: DocumentSelector, provider: DefinitionProvider): Disposable;

/**
* Register a declaration provider.
*
* Multiple providers can be registered for a language. In that case providers are asked in
* parallel and the results are merged. A failing provider (rejected promise or exception) will
* not cause a failure of the whole operation.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider A declaration provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerDeclarationProvider(selector: DocumentSelector, provider: DeclarationProvider): Disposable;

/**
* Register a signature help provider.
*
Expand Down

0 comments on commit 4360a39

Please sign in to comment.