From a28d87c4ae606a5204fdec6e627bae32122e5aa0 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Thu, 31 Mar 2022 18:10:55 +0200 Subject: [PATCH] #10027 WIP Support EvaluatableExpressions - Implement support for plugins providing evalutable epressions Contributed on behalf of STMicroelectronics Signed-off-by: Nina Doschek Fixes #10027 --- .../editor/debug-expression-provider.ts | 2 +- .../src/browser/editor/debug-hover-widget.ts | 8 +++- .../src/common/plugin-api-rpc-model.ts | 10 ++++ .../plugin-ext/src/common/plugin-api-rpc.ts | 2 + .../src/main/browser/languages-main.ts | 20 +++++++- packages/plugin-ext/src/plugin/languages.ts | 15 ++++++ .../languages/evaluatable-expression.ts | 32 +++++++++++++ .../plugin-ext/src/plugin/plugin-context.ts | 5 ++ packages/plugin-ext/src/plugin/types-impl.ts | 17 +++++++ .../browser/plugin-metrics-languages-main.ts | 12 +++++ packages/plugin/src/theia.d.ts | 46 +++++++++++++++++++ 11 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 packages/plugin-ext/src/plugin/languages/evaluatable-expression.ts diff --git a/packages/debug/src/browser/editor/debug-expression-provider.ts b/packages/debug/src/browser/editor/debug-expression-provider.ts index a6a020906c353..3d638d0964a0f 100644 --- a/packages/debug/src/browser/editor/debug-expression-provider.ts +++ b/packages/debug/src/browser/editor/debug-expression-provider.ts @@ -26,7 +26,7 @@ import * as monaco from '@theia/monaco-editor-core'; */ @injectable() export class DebugExpressionProvider { - get(model: monaco.editor.IModel, selection: monaco.IRange): string { + get(model: monaco.editor.ITextModel, selection: monaco.IRange): string { const lineContent = model.getLineContent(selection.startLineNumber); const { start, end } = this.getExactExpressionStartAndEnd(lineContent, selection.startColumn, selection.endColumn); return lineContent.substring(start - 1, end); diff --git a/packages/debug/src/browser/editor/debug-hover-widget.ts b/packages/debug/src/browser/editor/debug-hover-widget.ts index fa5e4f208c921..e9fee32ab3eaa 100644 --- a/packages/debug/src/browser/editor/debug-hover-widget.ts +++ b/packages/debug/src/browser/editor/debug-hover-widget.ts @@ -162,7 +162,13 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor. } this.options = options; - const matchingExpression = this.expressionProvider.get(this.editor.getControl().getModel()!, options.selection); + + const textModel = this.editor.getControl().getModel(); + if (!textModel) { + return; + } + + const matchingExpression = this.expressionProvider.get(textModel, options.selection); if (!matchingExpression) { this.hide(); return; 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 e0191427a60e9..a520f2f6973f8 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts @@ -258,6 +258,16 @@ export interface HoverProvider { provideHover(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Hover | undefined | Thenable; } +export interface EvaluatableExpression { + range: Range; + expression?: string; +} + +export interface EvaluatableExpressionProvider { + provideEvaluatableExpression(model: monaco.editor.ITextModel, position: monaco.Position, + token: monaco.CancellationToken): EvaluatableExpression | undefined | Thenable; +} + export enum DocumentHighlightKind { Text = 0, Read = 1, diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index b27e0c632eb50..8863ecd5a5a11 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1428,6 +1428,7 @@ export interface LanguagesExt { ): Promise; $releaseSignatureHelp(handle: number, id: number): void; $provideHover(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise; + $provideEvaluatableExpression(handle: number, resource: UriComponents, position: theia.Position, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: FormattingOptions, token: CancellationToken): Promise; @@ -1502,6 +1503,7 @@ export interface LanguagesMain { $registerReferenceProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void; $registerSignatureHelpProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], metadata: theia.SignatureHelpProviderMetadata): void; $registerHoverProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void; + $registerEvaluatableExpressionProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void; $registerDocumentHighlightProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void; $registerQuickFixProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], codeActionKinds?: string[], documentation?: CodeActionProviderDocumentation): void; $clearDiagnostics(id: string): void; diff --git a/packages/plugin-ext/src/main/browser/languages-main.ts b/packages/plugin-ext/src/main/browser/languages-main.ts index 1297f81bb9f07..93c985f72fddd 100644 --- a/packages/plugin-ext/src/main/browser/languages-main.ts +++ b/packages/plugin-ext/src/main/browser/languages-main.ts @@ -328,7 +328,25 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable { return this.proxy.$provideHover(handle, model.uri, position, token); } - $registerDocumentHighlightProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + $registerEvaluatableExpressionProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + const languageSelector = this.toLanguageSelector(selector); + const evaluatableExpressionProvider = this.createEvaluatableExpressionProvider(handle); + this.register(handle, (theia.languages.registerEvaluatableExpressionProvider as RegistrationFunction) + (languageSelector, evaluatableExpressionProvider)); + } + + protected createEvaluatableExpressionProvider(handle: number): theia.EvaluatableExpressionProvider { + return { + provideEvaluatableExpression: (model, position, token) => this.provideEvaluatableExpression(handle, model, position, token) + }; + } + + protected provideEvaluatableExpression(handle: number, model: monaco.editor.ITextModel, position: theia.Position, + token: monaco.CancellationToken): Promise { + return this.proxy.$provideEvaluatableExpression(handle, model.uri, position, token); + } + + $registerDocumentHighlightProvider(handle: number, _pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { const languageSelector = this.toLanguageSelector(selector); const documentHighlightProvider = this.createDocumentHighlightProvider(handle); this.register(handle, (monaco.languages.registerDocumentHighlightProvider as RegistrationFunction) diff --git a/packages/plugin-ext/src/plugin/languages.ts b/packages/plugin-ext/src/plugin/languages.ts index ebb9b519f9847..cfeb6f1813beb 100644 --- a/packages/plugin-ext/src/plugin/languages.ts +++ b/packages/plugin-ext/src/plugin/languages.ts @@ -93,10 +93,12 @@ import { BinaryBuffer } from '@theia/core/lib/common/buffer'; import { DocumentSemanticTokensAdapter, DocumentRangeSemanticTokensAdapter } from './languages/semantic-highlighting'; import { isReadonlyArray } from '../common/arrays'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { EvaluatableExpressionAdapter } from './languages/evaluatable-expression'; type Adapter = CompletionAdapter | SignatureHelpAdapter | HoverAdapter | + EvaluatableExpressionAdapter | DocumentHighlightAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | @@ -346,6 +348,19 @@ export class LanguagesExtImpl implements LanguagesExt { } // ### Hover Provider end + // ### EvaluatableExpression Provider begin + registerEvaluatableExpressionProvider(selector: theia.DocumentSelector, provider: theia.EvaluatableExpressionProvider, pluginInfo: PluginInfo): theia.Disposable { + const callId = this.addNewAdapter(new EvaluatableExpressionAdapter(provider, this.documents)); + this.proxy.$registerEvaluatableExpressionProvider(callId, pluginInfo, this.transformDocumentSelector(selector)); + return this.createDisposable(callId); + } + + $provideEvaluatableExpression(handle: number, resource: UriComponents, position: theia.Position, + token: theia.CancellationToken): Promise { + return this.withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined); + } + // ### EvaluatableExpression Provider end + // ### Document Highlight Provider begin registerDocumentHighlightProvider(selector: theia.DocumentSelector, provider: theia.DocumentHighlightProvider, pluginInfo: PluginInfo): theia.Disposable { const callId = this.addNewAdapter(new DocumentHighlightAdapter(provider, this.documents)); diff --git a/packages/plugin-ext/src/plugin/languages/evaluatable-expression.ts b/packages/plugin-ext/src/plugin/languages/evaluatable-expression.ts new file mode 100644 index 0000000000000..6c3756f62ae93 --- /dev/null +++ b/packages/plugin-ext/src/plugin/languages/evaluatable-expression.ts @@ -0,0 +1,32 @@ +// ***************************************************************************** +// Copyright (C) 2022 EclipseSource 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 '@theia/core/shared/vscode-uri'; +import * as theia from '@theia/plugin'; +import { DocumentsExtImpl } from '../documents'; + +export class EvaluatableExpressionAdapter { + + constructor( + private readonly provider: theia.EvaluatableExpressionProvider, + private readonly documents: DocumentsExtImpl + ) { } + + async provideEvaluatableExpression(resource: URI, position: theia.Position, token: theia.CancellationToken): Promise { + const document = this.documents.getDocument(resource); + const value = await this.provider.provideEvaluatableExpression(document, position, token); + return value; + } +} diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index e9268d1c2c9b9..4bb9c86ec2dd7 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -78,6 +78,7 @@ import { SignatureHelp, SignatureHelpTriggerKind, Hover, + EvaluatableExpression, DocumentHighlightKind, DocumentHighlight, DocumentLink, @@ -721,6 +722,9 @@ export function createAPIFactory( registerHoverProvider(selector: theia.DocumentSelector, provider: theia.HoverProvider): theia.Disposable { return languagesExt.registerHoverProvider(selector, provider, pluginToPluginInfo(plugin)); }, + registerEvaluatableExpressionProvider(selector: theia.DocumentSelector, provider: theia.EvaluatableExpressionProvider): theia.Disposable { + return languagesExt.registerEvaluatableExpressionProvider(selector, provider, pluginToPluginInfo(plugin)); + }, registerDocumentHighlightProvider(selector: theia.DocumentSelector, provider: theia.DocumentHighlightProvider): theia.Disposable { return languagesExt.registerDocumentHighlightProvider(selector, provider, pluginToPluginInfo(plugin)); }, @@ -971,6 +975,7 @@ export function createAPIFactory( SignatureHelp, SignatureHelpTriggerKind, Hover, + EvaluatableExpression, DocumentHighlightKind, DocumentHighlight, DocumentLink, diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 857824aa2b892..5954acba0a358 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -1104,6 +1104,23 @@ export class Hover { this.range = range; } } +@es5ClassCompat +export class EvaluatableExpression { + + public range: Range; + public expression?: string; + + constructor( + range: Range, + expression?: string + ) { + if (!range) { + illegalArgument('range must be defined'); + } + this.range = range; + this.expression = expression; + } +} export enum DocumentHighlightKind { Text = 0, diff --git a/packages/plugin-metrics/src/browser/plugin-metrics-languages-main.ts b/packages/plugin-metrics/src/browser/plugin-metrics-languages-main.ts index 6cb98831b08bb..8301387dccac2 100644 --- a/packages/plugin-metrics/src/browser/plugin-metrics-languages-main.ts +++ b/packages/plugin-metrics/src/browser/plugin-metrics-languages-main.ts @@ -80,6 +80,13 @@ export class LanguagesMainPluginMetrics extends LanguagesMainImpl { super.provideHover(handle, model, position, token)); } + // protected override provideEvaluatableExpression(handle: number, model: monaco.editor.ITextModel, position: monaco.Position, + // token: monaco.CancellationToken): monaco.languages.ProviderResult { + // return this.pluginMetricsResolver.resolveRequest(this.handleToExtensionName(handle), + // vst.HoverRequest.type.method, + // super.provideEvaluatableExpression(handle, model, position, token)); + // } + protected override provideDocumentHighlights(handle: number, model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): monaco.languages.ProviderResult { return this.pluginMetricsResolver.resolveRequest(this.handleToExtensionName(handle), @@ -254,6 +261,11 @@ export class LanguagesMainPluginMetrics extends LanguagesMainImpl { super.$registerHoverProvider(handle, pluginInfo, selector); } + override $registerEvaluatableExpressionProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.registerPluginWithFeatureHandle(handle, pluginInfo.id); + super.$registerEvaluatableExpressionProvider(handle, pluginInfo, selector); + } + override $registerDocumentHighlightProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { this.registerPluginWithFeatureHandle(handle, pluginInfo.id); super.$registerDocumentHighlightProvider(handle, pluginInfo, selector); diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 0c3b7d3e8720f..037d3c513d72f 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -8913,6 +8913,18 @@ export module '@theia/plugin' { */ export function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable; + /** + * Register a provider that locates evaluatable expressions in text documents. + * The editor will evaluate the expression in the active debug session and will show the result in the debug hover. + * + * If multiple providers are registered for a language an arbitrary provider will be used. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An evaluatable expression provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; + /** * Register a workspace symbol provider. * @@ -9205,6 +9217,40 @@ export module '@theia/plugin' { provideHover(document: TextDocument, position: Position, token: CancellationToken | undefined): ProviderResult; } + /** + * An evaluatable expression represents additional information for an expression in a document. Evaluatable expressions are + * evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget. + * @internal + */ + export interface EvaluatableExpression { + /** + * The range to which this expression applies. + */ + range: Range; + /** + * This expression overrides the expression extracted from the range. + */ + expression?: string; + } + + /** + * The evaluatable expression provider interface defines the contract between extensions and + * the debug hover. + * @internal + */ + export interface EvaluatableExpressionProvider { + /** + * Provide a hover for the given position and document. Multiple hovers at the same + * position will be merged by the editor. A hover can have a range which defaults + * to the word range at the position when omitted. + * + * @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. + */ + provideEvaluatableExpression(document: model.ITextModel, position: theia.Position, token: CancellationToken): ProviderResult; + } + /** * A document highlight kind. */