diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4de150e16915e..c737bf21ff8bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
## v1.29.0 - unreleased
+- [plugin] added support for `EvaluatableExpressions` [#11484](https://github.com/eclipse-theia/theia/pull/11484) - Contributed on behalf of STMicroelectronics
+
[Breaking Changes:](#breaking_changes_1.29.0)
- [core] `updateThemePreference` and `updateThemeFromPreference` removed from `CommonFrontendContribution`. Corresponding functionality as been moved to the respective theme service. `load` removed from `IconThemeService` [#11473](https://github.com/eclipse-theia/theia/issues/11473)
diff --git a/packages/core/src/common/types.ts b/packages/core/src/common/types.ts
index cdabbeb69a96c..a01d1c4bef275 100644
--- a/packages/core/src/common/types.ts
+++ b/packages/core/src/common/types.ts
@@ -162,6 +162,13 @@ export namespace ArrayUtils {
}
return -(low + 1);
}
+
+ /**
+ * @returns New array with all falsy values removed. The original array IS NOT modified.
+ */
+ export function coalesce(array: ReadonlyArray): T[] {
+ return array.filter(e => !!e);
+ }
}
/**
diff --git a/packages/debug/src/browser/editor/debug-hover-widget.ts b/packages/debug/src/browser/editor/debug-hover-widget.ts
index fa5e4f208c921..ea1d1c2f87d67 100644
--- a/packages/debug/src/browser/editor/debug-hover-widget.ts
+++ b/packages/debug/src/browser/editor/debug-hover-widget.ts
@@ -28,6 +28,11 @@ import { DebugExpressionProvider } from './debug-expression-provider';
import { DebugHoverSource } from './debug-hover-source';
import { DebugVariable } from '../console/debug-console-items';
import * as monaco from '@theia/monaco-editor-core';
+import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
+import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
+import { CancellationTokenSource } from '@theia/monaco-editor-core/esm/vs/base/common/cancellation';
+import { Position } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position';
+import { ArrayUtils } from '@theia/core';
export interface ShowDebugHoverOptions {
selection: monaco.Range
@@ -146,6 +151,8 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
}
protected async doShow(options: ShowDebugHoverOptions | undefined = this.options): Promise {
+ const cancellationSource = new CancellationTokenSource();
+
if (!this.isEditorFrame()) {
this.hide();
return;
@@ -162,7 +169,33 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
}
this.options = options;
- const matchingExpression = this.expressionProvider.get(this.editor.getControl().getModel()!, options.selection);
+ let matchingExpression: string | undefined;
+
+ const pluginExpressionProvider = StandaloneServices.get(ILanguageFeaturesService).evaluatableExpressionProvider;
+ const textEditorModel = this.editor.document.textEditorModel;
+
+ if (pluginExpressionProvider && pluginExpressionProvider.has(textEditorModel)) {
+ const registeredProviders = pluginExpressionProvider.ordered(textEditorModel);
+ const position = new Position(this.options!.selection.startLineNumber, this.options!.selection.startColumn);
+
+ const promises = registeredProviders.map(support =>
+ Promise.resolve(support.provideEvaluatableExpression(textEditorModel, position, cancellationSource.token))
+ );
+
+ const results = await Promise.all(promises).then(ArrayUtils.coalesce);
+ if (results.length > 0) {
+ matchingExpression = results[0].expression;
+ const range = results[0].range;
+
+ if (!matchingExpression) {
+ const lineContent = textEditorModel.getLineContent(position.lineNumber);
+ matchingExpression = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
+ }
+ }
+ } else { // use fallback if no provider was registered
+ matchingExpression = this.expressionProvider.get(this.editor.getControl().getModel()!, 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 681247c2a1310..976bf2cfc25fe 100644
--- a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts
+++ b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts
@@ -255,6 +255,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 8754dcee861ce..7d7311488686b 100644
--- a/packages/plugin-ext/src/common/plugin-api-rpc.ts
+++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts
@@ -41,6 +41,7 @@ import {
MarkerData,
SignatureHelp,
Hover,
+ EvaluatableExpression,
DocumentHighlight,
FormattingOptions,
ChainedCacheId,
@@ -1470,6 +1471,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: Position, token: CancellationToken): Promise;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise;
$provideDocumentFormattingEdits(handle: number, resource: UriComponents,
options: FormattingOptions, token: CancellationToken): Promise;
@@ -1546,6 +1548,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 2b7438967ab26..078d4e20d668d 100644
--- a/packages/plugin-ext/src/main/browser/languages-main.ts
+++ b/packages/plugin-ext/src/main/browser/languages-main.ts
@@ -66,6 +66,9 @@ import * as MonacoPath from '@theia/monaco-editor-core/esm/vs/base/common/path';
import { IRelativePattern } from '@theia/monaco-editor-core/esm/vs/base/common/glob';
import { EditorLanguageStatusService, LanguageStatus as EditorLanguageStatus } from '@theia/editor/lib/browser/language-status/editor-language-status-service';
import { LanguageSelector, RelativePattern } from '@theia/editor/lib/common/language-selector';
+import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
+import { EvaluatableExpression, EvaluatableExpressionProvider } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
+import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
/**
* @monaco-uplift The public API declares these functions as (languageId: string, service).
@@ -338,7 +341,26 @@ 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,
+ (StandaloneServices.get(ILanguageFeaturesService).evaluatableExpressionProvider.register as RegistrationFunction)
+ (languageSelector, evaluatableExpressionProvider));
+ }
+
+ protected createEvaluatableExpressionProvider(handle: number): EvaluatableExpressionProvider {
+ return {
+ provideEvaluatableExpression: (model, position, token) => this.provideEvaluatableExpression(handle, model, position, token)
+ };
+ }
+
+ protected provideEvaluatableExpression(handle: number, model: ITextModel, position: monaco.Position,
+ token: monaco.CancellationToken): monaco.languages.ProviderResult {
+ 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 3e80545fbe764..c438d85eb924e 100644
--- a/packages/plugin-ext/src/plugin/languages.ts
+++ b/packages/plugin-ext/src/plugin/languages.ts
@@ -62,11 +62,13 @@ import {
CallHierarchyIncomingCall,
CallHierarchyOutgoingCall,
LinkedEditingRanges,
+ EvaluatableExpression
} from '../common/plugin-api-rpc-model';
import { CompletionAdapter } from './languages/completion';
import { Diagnostics } from './languages/diagnostics';
import { SignatureHelpAdapter } from './languages/signature';
import { HoverAdapter } from './languages/hover';
+import { EvaluatableExpressionAdapter } from './languages/evaluatable-expression';
import { DocumentHighlightAdapter } from './languages/document-highlight';
import { DocumentFormattingAdapter } from './languages/document-formatting';
import { RangeFormattingAdapter } from './languages/range-formatting';
@@ -100,6 +102,7 @@ import { serializeEnterRules, serializeIndentation, serializeRegExp } from './la
type Adapter = CompletionAdapter |
SignatureHelpAdapter |
HoverAdapter |
+ EvaluatableExpressionAdapter |
DocumentHighlightAdapter |
DocumentFormattingAdapter |
RangeFormattingAdapter |
@@ -350,6 +353,18 @@ 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: 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..ea7fb6f643b3f
--- /dev/null
+++ b/packages/plugin-ext/src/plugin/languages/evaluatable-expression.ts
@@ -0,0 +1,47 @@
+// *****************************************************************************
+// Copyright (C) 2022 STMicroelectronics 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 { Position } from '../../common/plugin-api-rpc';
+import { EvaluatableExpression } from '../../common/plugin-api-rpc-model';
+import { DocumentsExtImpl } from '../documents';
+import * as Converter from '../type-converters';
+
+export class EvaluatableExpressionAdapter {
+
+ constructor(
+ private readonly provider: theia.EvaluatableExpressionProvider,
+ private readonly documents: DocumentsExtImpl
+ ) { }
+
+ async provideEvaluatableExpression(resource: URI, position: Position, token: theia.CancellationToken): Promise {
+ const documentData = this.documents.getDocumentData(resource);
+ if (!documentData) {
+ return Promise.reject(new Error(`There is no document data for ${resource}`));
+ }
+
+ const document = documentData.document;
+ const pos = Converter.toPosition(position);
+
+ return Promise.resolve(this.provider.provideEvaluatableExpression(document, pos, token)).then(expression => {
+ if (!expression) {
+ return undefined;
+ }
+ return Converter.fromEvaluatableExpression(expression);
+ });
+ }
+}
diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts
index a5cf1e505b1b7..00fddb1f7c806 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,
@@ -731,6 +732,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));
},
@@ -991,6 +995,7 @@ export function createAPIFactory(
SignatureHelp,
SignatureHelpTriggerKind,
Hover,
+ EvaluatableExpression,
DocumentHighlightKind,
DocumentHighlight,
DocumentLink,
diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts
index ae400b0db32f9..3d0813c496542 100644
--- a/packages/plugin-ext/src/plugin/type-converters.ts
+++ b/packages/plugin-ext/src/plugin/type-converters.ts
@@ -396,6 +396,13 @@ export function fromHover(hover: theia.Hover): model.Hover {
};
}
+export function fromEvaluatableExpression(evaluatableExpression: theia.EvaluatableExpression): model.EvaluatableExpression {
+ return {
+ range: fromRange(evaluatableExpression.range),
+ expression: evaluatableExpression.expression
+ };
+}
+
export function fromLocation(location: theia.Location): model.Location {
return {
uri: location.uri,
diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts
index 5ababe64d05ac..979670be308b5 100644
--- a/packages/plugin-ext/src/plugin/types-impl.ts
+++ b/packages/plugin-ext/src/plugin/types-impl.ts
@@ -1119,6 +1119,24 @@ export class Hover {
}
}
+@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,
Read = 1,
diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts
index 5fccb1e109bf1..713f8276bf8ef 100644
--- a/packages/plugin/src/theia.d.ts
+++ b/packages/plugin/src/theia.d.ts
@@ -9269,6 +9269,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.
*
@@ -9582,6 +9594,54 @@ export module '@theia/plugin' {
provideHover(document: TextDocument, position: Position, token: CancellationToken | undefined): ProviderResult;
}
+ /**
+ * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime.
+ * The result of this evaluation is shown in a tooltip-like widget.
+ * If only a range is specified, the expression will be extracted from the underlying document.
+ * An optional expression can be used to override the extracted expression.
+ * In this case the range is still used to highlight the range in the document.
+ */
+ export class EvaluatableExpression {
+
+ /*
+ * The range is used to extract the evaluatable expression from the underlying document and to highlight it.
+ */
+ readonly range: Range;
+
+ /*
+ * If specified the expression overrides the extracted expression.
+ */
+ readonly expression?: string | undefined;
+
+ /**
+ * Creates a new evaluatable expression object.
+ *
+ * @param range The range in the underlying document from which the evaluatable expression is extracted.
+ * @param expression If specified overrides the extracted expression.
+ */
+ constructor(range: Range, expression?: string);
+ }
+
+ /**
+ * The evaluatable expression provider interface defines the contract between extensions and
+ * the debug hover. In this contract the provider returns an evaluatable expression for a given position
+ * in a document and the editor evaluates this expression in the active debug session and shows the result in a debug hover.
+ */
+ export interface EvaluatableExpressionProvider {
+ /**
+ * Provide an evaluatable expression for the given document and position.
+ * The editor will evaluate this expression in the active debug session and will show the result in the debug hover.
+ * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
+ *
+ * @param document The document for which the debug hover is about to appear.
+ * @param position The line and character position in the document where the debug hover is about to appear.
+ * @param token A cancellation token.
+ * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be
+ * signaled by returning `undefined` or `null`.
+ */
+ provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken | undefined): ProviderResult;
+ }
+
/**
* A document highlight kind.
*/