Skip to content

Commit

Permalink
eclipse-theia#10027 Support EvaluatableExpressions
Browse files Browse the repository at this point in the history
- Implement support for plugins providing evalutable epressions
- Update the debug hover widget to consume evaluatable expressions from the registered providers. Keep the former implementation of guessing the expression from the current line as fallback.

Contributed on behalf of STMicroelectronics

Signed-off-by: Nina Doschek <ndoschek@eclipsesource.com>

Fixes eclipse-theia#10027
  • Loading branch information
ndoschek committed Jul 29, 2022
1 parent 4e6b739 commit 49eea2c
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
- [plugin] removed `Plugin: Deploy Plugin by Id` command [#11417](https://github.com/eclipse-theia/theia/pull/11417)
- [plugin-ext] `CodeEditorWidgetUtil` moved to `packages/plugin-ext/src/main/browser/menus/vscode-theia-menu-mappings.ts`. `MenusContributionPointHandler` extensively refactored. See PR description for details. [#11290](https://github.com/eclipse-theia/theia/pull/11290)
- [plugin] added support for `DebugProtocolBreakpoint` and `DebugProtocolSource` [#10011](https://github.com/eclipse-theia/theia/issues/10011) - Contributed on behalf of STMicroelectronics
- [plugin] added support for `EvaluatableExpressions` [#tbd](https://github.com/eclipse-theia/theia/pull/tbd) - Contributed on behalf of STMicroelectronics
- [plugin-ext] `LocalFilePluginDeployerResolver` moved to `plugin-ext` `local-vsix-file-plugin-deployer-resolver.ts`. [#11466](https://github.com/eclipse-theia/theia/issues/11466)
- [vsx-registry] removed `downloadPath` field from `VSXExtensionResolver`. Plugins are now placed directly in user plugin directory. [#11466](https://github.com/eclipse-theia/theia/issues/11466)

## v1.27.0 - 6/30/2022

- [core] added better styling for active sidepanel borders [#11330](https://github.com/eclipse-theia/theia/pull/11330)
Expand Down
41 changes: 40 additions & 1 deletion packages/debug/src/browser/editor/debug-hover-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ 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';

export interface ShowDebugHoverOptions {
selection: monaco.Range
Expand Down Expand Up @@ -146,6 +150,8 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
}

protected async doShow(options: ShowDebugHoverOptions | undefined = this.options): Promise<void> {
const cancellationSource = new CancellationTokenSource();

if (!this.isEditorFrame()) {
this.hide();
return;
Expand All @@ -162,7 +168,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)).then(exp => exp)
);

const results = await Promise.all(promises).then(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;
Expand Down Expand Up @@ -241,3 +273,10 @@ export class DebugHoverWidget extends SourceTreeWidget implements monaco.editor.
}

}

/**
* @returns New array with all falsy values removed. The original array IS NOT modified.
*/
export function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
return <T[]>array.filter(e => !!e);
}
10 changes: 10 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 @@ -255,6 +255,16 @@ export interface HoverProvider {
provideHover(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Hover | undefined | Thenable<Hover | undefined>;
}

export interface EvaluatableExpression {
range: Range;
expression?: string;
}

export interface EvaluatableExpressionProvider {
provideEvaluatableExpression(model: monaco.editor.ITextModel, position: monaco.Position,
token: monaco.CancellationToken): EvaluatableExpression | undefined | Thenable<EvaluatableExpression | undefined>;
}

export enum DocumentHighlightKind {
Text = 0,
Read = 1,
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 @@ -41,6 +41,7 @@ import {
MarkerData,
SignatureHelp,
Hover,
EvaluatableExpression,
DocumentHighlight,
FormattingOptions,
ChainedCacheId,
Expand Down Expand Up @@ -1463,6 +1464,7 @@ export interface LanguagesExt {
): Promise<SignatureHelp | undefined>;
$releaseSignatureHelp(handle: number, id: number): void;
$provideHover(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<Hover | undefined>;
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<EvaluatableExpression | undefined>;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<DocumentHighlight[] | undefined>;
$provideDocumentFormattingEdits(handle: number, resource: UriComponents,
options: FormattingOptions, token: CancellationToken): Promise<TextEdit[] | undefined>;
Expand Down Expand Up @@ -1539,6 +1541,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;
Expand Down
24 changes: 23 additions & 1 deletion packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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<EvaluatableExpressionProvider>)
(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<EvaluatableExpression | undefined> {
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<monaco.languages.DocumentHighlightProvider>)
Expand Down
15 changes: 15 additions & 0 deletions packages/plugin-ext/src/plugin/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -100,6 +102,7 @@ import { serializeEnterRules, serializeIndentation, serializeRegExp } from './la
type Adapter = CompletionAdapter |
SignatureHelpAdapter |
HoverAdapter |
EvaluatableExpressionAdapter |
DocumentHighlightAdapter |
DocumentFormattingAdapter |
RangeFormattingAdapter |
Expand Down Expand Up @@ -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<EvaluatableExpression | undefined> {
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));
Expand Down
47 changes: 47 additions & 0 deletions packages/plugin-ext/src/plugin/languages/evaluatable-expression.ts
Original file line number Diff line number Diff line change
@@ -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<EvaluatableExpression | undefined> {
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);
});
}
}
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 @@ -78,6 +78,7 @@ import {
SignatureHelp,
SignatureHelpTriggerKind,
Hover,
EvaluatableExpression,
DocumentHighlightKind,
DocumentHighlight,
DocumentLink,
Expand Down Expand Up @@ -724,6 +725,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));
},
Expand Down Expand Up @@ -984,6 +988,7 @@ export function createAPIFactory(
SignatureHelp,
SignatureHelpTriggerKind,
Hover,
EvaluatableExpression,
DocumentHighlightKind,
DocumentHighlight,
DocumentLink,
Expand Down
7 changes: 7 additions & 0 deletions packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,13 @@ export function fromHover(hover: theia.Hover): model.Hover {
};
}

export function fromEvaluatableExpression(evaluatableExpression: theia.EvaluatableExpression): model.EvaluatableExpression {
return <model.EvaluatableExpression>{
range: fromRange(evaluatableExpression.range),
expression: evaluatableExpression.expression
};
}

export function fromLocation(location: theia.Location): model.Location {
return <model.Location>{
uri: location.uri,
Expand Down
18 changes: 18 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 49eea2c

Please sign in to comment.