/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { CancellationError, onUnexpectedExternalError } from '../../../../base/common/errors.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; export class InlayHintAnchor { constructor(range, direction) { this.range = range; this.direction = direction; } } export class InlayHintItem { constructor(hint, anchor, provider) { this.hint = hint; this.anchor = anchor; this.provider = provider; this._isResolved = false; } with(delta) { const result = new InlayHintItem(this.hint, delta.anchor, this.provider); result._isResolved = this._isResolved; result._currentResolve = this._currentResolve; return result; } resolve(token) { return __awaiter(this, void 0, void 0, function* () { if (typeof this.provider.resolveInlayHint !== 'function') { return; } if (this._currentResolve) { // wait for an active resolve operation and try again // when that's done. yield this._currentResolve; if (token.isCancellationRequested) { return; } return this.resolve(token); } if (!this._isResolved) { this._currentResolve = this._doResolve(token) .finally(() => this._currentResolve = undefined); } yield this._currentResolve; }); } _doResolve(token) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { try { const newHint = yield Promise.resolve(this.provider.resolveInlayHint(this.hint, token)); this.hint.tooltip = (_a = newHint === null || newHint === void 0 ? void 0 : newHint.tooltip) !== null && _a !== void 0 ? _a : this.hint.tooltip; this.hint.label = (_b = newHint === null || newHint === void 0 ? void 0 : newHint.label) !== null && _b !== void 0 ? _b : this.hint.label; this._isResolved = true; } catch (err) { onUnexpectedExternalError(err); this._isResolved = false; } }); } } export class InlayHintsFragments { constructor(ranges, data, model) { this._disposables = new DisposableStore(); this.ranges = ranges; this.provider = new Set(); const items = []; for (const [list, provider] of data) { this._disposables.add(list); this.provider.add(provider); for (const hint of list.hints) { // compute the range to which the item should be attached to const position = model.validatePosition(hint.position); let direction = 'before'; const wordRange = InlayHintsFragments._getRangeAtPosition(model, position); let range; if (wordRange.getStartPosition().isBefore(position)) { range = Range.fromPositions(wordRange.getStartPosition(), position); direction = 'after'; } else { range = Range.fromPositions(position, wordRange.getEndPosition()); direction = 'before'; } items.push(new InlayHintItem(hint, new InlayHintAnchor(range, direction), provider)); } } this.items = items.sort((a, b) => Position.compare(a.hint.position, b.hint.position)); } static create(registry, model, ranges, token) { return __awaiter(this, void 0, void 0, function* () { const data = []; const promises = registry.ordered(model).reverse().map(provider => ranges.map((range) => __awaiter(this, void 0, void 0, function* () { try { const result = yield provider.provideInlayHints(model, range, token); if (result === null || result === void 0 ? void 0 : result.hints.length) { data.push([result, provider]); } } catch (err) { onUnexpectedExternalError(err); } }))); yield Promise.all(promises.flat()); if (token.isCancellationRequested || model.isDisposed()) { throw new CancellationError(); } return new InlayHintsFragments(ranges, data, model); }); } dispose() { this._disposables.dispose(); } static _getRangeAtPosition(model, position) { const line = position.lineNumber; const word = model.getWordAtPosition(position); if (word) { // always prefer the word range return new Range(line, word.startColumn, line, word.endColumn); } model.tokenization.tokenizeIfCheap(line); const tokens = model.tokenization.getLineTokens(line); const offset = position.column - 1; const idx = tokens.findTokenIndexAtOffset(offset); let start = tokens.getStartOffset(idx); let end = tokens.getEndOffset(idx); if (end - start === 1) { // single character token, when at its end try leading/trailing token instead if (start === offset && idx > 1) { // leading token start = tokens.getStartOffset(idx - 1); end = tokens.getEndOffset(idx - 1); } else if (end === offset && idx < tokens.getCount() - 1) { // trailing token start = tokens.getStartOffset(idx + 1); end = tokens.getEndOffset(idx + 1); } } return new Range(line, start + 1, line, end + 1); } } export function asCommandLink(command) { return URI.from({ scheme: Schemas.command, path: command.id, query: command.arguments && encodeURIComponent(JSON.stringify(command.arguments)) }).toString(); }