Merge pull request #279147 from microsoft/benibenj/wet-skunk

Add support for rename inline suggestions
This commit is contained in:
Benjamin Christopher Simmonds
2025-11-24 15:53:22 +01:00
committed by GitHub
14 changed files with 286 additions and 15 deletions
+7 -2
View File
@@ -837,7 +837,9 @@ export interface InlineCompletion {
readonly warning?: InlineCompletionWarning;
readonly hint?: InlineCompletionHint;
readonly hint?: IInlineCompletionHint;
readonly supportsRename?: boolean;
/**
* Used for telemetry.
@@ -855,7 +857,7 @@ export enum InlineCompletionHintStyle {
Label = 2
}
export interface InlineCompletionHint {
export interface IInlineCompletionHint {
/** Refers to the current document. */
range: IRange;
style: InlineCompletionHintStyle;
@@ -1052,6 +1054,9 @@ export type LifetimeSummary = {
typingIntervalCharacterCount: number;
selectedSuggestionInfo: boolean;
availableProviders: string;
renameCreated: boolean;
renameDuration?: number;
renameTimedOut: boolean;
};
export interface CodeAction {
@@ -14,3 +14,5 @@ export const jumpToNextInlineEditId = 'editor.action.inlineSuggest.jump';
export const hideInlineCompletionId = 'editor.action.inlineSuggest.hide';
export const toggleShowCollapsedId = 'editor.action.inlineSuggest.toggleShowCollapsed';
export const renameSymbolCommandId = 'editor.action.inlineSuggest.renameSymbol';
@@ -947,6 +947,12 @@ export class InlineCompletionsModel extends Disposable {
// Reset before invoking the command, as the command might cause a follow up trigger (which we don't want to reset).
this.stop();
if (completion.renameCommand) {
await this._commandService
.executeCommand(completion.renameCommand.id, ...(completion.renameCommand.arguments || []))
.then(undefined, onUnexpectedExternalError);
}
if (completion.command) {
await this._commandService
.executeCommand(completion.command.id, ...(completion.command.arguments || []))
@@ -33,6 +33,7 @@ import { InlineCompletionEndOfLifeEvent, sendInlineCompletionsEndOfLifeTelemetry
import { wait } from '../utils.js';
import { InlineSuggestionIdentity, InlineSuggestionItem } from './inlineSuggestionItem.js';
import { InlineCompletionContextWithoutUuid, InlineSuggestRequestInfo, provideInlineCompletions, runWhenCancelled } from './provideInlineCompletions.js';
import { RenameSymbolProcessor } from './renameSymbolProcessor.js';
export class InlineCompletionsSource extends Disposable {
private static _requestId = 0;
@@ -75,6 +76,8 @@ export class InlineCompletionsSource extends Disposable {
public readonly inlineCompletions = this._state.map(this, v => v.inlineCompletions);
public readonly suggestWidgetInlineCompletions = this._state.map(this, v => v.suggestWidgetInlineCompletions);
private readonly _renameProcessor: RenameSymbolProcessor;
private _completionsEnabled: Record<string, boolean> | undefined = undefined;
constructor(
@@ -98,6 +101,8 @@ export class InlineCompletionsSource extends Disposable {
'editor.inlineSuggest.logFetch.commandId'
));
this._renameProcessor = this._store.add(this._instantiationService.createInstance(RenameSymbolProcessor));
this.clearOperationOnTextModelChange.recomputeInitiallyAndOnChange(this._store);
const enablementSetting = product.defaultChatAgent?.completionsEnablementSetting ?? undefined;
@@ -225,7 +230,7 @@ export class InlineCompletionsSource extends Disposable {
let shouldStopEarly = false;
let producedSuggestion = false;
const suggestions: InlineSuggestionItem[] = [];
const providerSuggestions: InlineSuggestionItem[] = [];
for await (const list of providerResult.lists) {
if (!list) {
continue;
@@ -245,7 +250,7 @@ export class InlineCompletionsSource extends Disposable {
}
const i = InlineSuggestionItem.create(item, this._textModel);
suggestions.push(i);
providerSuggestions.push(i);
// Stop after first visible inline completion
if (!i.isInlineEdit && !i.showInlineEditMenu && context.triggerKind === InlineCompletionTriggerKind.Automatic) {
if (i.isVisible(this._textModel, this._cursorPosition.get())) {
@@ -259,6 +264,10 @@ export class InlineCompletionsSource extends Disposable {
}
}
const suggestions: InlineSuggestionItem[] = await Promise.all(providerSuggestions.map(async s => {
return this._renameProcessor.proposeRenameRefactoring(this._textModel, s);
}));
providerResult.cancelAndDispose({ kind: 'lostRace' });
if (this._loggingEnabled.get() || this._structuredFetchLogger.isEnabled.get()) {
@@ -440,6 +449,9 @@ export class InlineCompletionsSource extends Disposable {
disjointReplacements: undefined,
sameShapeReplacements: undefined,
notShownReason: undefined,
renameCreated: false,
renameDuration: undefined,
renameTimedOut: false,
};
const dataChannel = this._instantiationService.createInstance(DataChannelForwardingTelemetryService);
@@ -20,11 +20,11 @@ import { getPositionOffsetTransformerFromTextModel } from '../../../../common/co
import { PositionOffsetTransformerBase } from '../../../../common/core/text/positionToOffset.js';
import { TextLength } from '../../../../common/core/text/textLength.js';
import { linesDiffComputers } from '../../../../common/diff/linesDiffComputers.js';
import { Command, InlineCompletion, InlineCompletionHintStyle, InlineCompletionEndOfLifeReason, InlineCompletionTriggerKind, InlineCompletionWarning, PartialAcceptInfo, InlineCompletionHint } from '../../../../common/languages.js';
import { Command, InlineCompletion, InlineCompletionHintStyle, InlineCompletionEndOfLifeReason, InlineCompletionTriggerKind, InlineCompletionWarning, PartialAcceptInfo, IInlineCompletionHint } from '../../../../common/languages.js';
import { EndOfLinePreference, ITextModel } from '../../../../common/model.js';
import { TextModelText } from '../../../../common/model/textModelText.js';
import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js';
import { InlineSuggestData, InlineSuggestionList, PartialAcceptance, SnippetInfo } from './provideInlineCompletions.js';
import { InlineSuggestData, InlineSuggestionList, PartialAcceptance, RenameInfo, SnippetInfo } from './provideInlineCompletions.js';
import { singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js';
export type InlineSuggestionItem = InlineEditItem | InlineCompletionItem;
@@ -63,6 +63,8 @@ abstract class InlineSuggestionItemBase {
public get semanticId(): string { return this.hash; }
public get action(): Command | undefined { return this._sourceInlineCompletion.gutterMenuLinkAction; }
public get command(): Command | undefined { return this._sourceInlineCompletion.command; }
public get supportsRename(): boolean { return this._data.supportsRename; }
public get renameCommand(): Command | undefined { return this._data.renameCommand; }
public get warning(): InlineCompletionWarning | undefined { return this._sourceInlineCompletion.warning; }
public get showInlineEditMenu(): boolean { return !!this._sourceInlineCompletion.showInlineEditMenu; }
public get hash() {
@@ -133,6 +135,14 @@ abstract class InlineSuggestionItemBase {
public getSourceCompletion(): InlineCompletion {
return this._sourceInlineCompletion;
}
public setRenameProcessingInfo(info: RenameInfo): void {
this._data.setRenameProcessingInfo(info);
}
public withRename(command: Command, hint: InlineSuggestHint): InlineSuggestData {
return this._data.withRename(command, hint);
}
}
export class InlineSuggestionIdentity {
@@ -166,11 +176,11 @@ export class InlineSuggestionIdentity {
export class InlineSuggestHint {
public static create(displayLocation: InlineCompletionHint) {
public static create(hint: IInlineCompletionHint) {
return new InlineSuggestHint(
Range.lift(displayLocation.range),
displayLocation.content,
displayLocation.style,
Range.lift(hint.range),
hint.content,
hint.style,
);
}
@@ -6,7 +6,7 @@
import { assertNever } from '../../../../../base/common/assert.js';
import { AsyncIterableProducer } from '../../../../../base/common/async.js';
import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';
import { onUnexpectedExternalError } from '../../../../../base/common/errors.js';
import { BugIndicatingError, onUnexpectedExternalError } from '../../../../../base/common/errors.js';
import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';
import { prefixedUuid } from '../../../../../base/common/uuid.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
@@ -16,7 +16,7 @@ import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js';
import { Position } from '../../../../common/core/position.js';
import { Range } from '../../../../common/core/range.js';
import { TextReplacement } from '../../../../common/core/edits/textEdit.js';
import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary, ProviderId, InlineCompletionHint } from '../../../../common/languages.js';
import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary, ProviderId, IInlineCompletionHint, Command } from '../../../../common/languages.js';
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
import { ITextModel } from '../../../../common/model.js';
import { fixBracketsInLine } from '../../../../common/model/bracketPairsTextModelPart/fixBrackets.js';
@@ -246,6 +246,8 @@ function toInlineSuggestData(
source,
context,
inlineCompletion.isInlineEdit ?? false,
inlineCompletion.supportsRename ?? false,
undefined,
requestInfo,
providerRequestInfo,
inlineCompletion.correlationId,
@@ -273,6 +275,12 @@ export type PartialAcceptance = {
ratio: number;
};
export type RenameInfo = {
createdRename: boolean;
duration: number;
timedOut?: boolean;
};
export type InlineSuggestViewData = {
editorType: InlineCompletionEditorType;
renderData?: InlineCompletionViewData;
@@ -294,19 +302,22 @@ export class InlineSuggestData {
private _isPreceeded = false;
private _partiallyAcceptedCount = 0;
private _partiallyAcceptedSinceOriginal: PartialAcceptance = { characters: 0, ratio: 0, count: 0 };
private _renameInfo: RenameInfo | undefined = undefined;
constructor(
public readonly range: Range,
public readonly insertText: string,
public readonly snippetInfo: SnippetInfo | undefined,
public readonly uri: URI | undefined,
public readonly hint: InlineCompletionHint | undefined,
public readonly hint: IInlineCompletionHint | undefined,
public readonly additionalTextEdits: readonly ISingleEditOperation[],
public readonly sourceInlineCompletion: InlineCompletion,
public readonly source: InlineSuggestionList,
public readonly context: InlineCompletionContext,
public readonly isInlineEdit: boolean,
public readonly supportsRename: boolean,
public readonly renameCommand: Command | undefined,
private readonly _requestInfo: InlineSuggestRequestInfo,
private readonly _providerRequestInfo: InlineSuggestProviderRequestInfo,
@@ -397,6 +408,9 @@ export class InlineSuggestData {
requestReason: this._requestInfo.reason,
viewKind: this._viewData.viewKind,
notShownReason: this._notShownReason,
renameCreated: this._renameInfo?.createdRename ?? false,
renameDuration: this._renameInfo?.duration,
renameTimedOut: this._renameInfo?.timedOut ?? false,
typingInterval: this._requestInfo.typingInterval,
typingIntervalCharacterCount: this._requestInfo.typingIntervalCharacterCount,
availableProviders: this._requestInfo.availableProviders.map(p => p.toString()).join(','),
@@ -457,6 +471,33 @@ export class InlineSuggestData {
this._showUncollapsedDuration += timeNow - this._showUncollapsedStartTime;
this._showUncollapsedStartTime = undefined;
}
public setRenameProcessingInfo(info: RenameInfo): void {
if (this._renameInfo) {
throw new BugIndicatingError('Rename info has already been set.');
}
this._renameInfo = info;
}
public withRename(command: Command, hint: IInlineCompletionHint): InlineSuggestData {
return new InlineSuggestData(
new Range(1, 1, 1, 1),
'',
this.snippetInfo,
this.uri,
hint,
this.additionalTextEdits,
this.sourceInlineCompletion,
this.source,
this.context,
this.isInlineEdit,
this.supportsRename,
command,
this._requestInfo,
this._providerRequestInfo,
this._correlationId,
);
}
}
export interface SnippetInfo {
@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { raceTimeout } from '../../../../../base/common/async.js';
import { LcsDiff, StringDiffSequence } from '../../../../../base/common/diff/diff.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { localize } from '../../../../../nls.js';
import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js';
import { ServicesAccessor } from '../../../../browser/editorExtensions.js';
import { IBulkEditService } from '../../../../browser/services/bulkEditService.js';
import { TextEdit } from '../../../../common/core/edits/textEdit.js';
import { Position } from '../../../../common/core/position.js';
import { Range } from '../../../../common/core/range.js';
import { StandardTokenType } from '../../../../common/encodedTokenAttributes.js';
import { Command, InlineCompletionHintStyle } from '../../../../common/languages.js';
import { ITextModel } from '../../../../common/model.js';
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
import { prepareRename, rename } from '../../../rename/browser/rename.js';
import { renameSymbolCommandId } from '../controller/commandIds.js';
import { InlineSuggestHint, InlineSuggestionItem } from './inlineSuggestionItem.js';
type SingleEdits = {
renames: { edits: TextEdit[]; position: Position; oldName: string; newName: string };
others: { edits: TextEdit[] };
};
export class RenameSymbolProcessor extends Disposable {
constructor(
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IBulkEditService bulkEditService: IBulkEditService,
) {
super();
this._register(CommandsRegistry.registerCommand(renameSymbolCommandId, async (_: ServicesAccessor, textModel: ITextModel, position: Position, newName: string) => {
try {
const result = await rename(this._languageFeaturesService.renameProvider, textModel, position, newName);
if (result.rejectReason) {
return;
}
bulkEditService.apply(result);
} catch (error) {
// The actual rename failed we should log this.
}
}));
}
public async proposeRenameRefactoring(textModel: ITextModel, suggestItem: InlineSuggestionItem): Promise<InlineSuggestionItem> {
if (!suggestItem.supportsRename) {
return suggestItem;
}
const start = Date.now();
const edits = this.createSingleEdits(textModel, suggestItem.editRange, suggestItem.insertText);
if (edits === undefined || edits.renames.edits.length === 0) {
return suggestItem;
}
const { oldName, newName, position } = edits.renames;
let timedOut = false;
const loc = await raceTimeout(prepareRename(this._languageFeaturesService.renameProvider, textModel, position), 1000, () => { timedOut = true; });
const renamePossible = loc !== undefined && !loc.rejectReason;
suggestItem.setRenameProcessingInfo({ createdRename: renamePossible, duration: Date.now() - start, timedOut });
if (!renamePossible) {
return suggestItem;
}
const hintRange = edits.renames.edits[0].replacements[0].range;
const label = localize('renameSymbol', "Rename '{0}' to '{1}'", oldName, newName);
const command: Command = {
id: renameSymbolCommandId,
title: label,
arguments: [textModel, position, newName],
};
const hint = InlineSuggestHint.create({ range: hintRange, content: label, style: InlineCompletionHintStyle.Code });
return InlineSuggestionItem.create(suggestItem.withRename(command, hint), textModel);
}
private createSingleEdits(textModel: ITextModel, nesRange: Range, modifiedText: string): SingleEdits | undefined {
const others: TextEdit[] = [];
const renames: TextEdit[] = [];
let oldName: string | undefined = undefined;
let newName: string | undefined = undefined;
let position: Position | undefined = undefined;
const originalText = textModel.getValueInRange(nesRange);
const nesOffset = textModel.getOffsetAt(nesRange.getStartPosition());
const { changes } = (new LcsDiff(new StringDiffSequence(originalText), new StringDiffSequence(modifiedText))).ComputeDiff(true);
if (changes.length === 0) {
return undefined;
}
let tokenDiff: number = 0;
for (const change of changes) {
const startOffset = nesOffset + change.originalStart;
const startPos = textModel.getPositionAt(startOffset);
const wordRange = textModel.getWordAtPosition(startPos);
// If we don't have a word range at the start position of the current document then we
// don't treat it as a rename assuming that the rename refactoring will fail as well since
// there can't be an identifier at that position.
if (wordRange === null) {
return undefined;
}
const endOffset = startOffset + change.originalLength;
const endPos = textModel.getPositionAt(endOffset);
const range = Range.fromPositions(startPos, endPos);
const text = modifiedText.substring(change.modifiedStart, change.modifiedStart + change.modifiedLength);
const tokenInfo = getTokenAtPosition(textModel, startPos);
if (tokenInfo.type === StandardTokenType.Other) {
let identifier = textModel.getValueInRange(tokenInfo.range);
if (oldName === undefined) {
oldName = identifier;
} else if (oldName !== identifier) {
return undefined;
}
// We assume that the new name starts at the same position as the old name from a token range perspective.
const diff = text.length - change.originalLength;
const tokenStartPos = textModel.getOffsetAt(tokenInfo.range.getStartPosition()) - nesOffset + tokenDiff;
const tokenEndPos = textModel.getOffsetAt(tokenInfo.range.getEndPosition()) - nesOffset + tokenDiff;
identifier = modifiedText.substring(tokenStartPos, tokenEndPos + diff);
if (newName === undefined) {
newName = identifier;
} else if (newName !== identifier) {
return undefined;
}
if (position === undefined) {
position = tokenInfo.range.getStartPosition();
}
renames.push(TextEdit.replace(range, text));
tokenDiff += diff;
} else {
others.push(TextEdit.replace(range, text));
}
}
if (oldName === undefined || newName === undefined || position === undefined) {
return undefined;
}
return {
renames: { edits: renames, position, oldName, newName },
others: { edits: others }
};
}
}
function getTokenAtPosition(textModel: ITextModel, position: Position): { type: StandardTokenType; range: Range } {
textModel.tokenization.tokenizeIfCheap(position.lineNumber);
const tokens = textModel.tokenization.getLineTokens(position.lineNumber);
const idx = tokens.findTokenIndexAtOffset(position.column - 1);
return {
type: tokens.getStandardTokenType(idx),
range: new Range(position.lineNumber, 1 + tokens.getStartOffset(idx), position.lineNumber, 1 + tokens.getEndOffset(idx))
};
}
@@ -39,6 +39,9 @@ export type InlineCompletionEndOfLifeEvent = {
preceeded: boolean | undefined;
superseded: boolean | undefined;
notShownReason: string | undefined;
renameCreated: boolean;
renameDuration: number | undefined;
renameTimedOut: boolean;
// rendering
viewKind: string | undefined;
cursorColumnDistance: number | undefined;
@@ -79,6 +82,9 @@ type InlineCompletionsEndOfLifeClassification = {
requestReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason for the inline completion request' };
typingInterval: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The average typing interval of the user at the moment the inline completion was requested' };
typingIntervalCharacterCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The character count involved in the typing interval calculation' };
renameCreated: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether a rename operation was created' };
renameDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration of the rename processor' };
renameTimedOut: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the rename prepare operation timed out' };
superseded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was superseded by another one' };
editorType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the editor where the inline completion was shown' };
viewKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of the view where the inline completion was shown' };
@@ -25,6 +25,7 @@ import { TextEdit } from '../../../../common/core/edits/textEdit.js';
import { BugIndicatingError } from '../../../../../base/common/errors.js';
import { PositionOffsetTransformer } from '../../../../common/core/text/positionToOffset.js';
import { InlineSuggestionsView } from '../../browser/view/inlineSuggestionsView.js';
import { IBulkEditService } from '../../../../browser/services/bulkEditService.js';
export class MockInlineCompletionsProvider implements InlineCompletionsProvider {
private returnValue: InlineCompletion[] = [];
@@ -243,6 +244,13 @@ export async function withAsyncTestCodeEditorAndInlineCompletionsModel<T>(
playSignal: async () => { },
isSoundEnabled(signal: unknown) { return false; },
} as any);
options.serviceCollection.set(IBulkEditService, {
apply: async () => { throw new Error('IBulkEditService.apply not implemented'); },
hasPreviewHandler: () => { throw new Error('IBulkEditService.hasPreviewHandler not implemented'); },
setPreviewHandler: () => { throw new Error('IBulkEditService.setPreviewHandler not implemented'); },
_serviceBrand: undefined,
});
const d = languageFeaturesService.inlineCompletionsProvider.register({ pattern: '**' }, options.provider);
disposableStore.add(d);
}
@@ -128,6 +128,11 @@ export async function rename(registry: LanguageFeatureRegistry<RenameProvider>,
return skeleton.provideRenameEdits(newName, CancellationToken.None);
}
export async function prepareRename(registry: LanguageFeatureRegistry<RenameProvider>, model: ITextModel, position: Position): Promise<RenameLocation & Rejection | undefined> {
const skeleton = new RenameSkeleton(model, position, registry);
return skeleton.resolveRenameLocation(CancellationToken.None);
}
// --- register actions and commands
class RenameController implements IEditorContribution {
+6 -2
View File
@@ -7555,7 +7555,8 @@ declare namespace monaco.languages {
/** Only show the inline suggestion when the cursor is in the showRange. */
readonly showRange?: IRange;
readonly warning?: InlineCompletionWarning;
readonly hint?: InlineCompletionHint;
readonly hint?: IInlineCompletionHint;
readonly supportsRename?: boolean;
/**
* Used for telemetry.
*/
@@ -7572,7 +7573,7 @@ declare namespace monaco.languages {
Label = 2
}
export interface InlineCompletionHint {
export interface IInlineCompletionHint {
/** Refers to the current document. */
range: IRange;
style: InlineCompletionHintStyle;
@@ -7694,6 +7695,9 @@ declare namespace monaco.languages {
typingIntervalCharacterCount: number;
selectedSuggestionInfo: boolean;
availableProviders: string;
renameCreated: boolean;
renameDuration?: number;
renameTimedOut: boolean;
};
export interface CodeAction {
@@ -752,6 +752,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
: reason.kind === InlineCompletionEndOfLifeReasonKind.Ignored ? 'ignored' : undefined,
noSuggestionReason: undefined,
notShownReason: lifetimeSummary.notShownReason,
renameCreated: lifetimeSummary.renameCreated,
renameDuration: lifetimeSummary.renameDuration,
renameTimedOut: lifetimeSummary.renameTimedOut,
...forwardToChannelIf(isCopilotLikeExtension(extensionId)),
};
@@ -1447,6 +1447,7 @@ class InlineCompletionAdapter {
correlationId: this._isAdditionsProposedApiEnabled ? item.correlationId : undefined,
suggestionId: undefined,
uri: (this._isAdditionsProposedApiEnabled && item.uri) ? item.uri : undefined,
supportsRename: this._isAdditionsProposedApiEnabled ? item.supportsRename : false,
});
}),
commands: commands.map(c => {
@@ -66,6 +66,8 @@ declare module 'vscode' {
completeBracketPairs?: boolean;
warning?: InlineCompletionWarning;
supportsRename?: boolean;
}