Skip to content

Commit

Permalink
Implementation of SelectionRange and SelectionRangeProvider API
Browse files Browse the repository at this point in the history
Signed-off-by: Vladyslav Zhukovskyi <vzhukovs@redhat.com>
  • Loading branch information
vzhukovs committed Apr 17, 2020
1 parent 7217b4f commit ebf7b78
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 0 deletions.
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 @@ -471,6 +471,10 @@ export class FoldingRangeKind {
public constructor(public value: string) { }
}

export interface SelectionRange {
range: Range;
}

export interface Color {
readonly red: number;
readonly green: number;
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
CodeActionContext,
FoldingContext,
FoldingRange,
SelectionRange,
CallHierarchyDefinition,
CallHierarchyReference
} from './plugin-api-rpc-model';
Expand Down Expand Up @@ -1196,6 +1197,7 @@ export interface LanguagesExt {
context: FoldingContext,
token: CancellationToken
): PromiseLike<FoldingRange[] | undefined>;
$provideSelectionRanges(handle: number, resource: UriComponents, positions: Position[], token: CancellationToken): PromiseLike<SelectionRange[][]>;
$provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): PromiseLike<RawColorInfo[]>;
$provideColorPresentations(handle: number, resource: UriComponents, colorInfo: RawColorInfo, token: CancellationToken): PromiseLike<ColorPresentation[]>;
$provideRenameEdits(handle: number, resource: UriComponents, position: Position, newName: string, token: CancellationToken): PromiseLike<WorkspaceEditDto | undefined>;
Expand Down Expand Up @@ -1241,6 +1243,7 @@ export interface LanguagesMain {
$registerOutlineSupport(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void;
$registerWorkspaceSymbolProvider(handle: number, pluginInfo: PluginInfo): void;
$registerFoldingRangeProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void;
$registerSelectionRangeProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void;
$registerDocumentColorProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void;
$registerRenameProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], supportsResolveInitialValues: boolean): void;
$registerCallHierarchyProvider(handle: number, selector: SerializedDocumentFilter[]): void;
Expand Down
17 changes: 17 additions & 0 deletions packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,23 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
return this.proxy.$provideFoldingRange(handle, model.uri, context, token);
}

$registerSelectionRangeProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void {
const languageSelector = fromLanguageSelector(selector);
const provider = this.createSelectionRangeProvider(handle);
this.register(handle, monaco.languages.registerSelectionRangeProvider(languageSelector, provider));
}

protected createSelectionRangeProvider(handle: number): monaco.languages.SelectionRangeProvider {
return {
provideSelectionRanges: (model, positions, token) => this.provideSelectionRanges(handle, model, positions, token)
};
}

protected provideSelectionRanges(handle: number, model: monaco.editor.ITextModel,
positions: monaco.Position[], token: monaco.CancellationToken): monaco.languages.ProviderResult<monaco.languages.SelectionRange[][]> {
return this.proxy.$provideSelectionRanges(handle, model.uri, positions, token);
}

$registerDocumentColorProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void {
const languageSelector = fromLanguageSelector(selector);
const colorProvider = this.createColorProvider(handle);
Expand Down
13 changes: 13 additions & 0 deletions packages/plugin-ext/src/plugin/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
CodeActionContext,
CodeAction,
FoldingRange,
SelectionRange,
CallHierarchyDefinition,
CallHierarchyReference
} from '../common/plugin-api-rpc-model';
Expand All @@ -80,6 +81,7 @@ import { ReferenceAdapter } from './languages/reference';
import { WorkspaceSymbolAdapter } from './languages/workspace-symbol';
import { SymbolInformation } from 'vscode-languageserver-types';
import { FoldingProviderAdapter } from './languages/folding';
import { SelectionRangeProviderAdapter } from './languages/selection-range';
import { ColorProviderAdapter } from './languages/color';
import { RenameAdapter } from './languages/rename';
import { Event } from '@theia/core/lib/common/event';
Expand All @@ -106,6 +108,7 @@ type Adapter = CompletionAdapter |
ReferenceAdapter |
WorkspaceSymbolAdapter |
FoldingProviderAdapter |
SelectionRangeProviderAdapter |
ColorProviderAdapter |
RenameAdapter |
CallHierarchyAdapter;
Expand Down Expand Up @@ -544,6 +547,16 @@ export class LanguagesExtImpl implements LanguagesExt {
}
// ### Folding Range Provider end

