Skip to content

Commit 9bd6a83

Browse files
authored
Merge pull request #280171 from microsoft/dbaeumer/spotless-snipe-gray
Use server plugin to decide if a a symbol can be renamed using NES
2 parents 24c0076 + 843c29e commit 9bd6a83

File tree

3 files changed

+120
-12
lines changed

3 files changed

+120
-12
lines changed

src/vs/editor/contrib/inlineCompletions/browser/model/renameSymbolProcessor.ts

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,44 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { raceTimeout } from '../../../../../base/common/async.js';
7+
import { CancellationTokenSource } from '../../../../../base/common/cancellation.js';
78
import { LcsDiff, StringDiffSequence } from '../../../../../base/common/diff/diff.js';
89
import { Disposable } from '../../../../../base/common/lifecycle.js';
910
import { localize } from '../../../../../nls.js';
10-
import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js';
11+
import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';
1112
import { ServicesAccessor } from '../../../../browser/editorExtensions.js';
1213
import { IBulkEditService } from '../../../../browser/services/bulkEditService.js';
1314
import { TextReplacement } from '../../../../common/core/edits/textEdit.js';
1415
import { Position } from '../../../../common/core/position.js';
1516
import { Range } from '../../../../common/core/range.js';
1617
import { StandardTokenType } from '../../../../common/encodedTokenAttributes.js';
17-
import { Command } from '../../../../common/languages.js';
18+
import { Command, type Rejection, type WorkspaceEdit } from '../../../../common/languages.js';
1819
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
1920
import { ITextModel } from '../../../../common/model.js';
2021
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
2122
import { EditSources, TextModelEditSource } from '../../../../common/textModelEditSource.js';
22-
import { hasProvider, prepareRename, rename } from '../../../rename/browser/rename.js';
23+
import { hasProvider, rawRename } from '../../../rename/browser/rename.js';
2324
import { renameSymbolCommandId } from '../controller/commandIds.js';
2425
import { InlineSuggestionItem } from './inlineSuggestionItem.js';
2526
import { IInlineSuggestDataActionEdit } from './provideInlineCompletions.js';
2627

28+
enum RenameKind {
29+
no = 'no',
30+
yes = 'yes',
31+
maybe = 'maybe'
32+
}
33+
34+
namespace RenameKind {
35+
export function fromString(value: string): RenameKind {
36+
switch (value) {
37+
case 'no': return RenameKind.no;
38+
case 'yes': return RenameKind.yes;
39+
case 'maybe': return RenameKind.maybe;
40+
default: return RenameKind.no;
41+
}
42+
}
43+
}
44+
2745
export type RenameEdits = {
2846
renames: { edits: TextReplacement[]; position: Position; oldName: string; newName: string };
2947
others: { edits: TextReplacement[] };
@@ -203,22 +221,77 @@ export class RenameInferenceEngine {
203221
}
204222
}
205223

