forked from PowerShell/vscode-powershell
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRenameSymbol.ts
190 lines (156 loc) · 7.07 KB
/
RenameSymbol.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import vscode = require("vscode");
import { RequestType, TextDocumentIdentifier } from "vscode-languageclient";
import { LanguageClientConsumer } from "../languageClientConsumer";
import { RenameProvider, WorkspaceEdit, TextDocument, CancellationToken, Position,Uri,Range, DocumentSelector } from "vscode";
import { LanguageClient } from "vscode-languageclient/node";
import { ILogger } from "../logging";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface IRenameSymbolRequestArguments {
TextDocument:TextDocumentIdentifier
Position:Position
NewName:string
}
interface IPrepareRenameSymbolRequestArguments {
TextDocument:TextDocumentIdentifier
Position:Position
NewName:string
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface TextChange {
newText: string;
startLine: number;
startColumn: number;
endLine: number;
endColumn: number;
}
interface ModifiedFileResponse{
fileName: string;
changes : TextChange[]
}
interface IRenameSymbolRequestResponse {
changes : ModifiedFileResponse[]
}
interface IPrepareRenameSymbolRequestResponse {
message : string
}
const RenameSymbolRequestType = new RequestType<IRenameSymbolRequestArguments, IRenameSymbolRequestResponse, void>("textDocument/rename");
const PrepareRenameSymbolRequestType = new RequestType<IPrepareRenameSymbolRequestArguments, IPrepareRenameSymbolRequestResponse, void>("textDocument/prepareRename");
export class RenameSymbolFeature extends LanguageClientConsumer implements RenameProvider {
private languageRenameProvider:vscode.Disposable;
// Used to singleton the disclaimer prompt in case multiple renames are triggered
private disclaimerPromise?: Promise<boolean>;
constructor(documentSelector:DocumentSelector,private logger: ILogger){
super();
this.languageRenameProvider = vscode.languages.registerRenameProvider(documentSelector,this);
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
public override onLanguageClientSet(_languageClient: LanguageClient): void {}
public async provideRenameEdits(document: TextDocument, position: Position, newName: string, _token: CancellationToken): Promise<WorkspaceEdit | undefined | null> {
const disclaimerAccepted = await this.acknowledgeDisclaimer();
if (!disclaimerAccepted) {return undefined;}
const req:IRenameSymbolRequestArguments = {
TextDocument : {uri:document.uri.toString()},
Position : position,
NewName : newName
};
try {
const client = await LanguageClientConsumer.getLanguageClient();
const response = await client.sendRequest(RenameSymbolRequestType, req);
if (!response.changes.length) {
return undefined;
}
const edit = new WorkspaceEdit();
for (const file of response.changes) {
const uri = Uri.file(file.fileName);
for (const change of file.changes) {
edit.replace(
uri,
new Range(change.startLine, change.startColumn, change.endLine, change.endColumn),
change.newText
);
}
}
return edit;
} catch (error) {
return undefined;
}
}
public async prepareRename(
document: vscode.TextDocument,
position: vscode.Position,
_token: vscode.CancellationToken
): Promise<vscode.Range | {range: vscode.Range; placeholder: string;} | undefined | null> {
const disclaimerAccepted = await this.acknowledgeDisclaimer();
if (!disclaimerAccepted) {return undefined;}
const req:IRenameSymbolRequestArguments = {
TextDocument : {uri:document.uri.toString()},
Position : position,
NewName : ""
};
try {
const client = await LanguageClientConsumer.getLanguageClient();
const response = await client.sendRequest(PrepareRenameSymbolRequestType, req);
if (!response.message) {
return null;
}
const wordRange = document.getWordRangeAtPosition(position);
if (!wordRange) {
throw new Error("Not a valid location for renaming.");
}
const wordText = document.getText(wordRange);
if (response.message) {
throw new Error(response.message);
}
return {
range: wordRange,
placeholder: wordText
};
}catch (error) {
const msg = `RenameSymbol: ${error}`;
this.logger.writeError(msg);
throw new Error(msg);
}
}
/** Prompts the user to acknowledge the risks inherent with the rename provider and does not proceed until it is accepted */
async acknowledgeDisclaimer(): Promise<boolean> {
if (!this.disclaimerPromise) {
this.disclaimerPromise = this.acknowledgeDisclaimerImpl();
}
return this.disclaimerPromise;
}
/** This is a separate function so that it only runs once as a singleton and the promise only resolves once */
async acknowledgeDisclaimerImpl(): Promise<boolean>
{
const config = vscode.workspace.getConfiguration();
const acceptRenameDisclaimer = config.get<boolean>("powershell.renameSymbol.acceptRenameDisclaimer", false);
if (!acceptRenameDisclaimer) {
const extensionPath = vscode.extensions.getExtension("ms-vscode.PowerShell")?.extensionPath;
const disclaimerPath = vscode.Uri.file(`${extensionPath}/media/RenameDisclaimer.txt`);
const result = await vscode.window.showWarningMessage(
//TODO: Provide a link to a markdown document that appears in the editor window, preferably one hosted with the extension itself.
`The PowerShell Rename functionality has limitations and risks, please [review the disclaimer](${disclaimerPath}).`,
"I Accept",
"I Accept [Workspace]",
"No"
);
switch (result) {
case "I Accept":
await config.update("powershell.renameSymbol.acceptRenameDisclaimer", true, vscode.ConfigurationTarget.Global);
break;
case "I Accept [Workspace]":
await config.update("powershell.renameSymbol.acceptRenameDisclaimer", true, vscode.ConfigurationTarget.Workspace);
break;
default:
void vscode.window.showInformationMessage("Rename operation cancelled and rename has been disabled until the extension is restarted.");
break;
}
}
// Refresh the config to ensure it was set
return vscode.workspace.getConfiguration().get<boolean>("powershell.renameSymbol.acceptRenameDisclaimer", false);
}
public dispose(): void {
this.languageRenameProvider.dispose();
}
}