From 7d751e3dedafad1778620fc5afa767f08a849f99 Mon Sep 17 00:00:00 2001 From: Vladyslav Zhukovskyi Date: Thu, 16 Apr 2020 16:26:06 +0300 Subject: [PATCH] Implementation of SelectionRange and SelectionRangeProvider API Signed-off-by: Vladyslav Zhukovskyi --- .../src/common/plugin-api-rpc-model.ts | 4 + .../plugin-ext/src/common/plugin-api-rpc.ts | 3 + .../src/main/browser/languages-main.ts | 17 ++++ packages/plugin-ext/src/plugin/languages.ts | 13 +++ .../src/plugin/languages/selection-range.ts | 79 +++++++++++++++++++ .../plugin-ext/src/plugin/plugin-context.ts | 5 ++ .../plugin-ext/src/plugin/type-converters.ts | 4 + packages/plugin-ext/src/plugin/types-impl.ts | 15 ++++ packages/plugin/src/theia.d.ts | 55 +++++++++++++ 9 files changed, 195 insertions(+) create mode 100644 packages/plugin-ext/src/plugin/languages/selection-range.ts diff --git a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts index 1a2772fbb9fe7..e9cab4a5b53cc 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts @@ -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; diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index c090dcf57dfab..5cc683f08d2d9 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -65,6 +65,7 @@ import { CodeActionContext, FoldingContext, FoldingRange, + SelectionRange, CallHierarchyDefinition, CallHierarchyReference } from './plugin-api-rpc-model'; @@ -1196,6 +1197,7 @@ export interface LanguagesExt { context: FoldingContext, token: CancellationToken ): PromiseLike; + $provideSelectionRanges(handle: number, resource: UriComponents, positions: Position[], token: CancellationToken): PromiseLike; $provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): PromiseLike; $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: RawColorInfo, token: CancellationToken): PromiseLike; $provideRenameEdits(handle: number, resource: UriComponents, position: Position, newName: string, token: CancellationToken): PromiseLike; @@ -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; diff --git a/packages/plugin-ext/src/main/browser/languages-main.ts b/packages/plugin-ext/src/main/browser/languages-main.ts index 697abb88a3152..58b34bc7ac60d 100644 --- a/packages/plugin-ext/src/main/browser/languages-main.ts +++ b/packages/plugin-ext/src/main/browser/languages-main.ts @@ -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 { + 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); diff --git a/packages/plugin-ext/src/plugin/languages.ts b/packages/plugin-ext/src/plugin/languages.ts index c71982f50c402..aaa58fe4a9d25 100644 --- a/packages/plugin-ext/src/plugin/languages.ts +++ b/packages/plugin-ext/src/plugin/languages.ts @@ -58,6 +58,7 @@ import { CodeActionContext, CodeAction, FoldingRange, + SelectionRange, CallHierarchyDefinition, CallHierarchyReference } from '../common/plugin-api-rpc-model'; @@ -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'; @@ -106,6 +108,7 @@ type Adapter = CompletionAdapter | ReferenceAdapter | WorkspaceSymbolAdapter | FoldingProviderAdapter | + SelectionRangeProviderAdapter | ColorProviderAdapter | RenameAdapter | CallHierarchyAdapter; @@ -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 { + 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)); diff --git a/packages/plugin-ext/src/plugin/languages/selection-range.ts b/packages/plugin-ext/src/plugin/languages/selection-range.ts new file mode 100644 index 0000000000000..6d469570bd9e1 --- /dev/null +++ b/packages/plugin-ext/src/plugin/languages/selection-range.ts @@ -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 { + 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'); + } + oneResult.push(Converter.fromSelectionRange(selectionRange)); + if (!selectionRange.parent) { + break; + } + last = selectionRange.range; + selectionRange = selectionRange.parent; + } + } + return allResults; + }); + } +} diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 51f07cf6d0ea6..2680682212acc 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -104,6 +104,7 @@ import { FunctionBreakpoint, FoldingRange, FoldingRangeKind, + SelectionRange, Color, ColorInformation, ColorPresentation, @@ -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)); }, @@ -855,6 +859,7 @@ export function createAPIFactory( ColorInformation, ColorPresentation, FoldingRange, + SelectionRange, FoldingRangeKind, OperatingSystem, WebviewPanelTargetArea, diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index c17314a1d751b..90e628a34db8c 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -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, diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 9b561a6de4d5f..52110f462448e 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -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. */ diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index fd95e892ae9cc..6165fe32848e3 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -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. * @@ -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; + } + /** * Represents programming constructs like functions or constructors in the context * of call hierarchy.