224+
class RenameSymbolRunnable {
225+
226+
private readonly _cancellationTokenSource: CancellationTokenSource;
227+
private readonly _promise: Promise<WorkspaceEdit & Rejection>;
228+
private _result: WorkspaceEdit & Rejection | undefined = undefined;
229+
230+
constructor(languageFeaturesService: ILanguageFeaturesService, textModel: ITextModel, position: Position, newName: string, source: TextModelEditSource) {
231+
this._cancellationTokenSource = new CancellationTokenSource();
232+
this._promise = rawRename(languageFeaturesService.renameProvider, textModel, position, newName, this._cancellationTokenSource.token);
233+
}
234+
235+
public cancel(): void {
236+
this._cancellationTokenSource.cancel();
237+
}
238+
239+
public async getCount(): Promise<number> {
240+
const result = await this.getResult();
241+
if (result === undefined) {
242+
return 0;
243+
}
244+
245+
return result.edits.length;
246+
}
247+
248+
public async getWorkspaceEdit(): Promise<WorkspaceEdit | undefined> {
249+
return this.getResult();
250+
}
251+
252+
private async getResult(): Promise<WorkspaceEdit | undefined> {
253+
if (this._result === undefined) {
254+
this._result = await this._promise;
255+
}
256+
if (this._result.rejectReason) {
257+
return undefined;
258+
}
259+
return this._result;
260+
}
261+
}
262+
206263
export class RenameSymbolProcessor extends Disposable {
207264

208265
private readonly _renameInferenceEngine = new RenameInferenceEngine();
209266

267+
private _renameRunnable: { id: string; runnable: RenameSymbolRunnable } | undefined;
268+
210269
constructor(
270+
@ICommandService private readonly _commandService: ICommandService,
211271
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
212272
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
213273
@IBulkEditService bulkEditService: IBulkEditService,
214274
) {
215275
super();
216-
this._register(CommandsRegistry.registerCommand(renameSymbolCommandId, async (_: ServicesAccessor, textModel: ITextModel, position: Position, newName: string, source: TextModelEditSource) => {
217-
const result = await rename(this._languageFeaturesService.renameProvider, textModel, position, newName);
218-
if (result.rejectReason) {
276+
const self = this;
277+
this._register(CommandsRegistry.registerCommand(renameSymbolCommandId, async (_: ServicesAccessor, textModel: ITextModel, position: Position, newName: string, source: TextModelEditSource, id: string) => {
278+
if (self._renameRunnable === undefined) {
279+
return;
280+
}
281+
let workspaceEdit: WorkspaceEdit | undefined;
282+
if (self._renameRunnable.id !== id) {
283+
self._renameRunnable.runnable.cancel();
284+
self._renameRunnable = undefined;
285+
const runnable = new RenameSymbolRunnable(self._languageFeaturesService, textModel, position, newName, source);
286+
workspaceEdit = await runnable.getWorkspaceEdit();
287+
} else {
288+
workspaceEdit = await self._renameRunnable.runnable.getWorkspaceEdit();
289+
self._renameRunnable = undefined;
290+
}
291+
if (workspaceEdit === undefined) {
219292
return;
220293
}
221-
bulkEditService.apply(result, { reason: source });
294+
bulkEditService.apply(workspaceEdit, { reason: source });
222295
}));
223296
}
224297

@@ -243,9 +316,10 @@ export class RenameSymbolProcessor extends Disposable {
243316
}
244317

245318
const { oldName, newName, position, edits: renameEdits } = edits.renames;
319+
246320
let timedOut = false;
247-
const loc = await raceTimeout(prepareRename(this._languageFeaturesService.renameProvider, textModel, position), 1000, () => { timedOut = true; });
248-
const renamePossible = loc !== undefined && !loc.rejectReason && loc.text === oldName;
321+
const check = await raceTimeout<RenameKind>(this.checkRenamePrecondition(textModel, position, oldName, newName), 1000, () => { timedOut = true; });
322+
const renamePossible = check === RenameKind.yes || check === RenameKind.maybe;
249323

250324
suggestItem.setRenameProcessingInfo({
251325
createdRename: renamePossible,
@@ -259,6 +333,7 @@ export class RenameSymbolProcessor extends Disposable {
259333
return suggestItem;
260334
}
261335

336+
const id = suggestItem.identity.id;
262337
const source = EditSources.inlineCompletionAccept({
263338
nes: suggestItem.isInlineEdit,
264339
requestUuid: suggestItem.requestUuid,
@@ -268,7 +343,7 @@ export class RenameSymbolProcessor extends Disposable {
268343
const command: Command = {
269344
id: renameSymbolCommandId,
270345
title: localize('rename', "Rename"),
271-
arguments: [textModel, position, newName, source],
346+
arguments: [textModel, position, newName, source, id],
272347
};
273348
const textReplacement = renameEdits[0];
274349
const renameAction: IInlineSuggestDataActionEdit = {
@@ -279,6 +354,33 @@ export class RenameSymbolProcessor extends Disposable {
279354
alternativeAction: command,
280355
uri: textModel.uri
281356
};
357+
358+
if (this._renameRunnable !== undefined) {
359+
this._renameRunnable.runnable.cancel();
360+
this._renameRunnable = undefined;
361+
}
362+
const runnable = new RenameSymbolRunnable(this._languageFeaturesService, textModel, position, newName, source);
363+
this._renameRunnable = { id, runnable };
364+
282365
return InlineSuggestionItem.create(suggestItem.withAction(renameAction), textModel);
283366
}
367+
368+
private async checkRenamePrecondition(textModel: ITextModel, position: Position, oldName: string, newName: string): Promise<RenameKind> {
369+
// const result = await prepareRename(this._languageFeaturesService.renameProvider, textModel, position, CancellationToken.None);
370+
// if (result === undefined || result.rejectReason) {
371+
// return RenameKind.no;
372+
// }
373+
// return oldName === result.text ? RenameKind.yes : RenameKind.no;
374+
375+
try {
376+
const result = await this._commandService.executeCommand<RenameKind>('github.copilot.nes.prepareRename', textModel.uri, position, oldName, newName);
377+
if (result === undefined) {
378+
return RenameKind.no;
379+
} else {
380+
return RenameKind.fromString(result);
381+
}
382+
} catch (error) {
383+
return RenameKind.no;
384+
}
385+
}
284386
}

src/vs/editor/contrib/inlineCompletions/test/browser/renameSymbolProcessor.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class TestRenameInferenceEngine extends RenameInferenceEngine {
3131

3232
suite('renameSymbolProcessor', () => {
3333

34+
// This got copied from the TypeScript language configuration.
3435
const wordPattern = /(-?\d*\.\d\w*)|([^\`\@\~\!\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>/\?\s]+)/;
3536

3637
let disposables: DisposableStore;

src/vs/editor/contrib/rename/browser/rename.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,14 @@ export function hasProvider(registry: LanguageFeatureRegistry<RenameProvider>, m
124124
return providers.length > 0;
125125
}
126126

127-
export async function prepareRename(registry: LanguageFeatureRegistry<RenameProvider>, model: ITextModel, position: Position): Promise<RenameLocation & Rejection | undefined> {
127+
export async function prepareRename(registry: LanguageFeatureRegistry<RenameProvider>, model: ITextModel, position: Position, cancellationToken?: CancellationToken): Promise<RenameLocation & Rejection | undefined> {
128128
const skeleton = new RenameSkeleton(model, position, registry);
129-
return skeleton.resolveRenameLocation(CancellationToken.None);
129+
return skeleton.resolveRenameLocation(cancellationToken ?? CancellationToken.None);
130+
}
131+
132+
export async function rawRename(registry: LanguageFeatureRegistry<RenameProvider>, model: ITextModel, position: Position, newName: string, cancellationToken?: CancellationToken): Promise<WorkspaceEdit & Rejection> {
133+
const skeleton = new RenameSkeleton(model, position, registry);
134+
return skeleton.provideRenameEdits(newName, cancellationToken ?? CancellationToken.None);
130135
}
131136

132137
export async function rename(registry: LanguageFeatureRegistry<RenameProvider>, model: ITextModel, position: Position, newName: string): Promise<WorkspaceEdit & Rejection> {

0 commit comments

Comments
 (0)