diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5a32f08c1497..0e81a87b4442f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@
- [Previous Changelogs](https://github.com/eclipse-theia/theia/tree/master/doc/changelogs/)
## v1.31.0
+
+- [plugin] added support for the `InlineValues` feature [#11729](https://github.com/eclipse-theia/theia/pull/11729) - Contributed on behalf of STMicroelectronics
+
[Breaking Changes:](#breaking_changes_1.31.0)
- [core] the generated webpack configuration (`gen-webpack.config.js`) now exports an array of two webpack configs instead of a single one: the first contains the config for
diff --git a/packages/debug/src/browser/editor/debug-inline-value-decorator.ts b/packages/debug/src/browser/editor/debug-inline-value-decorator.ts
index a559163f56aeb..b3bfeb0b78874 100644
--- a/packages/debug/src/browser/editor/debug-inline-value-decorator.ts
+++ b/packages/debug/src/browser/editor/debug-inline-value-decorator.ts
@@ -20,18 +20,21 @@
*--------------------------------------------------------------------------------------------*/
// Based on https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts
+import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { inject, injectable } from '@theia/core/shared/inversify';
import * as monaco from '@theia/monaco-editor-core';
+import { CancellationTokenSource } from '@theia/monaco-editor-core/esm/vs/base/common/cancellation';
+import { DEFAULT_WORD_REGEXP } from '@theia/monaco-editor-core/esm/vs/editor/common/core/wordHelper';
import { IDecorationOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/editorCommon';
+import { InlineValueContext, StandardTokenType } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
-import { StandardTokenType } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
-import { DEFAULT_WORD_REGEXP } from '@theia/monaco-editor-core/esm/vs/editor/common/core/wordHelper';
-import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
+import { ILanguageFeaturesService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/languageFeatures';
+import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
-import { ExpressionContainer, DebugVariable } from '../console/debug-console-items';
+import { DebugVariable, ExpressionContainer, ExpressionItem } from '../console/debug-console-items';
import { DebugPreferences } from '../debug-preferences';
-import { DebugEditorModel } from './debug-editor-model';
import { DebugStackFrame } from '../model/debug-stack-frame';
+import { DebugEditorModel } from './debug-editor-model';
// https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts#L40-L43
export const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration';
@@ -48,6 +51,11 @@ const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline value
// https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/base/common/uint.ts#L7-L13
const MAX_SAFE_SMALL_INTEGER = 1 << 30;
+class InlineSegment {
+ constructor(public column: number, public text: string) {
+ }
+}
+
@injectable()
export class DebugInlineValueDecorator implements FrontendApplicationContribution {
@@ -73,11 +81,12 @@ export class DebugInlineValueDecorator implements FrontendApplicationContributio
async calculateDecorations(debugEditorModel: DebugEditorModel, stackFrame: DebugStackFrame | undefined): Promise {
this.wordToLineNumbersMap = undefined;
const model = debugEditorModel.editor.getControl().getModel() || undefined;
- return this.updateInlineValueDecorations(model, stackFrame);
+ return this.updateInlineValueDecorations(debugEditorModel, model, stackFrame);
}
// https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts#L382-L408
protected async updateInlineValueDecorations(
+ debugEditorModel: DebugEditorModel,
model: monaco.editor.ITextModel | undefined,
stackFrame: DebugStackFrame | undefined): Promise {
@@ -101,63 +110,188 @@ export class DebugInlineValueDecorator implements FrontendApplicationContributio
range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn);
}
- return this.createInlineValueDecorationsInsideRange(children, range, model);
+ return this.createInlineValueDecorationsInsideRange(children, range, model, debugEditorModel, stackFrame);
}));
return decorationsPerScope.reduce((previous, current) => previous.concat(current), []);
}
// https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts#L410-L452
- private createInlineValueDecorationsInsideRange(
+ private async createInlineValueDecorationsInsideRange(
expressions: ReadonlyArray,
range: monaco.Range,
- model: monaco.editor.ITextModel): IDecorationOptions[] {
+ model: monaco.editor.ITextModel,
+ debugEditorModel: DebugEditorModel,
+ stackFrame: DebugStackFrame): Promise {
- const nameValueMap = new Map();
- for (const expr of expressions) {
- if (expr instanceof DebugVariable) { // XXX: VS Code uses `IExpression` that has `name` and `value`.
- nameValueMap.set(expr.name, expr.value);
- }
- // Limit the size of map. Too large can have a perf impact
- if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) {
- break;
- }
- }
+ const decorations: IDecorationOptions[] = [];
- const lineToNamesMap: Map = new Map();
- const wordToPositionsMap = this.getWordToPositionsMap(model);
-
- // Compute unique set of names on each line
- nameValueMap.forEach((_, name) => {
- const positions = wordToPositionsMap.get(name);
- if (positions) {
- for (const position of positions) {
- if (range.containsPosition(position)) {
- if (!lineToNamesMap.has(position.lineNumber)) {
- lineToNamesMap.set(position.lineNumber, []);
+ const inlineValuesProvider = StandaloneServices.get(ILanguageFeaturesService).inlineValuesProvider;
+ const textEditorModel = debugEditorModel.editor.document.textEditorModel;
+
+ if (inlineValuesProvider && inlineValuesProvider.has(textEditorModel)) {
+
+ const findVariable = async (variableName: string, caseSensitiveLookup: boolean): Promise => {
+ const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range!);
+ const key = caseSensitiveLookup ? variableName : variableName.toLowerCase();
+ for (const scope of scopes) {
+ const expressionContainers = await scope.getElements();
+ let container = expressionContainers.next();
+ while (!container.done) {
+ const debugVariable = container.value;
+ if (debugVariable && debugVariable instanceof DebugVariable) {
+ if (caseSensitiveLookup) {
+ if (debugVariable.name === key) {
+ return debugVariable;
+ }
+ } else {
+ if (debugVariable.name.toLowerCase() === key) {
+ return debugVariable;
+ }
+ }
}
+ container = expressionContainers.next();
+ }
+ }
+ return undefined;
+ };
+
+ const context: InlineValueContext = {
+ frameId: stackFrame.raw.id,
+ stoppedLocation: range
+ };
+
+ const cancellationToken = new CancellationTokenSource().token;
+ const registeredProviders = inlineValuesProvider.ordered(textEditorModel).reverse();
+ const visibleRanges = debugEditorModel.editor.getControl().getVisibleRanges();
+
+ const lineDecorations = new Map();
+
+ for (const provider of registeredProviders) {
+ for (const visibleRange of visibleRanges) {
+ const result = await provider.provideInlineValues(textEditorModel, visibleRange, context, cancellationToken);
+ if (result) {
+ for (const inlineValue of result) {
+ let text: string | undefined = undefined;
+ switch (inlineValue.type) {
+ case 'text':
+ text = inlineValue.text;
+ break;
+ case 'variable': {
+ let varName = inlineValue.variableName;
+ if (!varName) {
+ const lineContent = model.getLineContent(inlineValue.range.startLineNumber);
+ varName = lineContent.substring(inlineValue.range.startColumn - 1, inlineValue.range.endColumn - 1);
+ }
+ const variable = await findVariable(varName, inlineValue.caseSensitiveLookup);
+ if (variable) {
+ text = this.formatInlineValue(varName, variable.value);
+ }
+ break;
+ }
+ case 'expression': {
+ let expr = inlineValue.expression;
+ if (!expr) {
+ const lineContent = model.getLineContent(inlineValue.range.startLineNumber);
+ expr = lineContent.substring(inlineValue.range.startColumn - 1, inlineValue.range.endColumn - 1);
+ }
+ if (expr) {
+ const expression = new ExpressionItem(expr, () => stackFrame.thread.session);
+ await expression.evaluate('watch');
+ if (expression.available) {
+ text = this.formatInlineValue(expr, expression.value);
+ }
+ }
+ break;
+ }
+ }
- if (lineToNamesMap.get(position.lineNumber)!.indexOf(name) === -1) {
- lineToNamesMap.get(position.lineNumber)!.push(name);
+ if (text) {
+ const line = inlineValue.range.startLineNumber;
+ let lineSegments = lineDecorations.get(line);
+ if (!lineSegments) {
+ lineSegments = [];
+ lineDecorations.set(line, lineSegments);
+ }
+ if (!lineSegments.some(segment => segment.text === text)) {
+ lineSegments.push(new InlineSegment(inlineValue.range.startColumn, text));
+ }
+ }
}
}
}
+ };
+
+ // sort line segments and concatenate them into a decoration
+ const separator = ', ';
+ lineDecorations.forEach((segments, line) => {
+ if (segments.length > 0) {
+ segments = segments.sort((a, b) => a.column - b.column);
+ const text = segments.map(s => s.text).join(separator);
+ decorations.push(this.createInlineValueDecoration(line, text));
+ }
+ });
+
+ } else { // use fallback if no provider was registered
+ const lineToNamesMap: Map = new Map();
+ const nameValueMap = new Map();
+ for (const expr of expressions) {
+ if (expr instanceof DebugVariable) { // XXX: VS Code uses `IExpression` that has `name` and `value`.
+ nameValueMap.set(expr.name, expr.value);
+ }
+ // Limit the size of map. Too large can have a perf impact
+ if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) {
+ break;
+ }
}
- });
- const decorations: IDecorationOptions[] = [];
- // Compute decorators for each line
- lineToNamesMap.forEach((names, line) => {
- const contentText = names.sort((first, second) => {
- const content = model.getLineContent(line);
- return content.indexOf(first) - content.indexOf(second);
- }).map(name => `${name} = ${nameValueMap.get(name)}`).join(', ');
- decorations.push(this.createInlineValueDecoration(line, contentText));
- });
+ const wordToPositionsMap = this.getWordToPositionsMap(model);
+
+ // Compute unique set of names on each line
+ nameValueMap.forEach((_, name) => {
+ const positions = wordToPositionsMap.get(name);
+ if (positions) {
+ for (const position of positions) {
+ if (range.containsPosition(position)) {
+ if (!lineToNamesMap.has(position.lineNumber)) {
+ lineToNamesMap.set(position.lineNumber, []);
+ }
+
+ if (lineToNamesMap.get(position.lineNumber)!.indexOf(name) === -1) {
+ lineToNamesMap.get(position.lineNumber)!.push(name);
+ }
+ }
+ }
+ }
+ });
+
+ // Compute decorators for each line
+ lineToNamesMap.forEach((names, line) => {
+ const contentText = names.sort((first, second) => {
+ const content = model.getLineContent(line);
+ return content.indexOf(first) - content.indexOf(second);
+ }).map(name => `${name} = ${nameValueMap.get(name)}`).join(', ');
+ decorations.push(this.createInlineValueDecoration(line, contentText));
+ });
+ }
return decorations;
}
+ protected formatInlineValue(...args: string[]): string {
+ const valuePattern = '{0} = {1}';
+ const formatRegExp = /{(\d+)}/g;
+ if (args.length === 0) {
+ return valuePattern;
+ }
+ return valuePattern.replace(formatRegExp, (match, group) => {
+ const idx = parseInt(group, 10);
+ return isNaN(idx) || idx < 0 || idx >= args.length ?
+ match :
+ args[idx];
+ });
+ }
+
// https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts#L454-L485
private createInlineValueDecoration(lineNumber: number, contentText: string): IDecorationOptions {
// If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line
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 3ac5ad8452ecd..4f14694c89ad9 100644
--- a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts
+++ b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts
@@ -265,6 +265,38 @@ export interface EvaluatableExpressionProvider {
token: monaco.CancellationToken): EvaluatableExpression | undefined | Thenable;
}
+export interface InlineValueContext {
+ frameId: number;
+ stoppedLocation: Range;
+}
+
+export interface InlineValueText {
+ type: 'text';
+ range: Range;
+ text: string;
+}
+
+export interface InlineValueVariableLookup {
+ type: 'variable';
+ range: Range;
+ variableName?: string;
+ caseSensitiveLookup: boolean;
+}
+
+export interface InlineValueEvaluatableExpression {
+ type: 'expression';
+ range: Range;
+ expression?: string;
+}
+
+export type InlineValue = InlineValueText | InlineValueVariableLookup | InlineValueEvaluatableExpression;
+
+export interface InlineValuesProvider {
+ onDidChangeInlineValues?: TheiaEvent | undefined;
+ provideInlineValues(model: monaco.editor.ITextModel, viewPort: Range, context: InlineValueContext, token: monaco.CancellationToken):
+ InlineValue[] | 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 35110a13cb237..e16f64fb3125b 100644
--- a/packages/plugin-ext/src/common/plugin-api-rpc.ts
+++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts
@@ -42,6 +42,8 @@ import {
SignatureHelp,
Hover,
EvaluatableExpression,
+ InlineValue,
+ InlineValueContext,
DocumentHighlight,
FormattingOptions,
ChainedCacheId,
@@ -1490,6 +1492,7 @@ export interface LanguagesExt {
$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;
+ $provideInlineValues(handle: number, resource: UriComponents, range: Range, context: InlineValueContext, token: CancellationToken): Promise;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise;
$provideDocumentFormattingEdits(handle: number, resource: UriComponents,
options: FormattingOptions, token: CancellationToken): Promise;
@@ -1567,6 +1570,8 @@ export interface LanguagesMain {
$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;
+ $registerInlineValuesProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void;
+ $emitInlineValuesEvent(eventHandle: number, event?: any): 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 23a12b6f7fe19..ed08b07b5707f 100644
--- a/packages/plugin-ext/src/main/browser/languages-main.ts
+++ b/packages/plugin-ext/src/main/browser/languages-main.ts
@@ -67,7 +67,13 @@ import { IRelativePattern } from '@theia/monaco-editor-core/esm/vs/base/common/g
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 {
+ EvaluatableExpression,
+ EvaluatableExpressionProvider,
+ InlineValue,
+ InlineValueContext,
+ InlineValuesProvider
+} from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
import { CodeActionTriggerKind } from '../../plugin/types-impl';
@@ -361,6 +367,33 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
return this.proxy.$provideEvaluatableExpression(handle, model.uri, position, token);
}
+ $registerInlineValuesProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void {
+ const languageSelector = this.toLanguageSelector(selector);
+ const inlineValuesProvider = this.createInlineValuesProvider(handle);
+ this.register(handle,
+ (StandaloneServices.get(ILanguageFeaturesService).inlineValuesProvider.register as RegistrationFunction)
+ (languageSelector, inlineValuesProvider));
+ }
+
+ protected createInlineValuesProvider(handle: number): InlineValuesProvider {
+ return {
+ provideInlineValues: (model, range, context, token) => this.provideInlineValues(handle, model, range, context, token)
+ };
+ }
+
+ protected provideInlineValues(handle: number, model: ITextModel, range: Range,
+ context: InlineValueContext, token: monaco.CancellationToken): monaco.languages.ProviderResult {
+ return this.proxy.$provideInlineValues(handle, model.uri, range, context, token);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ $emitInlineValuesEvent(eventHandle: number, event?: any): void {
+ const obj = this.services.get(eventHandle);
+ if (obj instanceof Emitter) {
+ obj.fire(event);
+ }
+ }
+
$registerDocumentHighlightProvider(handle: number, _pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void {
const languageSelector = this.toLanguageSelector(selector);
const documentHighlightProvider = this.createDocumentHighlightProvider(handle);
diff --git a/packages/plugin-ext/src/plugin/languages.ts b/packages/plugin-ext/src/plugin/languages.ts
index 136dd0e0293ec..860671c56457d 100644
--- a/packages/plugin-ext/src/plugin/languages.ts
+++ b/packages/plugin-ext/src/plugin/languages.ts
@@ -62,13 +62,16 @@ import {
CallHierarchyIncomingCall,
CallHierarchyOutgoingCall,
LinkedEditingRanges,
- EvaluatableExpression
+ EvaluatableExpression,
+ InlineValue,
+ InlineValueContext
} 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 { InlineValuesAdapter } from './languages/inline-values';
import { DocumentHighlightAdapter } from './languages/document-highlight';
import { DocumentFormattingAdapter } from './languages/document-formatting';
import { RangeFormattingAdapter } from './languages/range-formatting';
@@ -103,6 +106,7 @@ type Adapter = CompletionAdapter |
SignatureHelpAdapter |
HoverAdapter |
EvaluatableExpressionAdapter |
+ InlineValuesAdapter |
DocumentHighlightAdapter |
DocumentFormattingAdapter |
RangeFormattingAdapter |
@@ -365,6 +369,25 @@ export class LanguagesExtImpl implements LanguagesExt {
}
// ### EvaluatableExpression Provider end
+ // ### InlineValues Provider begin
+ registerInlineValuesProvider(selector: theia.DocumentSelector, provider: theia.InlineValuesProvider, pluginInfo: PluginInfo): theia.Disposable {
+ const eventHandle = typeof provider.onDidChangeInlineValues === 'function' ? this.nextCallId() : undefined;
+ const callId = this.addNewAdapter(new InlineValuesAdapter(provider, this.documents));
+ this.proxy.$registerInlineValuesProvider(callId, pluginInfo, this.transformDocumentSelector(selector));
+ let result = this.createDisposable(callId);
+
+ if (eventHandle !== undefined) {
+ const subscription = provider.onDidChangeInlineValues!(_ => this.proxy.$emitInlineValuesEvent(eventHandle));
+ result = Disposable.from(result, subscription);
+ }
+ return result;
+ }
+
+ $provideInlineValues(handle: number, resource: UriComponents, range: Range, context: InlineValueContext, token: theia.CancellationToken): Promise {
+ return this.withAdapter(handle, InlineValuesAdapter, adapter => adapter.provideInlineValues(URI.revive(resource), range, context, token), undefined);
+ }
+ // ### InlineValue 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/inline-values.ts b/packages/plugin-ext/src/plugin/languages/inline-values.ts
new file mode 100644
index 0000000000000..0b608780ebfe4
--- /dev/null
+++ b/packages/plugin-ext/src/plugin/languages/inline-values.ts
@@ -0,0 +1,50 @@
+// *****************************************************************************
+// 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 { InlineValue, InlineValueContext, Range } from '../../common/plugin-api-rpc-model';
+import { DocumentsExtImpl } from '../documents';
+import * as Converter from '../type-converters';
+
+export class InlineValuesAdapter {
+
+ constructor(
+ private readonly provider: theia.InlineValuesProvider,
+ private readonly documents: DocumentsExtImpl
+ ) { }
+
+ async provideInlineValues(resource: URI, range: Range, context: InlineValueContext, 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 viewPort = Converter.toRange(range);
+ const ctx = Converter.toInlineValueContext(context);
+
+ return Promise.resolve(this.provider.provideInlineValues(document, viewPort, ctx, token)).then(inlineValue => {
+ if (!inlineValue) {
+ return undefined;
+ }
+ if (Array.isArray(inlineValue)) {
+ return inlineValue.map(iv => Converter.fromInlineValue(iv));
+ }
+ return undefined;
+ });
+ }
+}
diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts
index 4f9a383b0f5ea..2538c6089a851 100644
--- a/packages/plugin-ext/src/plugin/plugin-context.ts
+++ b/packages/plugin-ext/src/plugin/plugin-context.ts
@@ -79,6 +79,10 @@ import {
SignatureHelpTriggerKind,
Hover,
EvaluatableExpression,
+ InlineValueEvaluatableExpression,
+ InlineValueText,
+ InlineValueVariableLookup,
+ InlineValueContext,
DocumentHighlightKind,
DocumentHighlight,
DocumentLink,
@@ -701,6 +705,9 @@ export function createAPIFactory(
registerEvaluatableExpressionProvider(selector: theia.DocumentSelector, provider: theia.EvaluatableExpressionProvider): theia.Disposable {
return languagesExt.registerEvaluatableExpressionProvider(selector, provider, pluginToPluginInfo(plugin));
},
+ registerInlineValuesProvider(selector: theia.DocumentSelector, provider: theia.InlineValuesProvider): theia.Disposable {
+ return languagesExt.registerInlineValuesProvider(selector, provider, pluginToPluginInfo(plugin));
+ },
registerDocumentHighlightProvider(selector: theia.DocumentSelector, provider: theia.DocumentHighlightProvider): theia.Disposable {
return languagesExt.registerDocumentHighlightProvider(selector, provider, pluginToPluginInfo(plugin));
},
@@ -962,6 +969,10 @@ export function createAPIFactory(
SignatureHelpTriggerKind,
Hover,
EvaluatableExpression,
+ InlineValueEvaluatableExpression,
+ InlineValueText,
+ InlineValueVariableLookup,
+ InlineValueContext,
DocumentHighlightKind,
DocumentHighlight,
DocumentLink,
diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts
index cc46dfb74f008..4b80b2b11a2f9 100644
--- a/packages/plugin-ext/src/plugin/type-converters.ts
+++ b/packages/plugin-ext/src/plugin/type-converters.ts
@@ -16,7 +16,7 @@
import * as theia from '@theia/plugin';
import * as lstypes from '@theia/core/shared/vscode-languageserver-protocol';
-import { QuickPickItemKind, URI } from './types-impl';
+import { InlineValueEvaluatableExpression, InlineValueText, InlineValueVariableLookup, QuickPickItemKind, URI } from './types-impl';
import * as rpc from '../common/plugin-api-rpc';
import {
DecorationOptions, EditorPosition, Plugin, Position, WorkspaceTextEditDto, WorkspaceFileEditDto, Selection, TaskDto, WorkspaceEditDto
@@ -401,6 +401,39 @@ export function fromEvaluatableExpression(evaluatableExpression: theia.Evaluatab
};
}
+export function fromInlineValue(inlineValue: theia.InlineValue): model.InlineValue {
+ if (inlineValue instanceof InlineValueText) {
+ return {
+ type: 'text',
+ range: fromRange(inlineValue.range),
+ text: inlineValue.text
+ };
+ } else if (inlineValue instanceof InlineValueVariableLookup) {
+ return {
+ type: 'variable',
+ range: fromRange(inlineValue.range),
+ variableName: inlineValue.variableName,
+ caseSensitiveLookup: inlineValue.caseSensitiveLookup
+ };
+ } else if (inlineValue instanceof InlineValueEvaluatableExpression) {
+ return {
+ type: 'expression',
+ range: fromRange(inlineValue.range),
+ expression: inlineValue.expression
+ };
+ } else {
+ throw new Error('Unknown InlineValue type');
+ }
+}
+
+export function toInlineValueContext(inlineValueContext: model.InlineValueContext): theia.InlineValueContext {
+ const ivLocation = inlineValueContext.stoppedLocation;
+ return {
+ frameId: inlineValueContext.frameId,
+ stoppedLocation: new types.Range(ivLocation.startLineNumber, ivLocation.startColumn, ivLocation.endLineNumber, ivLocation.endColumn)
+ };
+}
+
export function fromLocation(location: theia.Location): model.Location {
return {
uri: location.uri,
@@ -584,7 +617,7 @@ export namespace SymbolKind {
}
}
-export function toCodeActionTriggerKind(triggerKind: model.CodeActionTriggerKind): types.CodeActionTriggerKind {
+export function toCodeActionTriggerKind(triggerKind: model.CodeActionTriggerKind): types.CodeActionTriggerKind {
switch (triggerKind) {
case model.CodeActionTriggerKind.Invoke:
return types.CodeActionTriggerKind.Invoke;
@@ -1237,3 +1270,4 @@ export function pluginToPluginInfo(plugin: Plugin): rpc.PluginInfo {
displayName: plugin.model.displayName
};
}
+
diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts
index df97ec4b6f953..ca4ab2c4cc13b 100644
--- a/packages/plugin-ext/src/plugin/types-impl.ts
+++ b/packages/plugin-ext/src/plugin/types-impl.ts
@@ -1156,6 +1156,75 @@ export class EvaluatableExpression {
}
}
+@es5ClassCompat
+export class InlineValueContext implements theia.InlineValueContext {
+ public frameId: number;
+ public stoppedLocation: Range;
+
+ constructor(frameId: number, stoppedLocation: Range) {
+ if (!frameId) {
+ illegalArgument('frameId must be defined');
+ }
+ if (!stoppedLocation) {
+ illegalArgument('stoppedLocation must be defined');
+ }
+ this.frameId = frameId;
+ this.stoppedLocation = stoppedLocation;
+ }
+}
+
+@es5ClassCompat
+export class InlineValueText implements theia.InlineValueText {
+ public type = 'text';
+ public range: Range;
+ public text: string;
+
+ constructor(range: Range, text: string) {
+ if (!range) {
+ illegalArgument('range must be defined');
+ }
+ if (!text) {
+ illegalArgument('text must be defined');
+ }
+ this.range = range;
+ this.text = text;
+ }
+}
+
+@es5ClassCompat
+export class InlineValueVariableLookup implements theia.InlineValueVariableLookup {
+ public type = 'variable';
+ public range: Range;
+ public variableName?: string;
+ public caseSensitiveLookup: boolean;
+
+ constructor(range: Range, variableName?: string, caseSensitiveLookup?: boolean) {
+ if (!range) {
+ illegalArgument('range must be defined');
+ }
+ this.range = range;
+ this.caseSensitiveLookup = caseSensitiveLookup || true;
+ this.variableName = variableName;
+ }
+}
+
+@es5ClassCompat
+export class InlineValueEvaluatableExpression implements theia.InlineValueEvaluatableExpression {
+ public type = 'expression';
+ 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 type InlineValue = InlineValueText | InlineValueVariableLookup | InlineValueEvaluatableExpression;
+
export enum DocumentHighlightKind {
Text = 0,
Read = 1,
diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts
index aad9c56d5541c..bd47900aff704 100644
--- a/packages/plugin/src/theia.d.ts
+++ b/packages/plugin/src/theia.d.ts
@@ -9506,6 +9506,21 @@ export module '@theia/plugin' {
*/
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
+ /**
+ * Register a provider that returns data for the debugger's 'inline value' feature.
+ * Whenever the generic debugger has stopped in a source file, providers registered for the language of the file
+ * are called to return textual data that will be shown in the editor at the end of lines.
+ *
+ * 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 An inline values provider.
+ * @return A {@link Disposable} that unregisters this provider when being disposed.
+ */
+ export function registerInlineValuesProvider(selector: DocumentSelector, provider: InlineValuesProvider): Disposable;
+
/**
* Register a workspace symbol provider.
*
@@ -9867,6 +9882,134 @@ export module '@theia/plugin' {
provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken | undefined): ProviderResult;
}
+ /**
+ * Provide inline value as text.
+ */
+ export class InlineValueText {
+ /**
+ * The document range for which the inline value applies.
+ */
+ readonly range: Range;
+ /**
+ * The text of the inline value.
+ */
+ readonly text: string;
+ /**
+ * Creates a new InlineValueText object.
+ *
+ * @param range The document line where to show the inline value.
+ * @param text The value to be shown for the line.
+ */
+ constructor(range: Range, text: string);
+ }
+
+ /**
+ * Provide inline value through a variable lookup.
+ * If only a range is specified, the variable name will be extracted from the underlying document.
+ * An optional variable name can be used to override the extracted name.
+ */
+ export class InlineValueVariableLookup {
+ /**
+ * The document range for which the inline value applies.
+ * The range is used to extract the variable name from the underlying document.
+ */
+ readonly range: Range;
+ /**
+ * If specified the name of the variable to look up.
+ */
+ readonly variableName?: string | undefined;
+ /**
+ * How to perform the lookup.
+ */
+ readonly caseSensitiveLookup: boolean;
+ /**
+ * Creates a new InlineValueVariableLookup object.
+ *
+ * @param range The document line where to show the inline value.
+ * @param variableName The name of the variable to look up.
+ * @param caseSensitiveLookup How to perform the lookup. If missing lookup is case sensitive.
+ */
+ constructor(range: Range, variableName?: string, caseSensitiveLookup?: boolean);
+ }
+
+ /**
+ * Provide an inline value through an expression evaluation.
+ * 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.
+ */
+ export class InlineValueEvaluatableExpression {
+ /**
+ * The document range for which the inline value applies.
+ * The range is used to extract the evaluatable expression from the underlying document.
+ */
+ readonly range: Range;
+ /**
+ * If specified the expression overrides the extracted expression.
+ */
+ readonly expression?: string | undefined;
+ /**
+ * Creates a new InlineValueEvaluatableExpression 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);
+ }
+
+ /**
+ * Inline value information can be provided by different means:
+ * - directly as a text value (class InlineValueText).
+ * - as a name to use for a variable lookup (class InlineValueVariableLookup)
+ * - as an evaluatable expression (class InlineValueEvaluatableExpression)
+ * The InlineValue types combines all inline value types into one type.
+ */
+ export type InlineValue = InlineValueText | InlineValueVariableLookup | InlineValueEvaluatableExpression;
+
+ /**
+ * A value-object that contains contextual information when requesting inline values from a InlineValuesProvider.
+ */
+ export interface InlineValueContext {
+
+ /**
+ * The stack frame (as a DAP Id) where the execution has stopped.
+ */
+ readonly frameId: number;
+
+ /**
+ * The document range where execution has stopped.
+ * Typically the end position of the range denotes the line where the inline values are shown.
+ */
+ readonly stoppedLocation: Range;
+ }
+
+ /**
+ * The inline values provider interface defines the contract between extensions and the editor's debugger inline values feature.
+ * In this contract the provider returns inline value information for a given document range
+ * and the editor shows this information in the editor at the end of lines.
+ */
+ export interface InlineValuesProvider {
+
+ /**
+ * An optional event to signal that inline values have changed.
+ * @see {@link EventEmitter}
+ */
+ onDidChangeInlineValues?: Event | undefined;
+
+ /**
+ * Provide "inline value" information for a given document and range.
+ * The editor calls this method whenever debugging stops in the given document.
+ * The returned inline values information is rendered in the editor at the end of lines.
+ *
+ * @param document The document for which the inline values information is needed.
+ * @param viewPort The visible document range for which inline values should be computed.
+ * @param context A bag containing contextual information like the current location.
+ * @param token A cancellation token.
+ * @return An array of InlineValueDescriptors or a thenable that resolves to such. The lack of a result can be
+ * signaled by returning `undefined` or `null`.
+ */
+ provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken): ProviderResult;
+ }
+
/**
* A document highlight kind.
*/