Path: blob/main/src/vs/editor/contrib/inlayHints/browser/inlayHints.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../base/common/cancellation.js';6import { CancellationError, onUnexpectedExternalError } from '../../../../base/common/errors.js';7import { DisposableStore } from '../../../../base/common/lifecycle.js';8import { IPosition, Position } from '../../../common/core/position.js';9import { Range } from '../../../common/core/range.js';10import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js';11import { InlayHint, InlayHintList, InlayHintsProvider, Command } from '../../../common/languages.js';12import { ITextModel } from '../../../common/model.js';13import { Schemas } from '../../../../base/common/network.js';14import { URI } from '../../../../base/common/uri.js';1516export class InlayHintAnchor {17constructor(readonly range: Range, readonly direction: 'before' | 'after') { }18}1920export class InlayHintItem {2122private _isResolved: boolean = false;23private _currentResolve?: Promise<void>;2425constructor(readonly hint: InlayHint, readonly anchor: InlayHintAnchor, readonly provider: InlayHintsProvider) { }2627with(delta: { anchor: InlayHintAnchor }): InlayHintItem {28const result = new InlayHintItem(this.hint, delta.anchor, this.provider);29result._isResolved = this._isResolved;30result._currentResolve = this._currentResolve;31return result;32}3334async resolve(token: CancellationToken): Promise<void> {35if (typeof this.provider.resolveInlayHint !== 'function') {36return;37}38if (this._currentResolve) {39// wait for an active resolve operation and try again40// when that's done.41await this._currentResolve;42if (token.isCancellationRequested) {43return;44}45return this.resolve(token);46}47if (!this._isResolved) {48this._currentResolve = this._doResolve(token)49.finally(() => this._currentResolve = undefined);50}51await this._currentResolve;52}5354private async _doResolve(token: CancellationToken) {55try {56const newHint = await Promise.resolve(this.provider.resolveInlayHint!(this.hint, token));57this.hint.tooltip = newHint?.tooltip ?? this.hint.tooltip;58this.hint.label = newHint?.label ?? this.hint.label;59this.hint.textEdits = newHint?.textEdits ?? this.hint.textEdits;60this._isResolved = true;61} catch (err) {62onUnexpectedExternalError(err);63this._isResolved = false;64}65}66}6768export class InlayHintsFragments {6970private static _emptyInlayHintList: InlayHintList = Object.freeze({ dispose() { }, hints: [] });7172static async create(registry: LanguageFeatureRegistry<InlayHintsProvider>, model: ITextModel, ranges: Range[], token: CancellationToken): Promise<InlayHintsFragments> {7374const data: [InlayHintList, InlayHintsProvider][] = [];7576const promises = registry.ordered(model).reverse().map(provider => ranges.map(async range => {77try {78const result = await provider.provideInlayHints(model, range, token);79if (result?.hints.length || provider.onDidChangeInlayHints) {80data.push([result ?? InlayHintsFragments._emptyInlayHintList, provider]);81}82} catch (err) {83onUnexpectedExternalError(err);84}85}));8687await Promise.all(promises.flat());8889if (token.isCancellationRequested || model.isDisposed()) {90throw new CancellationError();91}9293return new InlayHintsFragments(ranges, data, model);94}9596private readonly _disposables = new DisposableStore();9798readonly items: readonly InlayHintItem[];99readonly ranges: readonly Range[];100readonly provider: Set<InlayHintsProvider>;101102private constructor(ranges: Range[], data: [InlayHintList, InlayHintsProvider][], model: ITextModel) {103this.ranges = ranges;104this.provider = new Set();105const items: InlayHintItem[] = [];106for (const [list, provider] of data) {107this._disposables.add(list);108this.provider.add(provider);109110for (const hint of list.hints) {111// compute the range to which the item should be attached to112const position = model.validatePosition(hint.position);113let direction: 'before' | 'after' = 'before';114115const wordRange = InlayHintsFragments._getRangeAtPosition(model, position);116let range: Range;117118if (wordRange.getStartPosition().isBefore(position)) {119range = Range.fromPositions(wordRange.getStartPosition(), position);120direction = 'after';121} else {122range = Range.fromPositions(position, wordRange.getEndPosition());123direction = 'before';124}125126items.push(new InlayHintItem(hint, new InlayHintAnchor(range, direction), provider));127}128}129this.items = items.sort((a, b) => Position.compare(a.hint.position, b.hint.position));130}131132dispose(): void {133this._disposables.dispose();134}135136private static _getRangeAtPosition(model: ITextModel, position: IPosition): Range {137const line = position.lineNumber;138const word = model.getWordAtPosition(position);139if (word) {140// always prefer the word range141return new Range(line, word.startColumn, line, word.endColumn);142}143144model.tokenization.tokenizeIfCheap(line);145const tokens = model.tokenization.getLineTokens(line);146const offset = position.column - 1;147const idx = tokens.findTokenIndexAtOffset(offset);148149let start = tokens.getStartOffset(idx);150let end = tokens.getEndOffset(idx);151152if (end - start === 1) {153// single character token, when at its end try leading/trailing token instead154if (start === offset && idx > 1) {155// leading token156start = tokens.getStartOffset(idx - 1);157end = tokens.getEndOffset(idx - 1);158} else if (end === offset && idx < tokens.getCount() - 1) {159// trailing token160start = tokens.getStartOffset(idx + 1);161end = tokens.getEndOffset(idx + 1);162}163}164165return new Range(line, start + 1, line, end + 1);166}167}168169export function asCommandLink(command: Command): string {170return URI.from({171scheme: Schemas.command,172path: command.id,173query: command.arguments && encodeURIComponent(JSON.stringify(command.arguments))174}).toString();175}176177178