diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 0bccffa3e35..3223c3cced1 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -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 { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/commandIds.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/commandIds.ts index 4902ea81c66..e0b555a4383 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/commandIds.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/commandIds.ts @@ -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'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 8f8110ebd84..485b5f787b9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -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 || [])) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 80f78e37d59..0e80cc8ca4e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -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 | 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); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts index b6d17e52264..bc6409a89fe 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts @@ -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, ); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index 2a8813dca58..694d1a1d5ac 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -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 { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/renameSymbolProcessor.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/renameSymbolProcessor.ts new file mode 100644 index 00000000000..6ceb1ff242f --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/renameSymbolProcessor.ts @@ -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 { + 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)) + }; +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts index b8f1ca93c6c..4ef2df054bd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts @@ -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' }; diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index bebb8b7a54a..da06d82d241 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -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( 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); } diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index b6a83904335..d72d531842c 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -128,6 +128,11 @@ export async function rename(registry: LanguageFeatureRegistry, return skeleton.provideRenameEdits(newName, CancellationToken.None); } +export async function prepareRename(registry: LanguageFeatureRegistry, model: ITextModel, position: Position): Promise { + const skeleton = new RenameSkeleton(model, position, registry); + return skeleton.resolveRenameLocation(CancellationToken.None); +} + // --- register actions and commands class RenameController implements IEditorContribution { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 53b43340132..99a75fc342e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -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 { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index a60dbde6c15..7f13640c1c7 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -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)), }; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 4875d417041..88b8ead1941 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -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 => { diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index ccd4c500fec..a2fc913767c 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -66,6 +66,8 @@ declare module 'vscode' { completeBracketPairs?: boolean; warning?: InlineCompletionWarning; + + supportsRename?: boolean; }