diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index bf79ed3455b..f670ffeab44 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1541,6 +1541,23 @@ export interface Command { arguments?: any[]; } +/** + * @internal + */ +export namespace Command { + + /** + * @internal + */ + export function is(obj: any): obj is Command { + if (!obj || typeof obj !== 'object') { + return false; + } + return typeof (obj).id === 'string' && + typeof (obj).title === 'string'; + } +} + /** * @internal */ @@ -1721,8 +1738,14 @@ export enum InlayHintKind { Parameter = 2, } -export interface InlayHint { +export interface InlayHintLabelPart { label: string; + collapsible?: boolean; + action?: Command | Location +} + +export interface InlayHint { + label: string | InlayHintLabelPart[]; tooltip?: string | IMarkdownString position: IPosition; kind: InlayHintKind; diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts index 2bcd9945105..c6ec5db0ad0 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsController.ts @@ -6,7 +6,6 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { parseLinkedText } from 'vs/base/common/linkedText'; import { LRUCache } from 'vs/base/common/map'; import { IRange } from 'vs/base/common/range'; import { assertType } from 'vs/base/common/types'; @@ -17,15 +16,15 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { InlayHint, InlayHintKind, InlayHintsProviderRegistry } from 'vs/editor/common/languages'; +import * as languages from 'vs/editor/common/languages'; import { LanguageFeatureRequestDelays } from 'vs/editor/common/languages/languageFeatureRegistry'; import { IModelDeltaDecoration, InjectedTextOptions, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ClickLinkGesture } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { InlayHintAnchor, InlayHintItem, InlayHintsFragments } from 'vs/editor/contrib/inlayHints/inlayHints'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -52,7 +51,16 @@ class InlayHintsCache { } export class InlayHintLabelPart { - constructor(readonly item: InlayHintItem, readonly index: number, readonly href?: string) { } + constructor(readonly item: InlayHintItem, readonly index: number) { } + + get part() { + const label = this.item.hint.label; + if (typeof label === 'string') { + return { label }; + } else { + return label[this.index]; + } + } } export class InlayHintsController implements IEditorContribution { @@ -65,7 +73,7 @@ export class InlayHintsController implements IEditorContribution { private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); - private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(InlayHintsProviderRegistry, 25, 500); + private readonly _getInlayHintsDelays = new LanguageFeatureRequestDelays(languages.InlayHintsProviderRegistry, 25, 500); private readonly _cache = new InlayHintsCache(); private readonly _decorationsMetadata = new Map(); private readonly _ruleFactory = new DynamicCssRules(this._editor); @@ -74,9 +82,10 @@ export class InlayHintsController implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, - @IOpenerService private readonly _openerService: IOpenerService, + @ICommandService private readonly _commandService: ICommandService, + @INotificationService private readonly _notificationService: INotificationService, ) { - this._disposables.add(InlayHintsProviderRegistry.onDidChange(() => this._update())); + this._disposables.add(languages.InlayHintsProviderRegistry.onDidChange(() => this._update())); this._disposables.add(_editor.onDidChangeModel(() => this._update())); this._disposables.add(_editor.onDidChangeModelLanguage(() => this._update())); this._disposables.add(_editor.onDidChangeConfiguration(e => { @@ -102,7 +111,7 @@ export class InlayHintsController implements IEditorContribution { } const model = this._editor.getModel(); - if (!model || !InlayHintsProviderRegistry.has(model)) { + if (!model || !languages.InlayHintsProviderRegistry.has(model)) { return; } @@ -156,7 +165,18 @@ export class InlayHintsController implements IEditorContribution { } const model = this._editor.getModel()!; const options = mouseEvent.target.detail.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { + + if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart)) { + removeHighlight(); + return; + } + + // kick-off resolve whenever the mouse is over inlay hints + // todo@jrieken CANCEL for real! + options.attachedData.item.resolve(CancellationToken.None); + + // render link => when the modifier is pressed and when there is an action + if (mouseEvent.hasTriggerModifier && options.attachedData.part.action) { this._activeInlayHintPart = options.attachedData; const lineNumber = this._activeInlayHintPart.item.hint.position.lineNumber; @@ -179,9 +199,16 @@ export class InlayHintsController implements IEditorContribution { if (e.target.type !== MouseTargetType.CONTENT_TEXT || !e.hasTriggerModifier) { return; } - const options = e.target.detail.injectedText?.options; - if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.href) { - this._openerService.open(options.attachedData.href, { allowCommands: true, openToSide: e.hasSideBySideModifier }); + const options = e.target.detail?.injectedText?.options; + if (options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart && options.attachedData.part.action) { + const part = options.attachedData.part; + if (languages.Command.is(part.action)) { + this._commandService.executeCommand(part.action.id, ...(part.action.arguments ?? [])).catch(err => this._notificationService.error(err)); + + } else if (part.action) { + console.log('TODO', part.action); + // todo@jrieken IMPLEMENT THIS + } } }); return gesture; @@ -233,17 +260,20 @@ export class InlayHintsController implements IEditorContribution { for (const item of items) { + const parts: languages.InlayHintLabelPart[] = typeof item.hint.label === 'string' + ? [{ label: item.hint.label }] + : item.hint.label; + // text w/ links - const { nodes } = parseLinkedText(item.hint.label); + const marginBefore = item.hint.whitespaceBefore ? (fontSize / 3) | 0 : 0; const marginAfter = item.hint.whitespaceAfter ? (fontSize / 3) | 0 : 0; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; const isFirst = i === 0; - const isLast = i === nodes.length - 1; - const isLink = typeof node === 'object'; + const isLast = i === parts.length - 1; const cssProperties: CssProperties = { fontSize: `${fontSize}px`, @@ -253,14 +283,11 @@ export class InlayHintsController implements IEditorContribution { this._fillInColors(cssProperties, item.hint); - if (isLink) { + if (part.action && this._activeInlayHintPart?.item === item && this._activeInlayHintPart.index === i) { + // active link! cssProperties.textDecoration = 'underline'; - - if (this._activeInlayHintPart?.item === item && this._activeInlayHintPart.index === i && this._activeInlayHintPart.href === node.href) { - // active link! - cssProperties.cursor = 'pointer'; - cssProperties.color = themeColorFromId(colors.editorActiveLinkForeground); - } + cssProperties.cursor = 'pointer'; + cssProperties.color = themeColorFromId(colors.editorActiveLinkForeground); } if (isFirst && isLast) { @@ -291,10 +318,10 @@ export class InlayHintsController implements IEditorContribution { range: item.anchor.range, options: { [item.anchor.direction]: { - content: fixSpace(isLink ? node.label : node), + content: fixSpace(part.label), inlineClassNameAffectsLetterSpacing: true, inlineClassName: classNameRef.className, - attachedData: new InlayHintLabelPart(item, i, isLink ? node.href : undefined) + attachedData: new InlayHintLabelPart(item, i) } as InjectedTextOptions, description: 'InlayHint', showIfCollapsed: !item.anchor.usesWordRange, @@ -330,11 +357,11 @@ export class InlayHintsController implements IEditorContribution { } } - private _fillInColors(props: CssProperties, hint: InlayHint): void { - if (hint.kind === InlayHintKind.Parameter) { + private _fillInColors(props: CssProperties, hint: languages.InlayHint): void { + if (hint.kind === languages.InlayHintKind.Parameter) { props.backgroundColor = themeColorFromId(colors.editorInlayHintParameterBackground); props.color = themeColorFromId(colors.editorInlayHintParameterForeground); - } else if (hint.kind === InlayHintKind.Type) { + } else if (hint.kind === languages.InlayHintKind.Type) { props.backgroundColor = themeColorFromId(colors.editorInlayHintTypeBackground); props.color = themeColorFromId(colors.editorInlayHintTypeForeground); } else { @@ -373,7 +400,7 @@ function fixSpace(str: string): string { registerEditorContribution(InlayHintsController.ID, InlayHintsController); -CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, ...args: [URI, IRange]): Promise => { +CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, ...args: [URI, IRange]): Promise => { const [uri, range] = args; assertType(URI.isUri(uri)); diff --git a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts index 5cad01c101f..632bef17f42 100644 --- a/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/inlayHintsHover.ts @@ -5,25 +5,39 @@ import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { Range } from 'vs/editor/common/core/range'; +import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { Command, HoverProviderRegistry } from 'vs/editor/common/languages'; import { IModelDecoration } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { HoverAnchor, HoverForeignElementAnchor, IEditorHoverParticipant } from 'vs/editor/contrib/hover/hoverTypes'; +import { ILanguageService } from 'vs/editor/common/services/language'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { getHover } from 'vs/editor/contrib/hover/getHover'; import { MarkdownHover, MarkdownHoverParticipant } from 'vs/editor/contrib/hover/markdownHoverParticipant'; -import { InlayHintItem } from 'vs/editor/contrib/inlayHints/inlayHints'; import { InlayHintLabelPart, InlayHintsController } from 'vs/editor/contrib/inlayHints/inlayHintsController'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { - - constructor(readonly item: InlayHintItem, owner: InlayHintsHover) { - super(10, owner, Range.fromPositions(item.hint.position)); + constructor(readonly part: InlayHintLabelPart, owner: InlayHintsHover) { + super(10, owner, part.item.anchor.range); } } export class InlayHintsHover extends MarkdownHoverParticipant implements IEditorHoverParticipant { + constructor( + editor: ICodeEditor, + @ILanguageService languageService: ILanguageService, + @IOpenerService openerService: IOpenerService, + @IConfigurationService configurationService: IConfigurationService, + @ITextModelService private readonly _resolverService: ITextModelService + ) { + super(editor, languageService, openerService, configurationService); + } + suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { const controller = InlayHintsController.get(this._editor); if (!controller) { @@ -36,7 +50,7 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof InlayHintLabelPart)) { return null; } - return new InlayHintsHoverAnchor(options.attachedData.item, this); + return new InlayHintsHoverAnchor(options.attachedData, this); } override computeSync(): MarkdownHover[] { @@ -48,18 +62,57 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor return AsyncIterableObject.EMPTY; } - const { item } = anchor; - return AsyncIterableObject.fromPromise(item.resolve(token).then(() => { - if (!item.hint.tooltip) { - return []; + return new AsyncIterableObject(async executor => { + + const { part } = anchor; + await part.item.resolve(token); + + if (token.isCancellationRequested) { + return; } - let contents: IMarkdownString; - if (typeof item.hint.tooltip === 'string') { - contents = new MarkdownString().appendText(item.hint.tooltip); - } else { - contents = item.hint.tooltip; + + // (1) Inlay Tooltip + let contents: IMarkdownString | undefined; + if (typeof part.item.hint.tooltip === 'string') { + contents = new MarkdownString().appendText(part.item.hint.tooltip); + } else if (part.item.hint.tooltip) { + contents = part.item.hint.tooltip; } - return [new MarkdownHover(this, anchor.range, [contents], 0)]; - })); + if (contents) { + executor.emitOne(new MarkdownHover(this, anchor.range, [contents], 0)); + } + + // (2) Inlay Label Part Tooltip + const iterable = await this._resolveInlayHintLabelPartHover(part, token); + for await (let item of iterable) { + executor.emitOne(item); + } + }); + } + + private async _resolveInlayHintLabelPartHover(part: InlayHintLabelPart, token: CancellationToken): Promise> { + if (typeof part.item.hint.label === 'string') { + return AsyncIterableObject.EMPTY; + } + + const candidate = part.part.action; + + if (!candidate || Command.is(candidate)) { + // LOCATION + return AsyncIterableObject.EMPTY; + } + const { uri, range } = candidate; + const ref = await this._resolverService.createModelReference(uri); + try { + const model = ref.object.textEditorModel; + if (!HoverProviderRegistry.has(model)) { + return AsyncIterableObject.EMPTY; + } + return getHover(model, new Position(range.startLineNumber, range.startColumn), token) + .filter(item => !isEmptyMarkdownString(item.hover.contents)) + .map(item => new MarkdownHover(this, part.item.anchor.range, item.hover.contents, item.ordinal)); + } finally { + ref.dispose(); + } } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b53a6c8b645..9c41e1a14bc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6844,8 +6844,14 @@ declare namespace monaco.languages { Parameter = 2 } - export interface InlayHint { + export interface InlayHintLabelPart { label: string; + collapsible?: boolean; + action?: Command | Location; + } + + export interface InlayHint { + label: string | InlayHintLabelPart[]; tooltip?: string | IMarkdownString; position: IPosition; kind: InlayHintKind; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 60cdd476675..3a448319f3c 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -23,6 +23,7 @@ import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy' import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { mixin } from 'vs/base/common/objects'; import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; +import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -557,7 +558,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return; } return { - hints: result.hints, + hints: revive(result.hints), dispose: () => { if (result.cacheId) { this._proxy.$releaseInlayHints(handle, result.cacheId); @@ -579,7 +580,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { ...hint, tooltip: result.tooltip, - label: result.label + label: revive(result.label) }; }; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a10fe9f4fcb..c497e643b2e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1276,6 +1276,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I WorkspaceEdit: extHostTypes.WorkspaceEdit, // proposed api types InlayHint: extHostTypes.InlayHint, + InlayHintLabelPart: extHostTypes.InlayHintLabelPart, InlayHintKind: extHostTypes.InlayHintKind, RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, ResolvedAuthority: extHostTypes.ResolvedAuthority, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3f8407431ac..f148381b4c6 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1504,7 +1504,7 @@ export interface ISignatureHelpContextDto { export interface IInlayHintDto { cacheId?: ChainedCacheId; - label: string; + label: string | modes.InlayHintLabelPart[]; tooltip?: string | IMarkdownString; position: IPosition; kind: modes.InlayHintKind; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 9c1e68debf7..4b6e53b0e6f 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -332,8 +332,8 @@ const newCommands: ApiCommand[] = [ new ApiCommand( 'vscode.executeInlayHintProvider', '_executeInlayHintProvider', 'Execute inlay hints provider', [ApiCommandArgument.Uri, ApiCommandArgument.Range], - new ApiCommandResult('A promise that resolves to an array of Inlay objects', result => { - return result.map(typeConverters.InlayHint.to); + new ApiCommandResult('A promise that resolves to an array of Inlay objects', (result, args, converter) => { + return result.map(typeConverters.InlayHint.to.bind(undefined, converter)); }) ), // --- notebooks diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 5bcf93a9dbc..073ab743129 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import type * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, InlayHintKind, Location } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/languages'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -1173,9 +1173,11 @@ class SignatureHelpAdapter { class InlayHintsAdapter { private _cache = new Cache('InlayHints'); + private readonly _disposables = new Map(); constructor( private readonly _documents: ExtHostDocuments, + private readonly _commands: CommandsConverter, private readonly _provider: vscode.InlayHintsProvider, ) { } @@ -1192,21 +1194,13 @@ class InlayHintsAdapter { // of results as they will leak return undefined; } - if (typeof this._provider.resolveInlayHint !== 'function') { - // no resolve -> no caching - return { hints: hints.map(typeConvert.InlayHint.from) }; - - } else { - // cache links for future resolving - const pid = this._cache.add(hints); - const result: extHostProtocol.IInlayHintsDto = { hints: [], cacheId: pid }; - for (let i = 0; i < hints.length; i++) { - const dto: extHostProtocol.IInlayHintDto = typeConvert.InlayHint.from(hints[i]); - dto.cacheId = [pid, i]; - result.hints.push(dto); - } - return result; + const pid = this._cache.add(hints); + this._disposables.set(pid, new DisposableStore()); + const result: extHostProtocol.IInlayHintsDto = { hints: [], cacheId: pid }; + for (let i = 0; i < hints.length; i++) { + result.hints.push(this._convertInlayHint(hints[i], [pid, i])); } + return result; } async resolveInlayHint(id: extHostProtocol.ChainedCacheId, token: CancellationToken) { @@ -1221,12 +1215,51 @@ class InlayHintsAdapter { if (!hint) { return undefined; } - return typeConvert.InlayHint.from(hint); + if (token.isCancellationRequested) { + return undefined; + } + return this._convertInlayHint(hint, id); } releaseHints(id: number): any { + this._disposables.get(id)?.dispose(); + this._disposables.delete(id); this._cache.delete(id); } + + private _convertInlayHint(hint: vscode.InlayHint, id: extHostProtocol.ChainedCacheId): extHostProtocol.IInlayHintDto { + + const disposables = this._disposables.get(id[0]); + if (!disposables) { + throw Error('DisposableStore is missing...'); + } + + const result: extHostProtocol.IInlayHintDto = { + label: '', // fill-in below + cacheId: id, + tooltip: hint.tooltip && typeConvert.MarkdownString.from(hint.tooltip), + position: typeConvert.Position.from(hint.position), + kind: typeConvert.InlayHintKind.from(hint.kind ?? InlayHintKind.Other), + whitespaceBefore: hint.whitespaceBefore, + whitespaceAfter: hint.whitespaceAfter, + }; + + if (typeof hint.label === 'string') { + result.label = hint.label; + } else { + result.label = hint.label.map(part => { + let r: modes.InlayHintLabelPart = { label: part.label }; + r.collapsible = part.collapsible; + if (Location.isLocation(part.action)) { + r.action = typeConvert.location.from(part.action); + } else if (part.action) { + r.action = this._commands.toInternal(part.action, disposables); + } + return r; + }); + } + return result; + } } class LinkProviderAdapter { @@ -2030,7 +2063,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF registerInlayHintsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider): vscode.Disposable { const eventHandle = typeof provider.onDidChangeInlayHints === 'function' ? this._nextHandle() : undefined; - const handle = this._addNewAdapter(new InlayHintsAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new InlayHintsAdapter(this._documents, this._commands.converter, provider), extension); this._proxy.$registerInlayHintsProvider(handle, this._transformDocumentSelector(selector), typeof provider.resolveInlayHint === 'function', eventHandle); let result = this._createDisposable(handle); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 072dfa84e35..bb200589630 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1152,20 +1152,9 @@ export namespace SignatureHelp { export namespace InlayHint { - export function from(hint: vscode.InlayHint): modes.InlayHint { - return { - label: hint.text, - tooltip: hint.tooltip && MarkdownString.from(hint.tooltip), - position: Position.from(hint.position), - kind: InlayHintKind.from(hint.kind ?? types.InlayHintKind.Other), - whitespaceBefore: hint.whitespaceBefore, - whitespaceAfter: hint.whitespaceAfter, - }; - } - - export function to(hint: modes.InlayHint): vscode.InlayHint { + export function to(converter: CommandsConverter, hint: modes.InlayHint): vscode.InlayHint { const res = new types.InlayHint( - hint.label, + typeof hint.label === 'string' ? hint.label : hint.label.map(InlayHintLabelPart.to.bind(undefined, converter)), Position.to(hint.position), InlayHintKind.to(hint.kind) ); @@ -1176,6 +1165,20 @@ export namespace InlayHint { } } +export namespace InlayHintLabelPart { + + export function to(converter: CommandsConverter, part: modes.InlayHintLabelPart): types.InlayHintLabelPart { + const result = new types.InlayHintLabelPart(part.label); + result.collapsible = part.collapsible; + if (modes.Command.is(part.action)) { + result.action = converter.fromInternal(part.action); + } else if (part.action) { + result.action = location.to(part.action); + } + return result; + } +} + export namespace InlayHintKind { export function from(kind: vscode.InlayHintKind): modes.InlayHintKind { return kind; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 789b7cc7527..99762f88a3d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -873,7 +873,7 @@ export enum DiagnosticSeverity { @es5ClassCompat export class Location { - static isLocation(thing: any): thing is Location { + static isLocation(thing: any): thing is vscode.Location { if (thing instanceof Location) { return true; } @@ -1421,16 +1421,29 @@ export enum InlayHintKind { } @es5ClassCompat -export class InlayHint { - text: string; +export class InlayHintLabelPart { + label: string; + collapsible?: boolean; + action?: vscode.Command | Location; // invokes provider + constructor(label: string) { + this.label = label; + } + toString(): string { + return this.label; + } +} + +@es5ClassCompat +export class InlayHint implements vscode.InlayHint { + label: string | InlayHintLabelPart[]; tooltip?: string | vscode.MarkdownString; position: Position; kind?: vscode.InlayHintKind; whitespaceBefore?: boolean; whitespaceAfter?: boolean; - constructor(text: string, position: Position, kind?: vscode.InlayHintKind) { - this.text = text; + constructor(label: string | InlayHintLabelPart[], position: Position, kind?: vscode.InlayHintKind) { + this.label = label; this.position = position; this.kind = kind; } diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index fc0c761c36e..91e9cf2ce68 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -1248,7 +1248,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 1); const [first] = value; - assert.strictEqual(first.text, 'Foo'); + assert.strictEqual(first.label, 'Foo'); assert.strictEqual(first.position.line, 0); assert.strictEqual(first.position.character, 1); }); @@ -1273,11 +1273,11 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 2); const [first, second] = value; - assert.strictEqual(first.text, 'Foo'); + assert.strictEqual(first.label, 'Foo'); assert.strictEqual(first.position.line, 0); assert.strictEqual(first.position.character, 1); - assert.strictEqual(second.text, 'Bar'); + assert.strictEqual(second.label, 'Bar'); assert.strictEqual(second.position.line, 10); assert.strictEqual(second.position.character, 11); }); @@ -1300,7 +1300,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(value.length, 1); const [first] = value; - assert.strictEqual(first.text, 'Foo'); + assert.strictEqual(first.label, 'Foo'); assert.strictEqual(first.position.line, 0); assert.strictEqual(first.position.character, 1); }); diff --git a/src/vscode-dts/vscode.proposed.inlayHints.d.ts b/src/vscode-dts/vscode.proposed.inlayHints.d.ts index 95f7c964f62..75630e4901f 100644 --- a/src/vscode-dts/vscode.proposed.inlayHints.d.ts +++ b/src/vscode-dts/vscode.proposed.inlayHints.d.ts @@ -34,15 +34,22 @@ declare module 'vscode' { Parameter = 2, } + export class InlayHintLabelPart { + label: string; + collapsible?: boolean; + // todo@api better name! + action?: Command | Location; // invokes provider + constructor(label: string); + } + /** * Inlay hint information. */ export class InlayHint { /** - * The text of the hint. + * */ - // todo@API label? - text: string; + label: string | InlayHintLabelPart[]; /** * The tooltip text when you hover over this item. */ @@ -65,7 +72,7 @@ declare module 'vscode' { whitespaceAfter?: boolean; // todo@API make range first argument - constructor(text: string, position: Position, kind?: InlayHintKind); + constructor(label: string | InlayHintLabelPart[], position: Position, kind?: InlayHintKind); } /**