Introduce evaluatable expression API; fixes #89084

This commit is contained in:
Andre Weinand 2020-02-14 12:47:52 +01:00
parent aa9f9695c4
commit e588a9cd1a
10 changed files with 243 additions and 15 deletions

View file

@ -265,6 +265,34 @@ export interface HoverProvider {
provideHover(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<Hover>;
}
/**
* An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are
* evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget.
*/
export interface EvaluatableExpression {
/**
* The range to which this expression applies.
*/
range: IRange;
/*
* This expression overrides the expression extracted from the range.
*/
expression?: string;
}
/**
* The hover provider interface defines the contract between extensions and
* the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.
*/
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.
*/
provideEvaluatableExpression(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
}
export const enum CompletionItemKind {
Method,
Function,
@ -1595,6 +1623,11 @@ export const SignatureHelpProviderRegistry = new LanguageFeatureRegistry<Signatu
*/
export const HoverProviderRegistry = new LanguageFeatureRegistry<HoverProvider>();
/**
* @internal
*/
export const EvaluatableExpressionProviderRegistry = new LanguageFeatureRegistry<EvaluatableExpressionProvider>();
/**
* @internal
*/

25
src/vs/monaco.d.ts vendored
View file

@ -5238,6 +5238,31 @@ declare namespace monaco.languages {
provideHover(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<Hover>;
}
/**
* An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are
* evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget.
*/
export interface EvaluatableExpression {
/**
* The range to which this expression applies.
*/
range: IRange;
expression?: string;
}
/**
* The hover provider interface defines the contract between extensions and
* the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.
*/
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.
*/
provideEvaluatableExpression(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
}
export enum CompletionItemKind {
Method = 0,
Function = 1,

View file

@ -876,7 +876,65 @@ declare module 'vscode' {
//#endregion
//#region Debug:
//#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084
/**
* 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;
/**
* 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.
*/
export interface EvaluatableExpressionProvider {
/**
* Provide an evaluatable expression for the given document and position.
* The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
*
* @param document The document in which the command was invoked.
* @param position The position where the command was invoked.
* @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): ProviderResult<EvaluatableExpression>;
}
export namespace languages {
/**
* Register a provider that locates evaluatable expressions in text documents.
*
* 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 [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
}
// deprecated

View file

@ -213,6 +213,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}));
}
// --- debug hover
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void {
this._registrations.set(handle, modes.EvaluatableExpressionProviderRegistry.register(selector, <modes.EvaluatableExpressionProvider>{
provideEvaluatableExpression: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> => {
return this._proxy.$provideEvaluatableExpression(handle, model.uri, position, token);
}
}));
}
// --- occurrences
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void {

View file

@ -345,6 +345,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable {
return extHostLanguageFeatures.registerHoverProvider(extension, checkSelector(selector), provider, extension.identifier);
},
registerEvaluatableExpressionProvider(selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider): vscode.Disposable {
return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier);
},
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider);
},
@ -926,6 +929,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
DocumentLink: extHostTypes.DocumentLink,
DocumentSymbol: extHostTypes.DocumentSymbol,
EndOfLine: extHostTypes.EndOfLine,
EvaluatableExpression: extHostTypes.EvaluatableExpression,
EventEmitter: Emitter,
ExtensionKind: extHostTypes.ExtensionKind,
CustomExecution: extHostTypes.CustomExecution,

View file

@ -353,6 +353,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void;
@ -1208,6 +1209,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IDefinitionLinkDto[]>;
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IDefinitionLinkDto[]>;
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;

View file

@ -276,6 +276,27 @@ class HoverAdapter {
}
}
class EvaluatableExpressionAdapter {
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.EvaluatableExpressionProvider,
) { }
public provideEvaluatableExpression(resource: URI, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> {
const doc = this._documents.getDocument(resource);
const pos = typeConvert.Position.to(position);
return asPromise(() => this._provider.provideEvaluatableExpression(doc, pos, token)).then(value => {
if (value) {
return typeConvert.EvaluatableExpression.from(value);
}
return undefined;
});
}
}
class DocumentHighlightAdapter {
constructor(
@ -1329,7 +1350,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter;
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter;
class AdapterData {
constructor(
@ -1549,6 +1570,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return this._withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, token), undefined);
}
// --- debug hover
registerEvaluatableExpressionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider, extensionId?: ExtensionIdentifier): vscode.Disposable {
const handle = this._addNewAdapter(new EvaluatableExpressionAdapter(this._documents, provider), extension);
this._proxy.$registerEvaluatableExpressionProvider(handle, this._transformDocumentSelector(selector));
return this._createDisposable(handle);
}
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> {
return this._withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined);
}
// --- occurrences
registerDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {

View file

@ -737,6 +737,20 @@ export namespace Hover {
return new types.Hover(info.contents.map(MarkdownString.to), Range.to(info.range));
}
}
export namespace EvaluatableExpression {
export function from(expression: vscode.EvaluatableExpression): modes.EvaluatableExpression {
return <modes.EvaluatableExpression>{
range: Range.from(expression.range),
expression: expression.expression
};
}
export function to(info: modes.EvaluatableExpression): types.EvaluatableExpression {
return new types.EvaluatableExpression(Range.to(info.range), info.expression);
}
}
export namespace DocumentHighlight {
export function from(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight {
return {

View file

@ -2283,6 +2283,17 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli
}
}
@es5ClassCompat
export class EvaluatableExpression implements vscode.EvaluatableExpression {
readonly range: vscode.Range;
readonly expression?: string;
constructor(range: vscode.Range, expression?: string) {
this.range = range;
this.expression = expression;
}
}
export enum LogLevel {
Trace = 1,
Debug = 2,

View file

@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Range, IRange } from 'vs/editor/common/core/range';
import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDebugService, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
@ -30,6 +30,8 @@ import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { coalesce } from 'vs/base/common/arrays';
import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
import { EvaluatableExpressionProviderRegistry } from 'vs/editor/common/modes';
import { CancellationToken } from 'vs/base/common/cancellation';
const $ = dom.$;
const MAX_TREE_HEIGHT = 324;
@ -174,18 +176,52 @@ export class DebugHoverWidget implements IContentWidget {
}
async showAt(range: Range, focus: boolean): Promise<void> {
const pos = range.getStartPosition();
const session = this.debugService.getViewModel().focusedSession;
if (!this.editor.hasModel()) {
if (!session || !this.editor.hasModel()) {
return Promise.resolve(this.hide());
}
const lineContent = this.editor.getModel().getLineContent(pos.lineNumber);
const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn);
// use regex to extract the sub-expression #9821
const matchingExpression = lineContent.substring(start - 1, end);
if (!matchingExpression || !session) {
const model = this.editor.getModel();
const pos = range.getStartPosition();
let rng: IRange | undefined = undefined;
let matchingExpression: string | undefined;
if (EvaluatableExpressionProviderRegistry.has(model)) {
const supports = EvaluatableExpressionProviderRegistry.ordered(model);
const promises = supports.map(support => {
return Promise.resolve(support.provideEvaluatableExpression(model, pos, CancellationToken.None)).then(expression => {
return expression;
}, err => {
//onUnexpectedExternalError(err);
return undefined;
});
});
const results = await Promise.all(promises).then(coalesce);
if (results.length > 0) {
matchingExpression = results[0].expression;
rng = results[0].range;
if (!matchingExpression) {
const lineContent = model.getLineContent(pos.lineNumber);
matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn);
}
}
} else { // old one-size-fits-all strategy
const lineContent = model.getLineContent(pos.lineNumber);
const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn);
// use regex to extract the sub-expression #9821
matchingExpression = lineContent.substring(start - 1, end);
rng = new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length);
}
if (!matchingExpression) {
return Promise.resolve(this.hide());
}
@ -202,13 +238,15 @@ export class DebugHoverWidget implements IContentWidget {
if (!expression || (expression instanceof Expression && !expression.available)) {
this.hide();
return undefined;
return;
}
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length),
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
}]);
if (rng) {
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
range: rng,
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
}]);
}
return this.doShow(pos, expression, focus);
}