registerSelectionRangeProvider(selector: theia.DocumentSelector, provider: theia.SelectionRangeProvider, pluginInfo: PluginInfo): theia.Disposable {
const callId = this.addNewAdapter(new SelectionRangeProviderAdapter(provider, this.documents));
this.proxy.$registerSelectionRangeProvider(callId, pluginInfo, this.transformDocumentSelector(selector));
return this.createDisposable(callId);
}

$provideSelectionRanges(handle: number, resource: UriComponents, positions: Position[], token: theia.CancellationToken): Promise<SelectionRange[][]> {
return this.withAdapter(handle, SelectionRangeProviderAdapter, adapter => adapter.provideSelectionRanges(URI.revive(resource), positions, token), []);
}

// ### Rename Provider begin
registerRenameProvider(selector: theia.DocumentSelector, provider: theia.RenameProvider, pluginInfo: PluginInfo): theia.Disposable {
const callId = this.addNewAdapter(new RenameAdapter(provider, this.documents));
Expand Down
79 changes: 79 additions & 0 deletions packages/plugin-ext/src/plugin/languages/selection-range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/********************************************************************************
* Copyright (C) 2020 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
********************************************************************************/
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// copied and modified from https://github.com/microsoft/vscode/blob/standalone/0.19.x/src/vs/workbench/api/common/extHostLanguageFeatures.ts#L1107-L1151

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

export class SelectionRangeProviderAdapter {

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

provideSelectionRanges(resource: URI, position: monaco.IPosition[], token: theia.CancellationToken): Promise<model.SelectionRange[][]> {
const documentData = this.documents.getDocumentData(resource);

if (!documentData) {
return Promise.reject(new Error(`There are no document for ${resource}`));
}

const document = documentData.document;
const positions = position.map(pos => Converter.toPosition(pos));

return Promise.resolve(this.provider.provideSelectionRanges(document, positions, token)).then(allProviderRanges => {
if (!Array.isArray(allProviderRanges) || allProviderRanges.length === 0) {
return [];
}

if (allProviderRanges.length !== positions.length) {
return [];
}

const allResults: model.SelectionRange[][] = [];
for (let i = 0; i < positions.length; i++) {
const oneResult: model.SelectionRange[] = [];
allResults.push(oneResult);

let last: types.Position | theia.Range = positions[i];
let selectionRange = allProviderRanges[i];

while (true) {
if (!selectionRange.range.contains(last)) {
return Promise.reject('INVALID selection range, must contain the previous range');

This comment has been minimized.

Copy link
@kittaakos

kittaakos Apr 17, 2020

Contributor

@vzhukovskii, it's better to reject with an error, instead of a string. See related PR: #4684

For the sake of quality; can you please submit a follow-up change?

This comment has been minimized.

Copy link
@akosyakov

akosyakov Apr 17, 2020

Member

@vzhukovskii I thought we supposed to copy it from https://github.com/microsoft/vscode/blob/4bbae4b7d81ecff78ba65ddc8227b542e734257e/src/vs/workbench/api/common/extHostLanguageFeatures.ts#L1138 without too many modifications? Please do a follow-up PR it is important to keep code as close as possible.

@vrubezhny sorry for referencing

This comment has been minimized.

Copy link
@vzhukovs

vzhukovs Apr 17, 2020

Author Contributor

Okay, will provide another PR for that. My fault. Missed to wrap it into Error object.

This comment has been minimized.

Copy link
@vzhukovs

vzhukovs Apr 17, 2020

Author Contributor

@kittaakos here is the PR: #7599

}
oneResult.push(Converter.fromSelectionRange(selectionRange));
if (!selectionRange.parent) {
break;
}
last = selectionRange.range;
selectionRange = selectionRange.parent;
}
}
return allResults;
});
}
}
5 changes: 5 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import {
FunctionBreakpoint,
FoldingRange,
FoldingRangeKind,
SelectionRange,
Color,
ColorInformation,
ColorPresentation,
Expand Down Expand Up @@ -626,6 +627,9 @@ export function createAPIFactory(
registerFoldingRangeProvider(selector: theia.DocumentSelector, provider: theia.FoldingRangeProvider): theia.Disposable {
return languagesExt.registerFoldingRangeProvider(selector, provider, pluginToPluginInfo(plugin));
},
registerSelectionRangeProvider(selector: theia.DocumentSelector, provider: theia.SelectionRangeProvider): theia.Disposable {
return languagesExt.registerSelectionRangeProvider(selector, provider, pluginToPluginInfo(plugin));
},
registerRenameProvider(selector: theia.DocumentSelector, provider: theia.RenameProvider): theia.Disposable {
return languagesExt.registerRenameProvider(selector, provider, pluginToPluginInfo(plugin));
},
Expand Down Expand Up @@ -855,6 +859,7 @@ export function createAPIFactory(
ColorInformation,
ColorPresentation,
FoldingRange,
SelectionRange,
FoldingRangeKind,
OperatingSystem,
WebviewPanelTargetArea,
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,10 @@ export function toSymbolInformation(symbolInformation: SymbolInformation): theia
};
}

export function fromSelectionRange(selectionRange: theia.SelectionRange): model.SelectionRange {
return { range: fromRange(selectionRange.range) };
}

export function fromFoldingRange(foldingRange: theia.FoldingRange): model.FoldingRange {
const range: model.FoldingRange = {
start: foldingRange.start + 1,
Expand Down
15 changes: 15 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2001,6 +2001,21 @@ export enum FoldingRangeKind {
Region = 3
}

export class SelectionRange {

range: Range;
parent?: SelectionRange;

constructor(range: Range, parent?: SelectionRange) {
this.range = range;
this.parent = parent;

if (parent && !parent.range.contains(this.range)) {
throw new Error('Invalid argument: parent must contain this range');
}
}
}

/**
* Enumeration of the supported operating systems.
*/
Expand Down
55 changes: 55 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7288,6 +7288,19 @@ declare module '@theia/plugin' {
*/
export function registerFoldingRangeProvider(selector: DocumentSelector, provider: FoldingRangeProvider): Disposable;

/**
* Register a selection range 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 selection range provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerSelectionRangeProvider(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable;

/**
* Register a reference provider.
*
Expand Down Expand Up @@ -9040,6 +9053,48 @@ declare module '@theia/plugin' {
export function createCommentController(id: string, label: string): CommentController;
}

/**
* A selection range represents a part of a selection hierarchy. A selection range
* may have a parent selection range that contains it.
*/
export class SelectionRange {

/**
* The [range](#Range) of this selection range.
*/
range: Range;

/**
* The parent selection range containing this range.
*/
parent?: SelectionRange;

/**
* Creates a new selection range.
*
* @param range The range of the selection range.
* @param parent The parent of the selection range.
*/
constructor(range: Range, parent?: SelectionRange);
}

export interface SelectionRangeProvider {
/**
* Provide selection ranges for the given positions.
*
* Selection ranges should be computed individually and independent for each position. The editor will merge
* and deduplicate ranges but providers must return hierarchies of selection ranges so that a range
* is [contained](#Range.contains) by its parent.
*
* @param document The document in which the command was invoked.
* @param positions The positions at which the command was invoked.
* @param token A cancellation token.
* @return Selection ranges or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
provideSelectionRanges(document: TextDocument, positions: Position[], token: CancellationToken): ProviderResult<SelectionRange[]>;
}

/**
* Represents programming constructs like functions or constructors in the context
* of call hierarchy.
Expand Down

0 comments on commit ebf7b78

Please sign in to comment.