Introduce evaluatable expression API; fixes #89084
This commit is contained in:
parent
aa9f9695c4
commit
e588a9cd1a
10 changed files with 243 additions and 15 deletions
|
@ -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
25
src/vs/monaco.d.ts
vendored
|
@ -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,
|
||||
|
|
60
src/vs/vscode.proposed.d.ts
vendored
60
src/vs/vscode.proposed.d.ts
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 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
|
||||
const matchingExpression = lineContent.substring(start - 1, end);
|
||||
if (!matchingExpression || !session) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (rng) {
|
||||
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
|
||||
range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length),
|
||||
range: rng,
|
||||
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
|
||||
}]);
|
||||
}
|
||||
|
||||
return this.doShow(pos, expression, focus);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue