Skip to content

Commit 5112249

Browse files
committed
changes to #325
1 parent 41cfe3e commit 5112249

8 files changed

Lines changed: 151 additions & 73 deletions

File tree

pythonFiles/refactor.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,23 @@ def __init__(self):
166166
self.default_sys_path = sys.path
167167
self._input = io.open(sys.stdin.fileno(), encoding='utf-8')
168168

169+
def _rename(self, filePath, start, newName):
170+
"""
171+
Extracts a variale
172+
"""
173+
project = rope.base.project.Project(
174+
WORKSPACE_ROOT, ropefolder=ROPE_PROJECT_FOLDER, save_history=False)
175+
resourceToRefactor = libutils.path_to_resource(project, filePath)
176+
refactor = RenameRefactor(
177+
project, resourceToRefactor, startOffset=start, newName=newName)
178+
refactor.refactor()
179+
changes = refactor.changes
180+
project.close()
181+
valueToReturn = []
182+
for change in changes:
183+
valueToReturn.append({'diff': change.diff})
184+
return valueToReturn
185+
169186
def _extractVariable(self, filePath, start, end, newName):
170187
"""
171188
Extracts a variale
@@ -225,6 +242,10 @@ def _process_request(self, request):
225242

226243
if lookup == '':
227244
pass
245+
elif lookup == 'rename':
246+
changes = self._rename(request['file'], int(
247+
request['start']), request['name'])
248+
return self._write_response(self._serialize(request['id'], changes))
228249
elif lookup == 'extract_variable':
229250
changes = self._extractVariable(request['file'], int(
230251
request['start']), int(request['end']), request['name'])

src/client/common/editor.ts

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {TextEdit, Position, Range, TextDocument} from 'vscode';
1+
import {TextEdit, Position, Range, TextDocument, WorkspaceEdit} from 'vscode';
2+
import * as vscode from 'vscode';
23
import * as dmp from 'diff-match-patch';
34
import {EOL} from 'os';
45
import * as fs from 'fs';
@@ -60,7 +61,7 @@ export function getTextEditsFromPatch(before: string, patch: string): TextEdit[]
6061
if (!Array.isArray(patches) || patches.length === 0) {
6162
throw new Error('Unable to parse Patch string');
6263
}
63-
let textEdits = [];
64+
let textEdits: TextEdit[] = [];
6465

6566
// Add line feeds
6667
// & build the text edits
@@ -69,24 +70,95 @@ export function getTextEditsFromPatch(before: string, patch: string): TextEdit[]
6970
diff[1] += EOL;
7071
});
7172

72-
textEdits = textEdits.concat(getTextEditsInternal(before, patch.diffs, patch.start1));
73+
getTextEditsInternal(before, patch.diffs, patch.start1).forEach(edit => textEdits.push(edit.apply()));
7374
});
7475

7576
return textEdits;
7677
}
78+
export function getWorkspaceEditsFromPatch(filePatches: string[]): WorkspaceEdit {
79+
const workspaceEdit = new WorkspaceEdit();
80+
filePatches.forEach(patch => {
81+
const indexOfAtAt = patch.indexOf('@@');
82+
if (indexOfAtAt === -1) {
83+
return;
84+
}
85+
const fileNameLines = patch.substring(0, indexOfAtAt).split(/\r?\n/g)
86+
.map(line => line.trim())
87+
.filter(line => line.length > 0 &&
88+
line.toLowerCase().endsWith('.py') &&
89+
line.indexOf(' a') > 0);
90+
91+
if (patch.startsWith('---')) {
92+
// Strip the first two lines
93+
patch = patch.substring(indexOfAtAt);
94+
}
95+
if (patch.length === 0) {
96+
return;
97+
}
98+
// We can't find the find name
99+
if (fileNameLines.length === 0) {
100+
return;
101+
}
102+
103+
let fileName = fileNameLines[0].substring(fileNameLines[0].indexOf(' a') + 3).trim();
104+
fileName = path.isAbsolute(fileName) ? fileName : path.resolve(vscode.workspace.rootPath, fileName);
105+
if (!fs.existsSync(fileName)) {
106+
return;
107+
}
108+
109+
// Remove the text added by unified_diff
110+
// # Work around missing newline (http://bugs.python.org/issue2142).
111+
patch = patch.replace(/\\ No newline at end of file[\r\n]/, '');
112+
113+
let d = new dmp.diff_match_patch();
114+
let patches: any[] = patch_fromText.call(d, patch);
115+
if (!Array.isArray(patches) || patches.length === 0) {
116+
throw new Error('Unable to parse Patch string');
117+
}
118+
119+
const fileSource = fs.readFileSync(fileName).toString('utf8');
120+
const fileUri = vscode.Uri.file(fileName);
121+
122+
// Add line feeds
123+
// & build the text edits
124+
patches.forEach(patch => {
125+
patch.diffs.forEach(diff => {
126+
diff[1] += EOL;
127+
});
128+
129+
getTextEditsInternal(fileSource, patch.diffs, patch.start1).forEach(edit => {
130+
switch (edit.action) {
131+
case EDIT_DELETE: {
132+
workspaceEdit.delete(fileUri, new Range(edit.start, edit.end));
133+
}
134+
case EDIT_INSERT: {
135+
workspaceEdit.insert(fileUri, edit.start, edit.text);
136+
}
137+
case EDIT_REPLACE: {
138+
workspaceEdit.replace(fileUri, new Range(edit.start, edit.end), edit.text);
139+
}
140+
}
141+
});
142+
});
143+
144+
145+
});
146+
147+
return workspaceEdit;
148+
}
77149
export function getTextEdits(before: string, after: string): TextEdit[] {
78150
let d = new dmp.diff_match_patch();
79151
let diffs = d.diff_main(before, after);
80-
return getTextEditsInternal(before, diffs);
152+
return getTextEditsInternal(before, diffs).map(edit => edit.apply());
81153
}
82-
function getTextEditsInternal(before: string, diffs: [number, string][], startLine: number = 0): TextEdit[] {
154+
function getTextEditsInternal(before: string, diffs: [number, string][], startLine: number = 0): Edit[] {
83155
let line = startLine;
84156
let character = 0;
85157
if (line > 0) {
86158
let beforeLines = <string[]>before.split(/\r?\n/g);
87159
beforeLines.filter((l, i) => i < line).forEach(l => character += l.length + NEW_LINE_LENGTH);
88160
}
89-
let edits: TextEdit[] = [];
161+
const edits: Edit[] = [];
90162
let edit: Edit = null;
91163

92164
for (let i = 0; i < diffs.length; i++) {
@@ -128,15 +200,15 @@ function getTextEditsInternal(before: string, diffs: [number, string][], startLi
128200

129201
case dmp.DIFF_EQUAL:
130202
if (edit != null) {
131-
edits.push(edit.apply());
203+
edits.push(edit);
132204
edit = null;
133205
}
134206
break;
135207
}
136208
}
137209

138210
if (edit != null) {
139-
edits.push(edit.apply());
211+
edits.push(edit);
140212
}
141213

142214
return edits;

src/client/common/telemetryContracts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export namespace IDE {
1717
export const Lint = 'LINTING';
1818
}
1919
export namespace REFACTOR {
20+
export const Rename = 'REFACTOR_RENAME';
2021
export const ExtractVariable = 'REFACTOR_EXTRACT_VAR';
2122
export const ExtractMethod = 'REFACTOR_EXTRACT_METHOD';
2223
}

src/client/extension.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,17 @@ export function activate(context: vscode.ExtensionContext) {
6161
]
6262
});
6363

64-
let renameProvider = new PythonRenameProvider(context);
65-
context.subscriptions.push(vscode.languages.registerRenameProvider(PYTHON, renameProvider));
64+
context.subscriptions.push(vscode.languages.registerRenameProvider(PYTHON, new PythonRenameProvider()));
6665
context.subscriptions.push(vscode.languages.registerHoverProvider(PYTHON, new PythonHoverProvider(context)));
67-
context.subscriptions.push(vscode.languages.registerDefinitionProvider(PYTHON, new PythonDefinitionProvider(context)));
68-
context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(context)));
66+
const definitionProvider = new PythonDefinitionProvider(context);
67+
const jediProx = definitionProvider.JediProxy;
68+
context.subscriptions.push(vscode.languages.registerDefinitionProvider(PYTHON, definitionProvider));
69+
context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(context, jediProx)));
6970
context.subscriptions.push(vscode.languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(context), '.'));
7071

71-
context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(PYTHON, new PythonSymbolProvider(context, renameProvider.JediProxy)));
72+
context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(PYTHON, new PythonSymbolProvider(context, jediProx)));
7273
if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) {
73-
context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(context, renameProvider.JediProxy), '(', ','));
74+
context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(context, jediProx), '(', ','));
7475
}
7576
const formatProvider = new PythonFormattingEditProvider(context, formatOutChannel, pythonSettings, vscode.workspace.rootPath);
7677
context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider));

src/client/providers/definitionProvider.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import * as telemetryContracts from "../common/telemetryContracts";
77

88
export class PythonDefinitionProvider implements vscode.DefinitionProvider {
99
private jediProxyHandler: proxy.JediProxyHandler<proxy.IDefinitionResult, vscode.Definition>;
10+
public get JediProxy(): proxy.JediProxy {
11+
return this.jediProxyHandler.JediProxy;
12+
}
1013

1114
public constructor(context: vscode.ExtensionContext) {
1215
this.jediProxyHandler = new proxy.JediProxyHandler(context, null, PythonDefinitionProvider.parseData);

src/client/providers/referenceProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import * as telemetryContracts from "../common/telemetryContracts";
88
export class PythonReferenceProvider implements vscode.ReferenceProvider {
99
private jediProxyHandler: proxy.JediProxyHandler<proxy.IReferenceResult, vscode.Location[]>;
1010

11-
public constructor(context: vscode.ExtensionContext) {
12-
this.jediProxyHandler = new proxy.JediProxyHandler(context, [], PythonReferenceProvider.parseData);
11+
public constructor(context: vscode.ExtensionContext, jediProxy: proxy.JediProxy = null) {
12+
this.jediProxyHandler = new proxy.JediProxyHandler(context, [], PythonReferenceProvider.parseData, jediProxy);
1313
}
1414
private static parseData(data: proxy.IReferenceResult): vscode.Location[] {
1515
if (data && data.references.length > 0) {
Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,51 @@
11
'use strict';
22

33
import * as vscode from 'vscode';
4-
import * as proxy from './jediProxy';
54
import * as telemetryContracts from "../common/telemetryContracts";
6-
7-
var _oldName = "";
8-
var _newName = "";
5+
import {RefactorProxy} from '../refactor/proxy';
6+
import {getWorkspaceEditsFromPatch, getTextEdits} from '../common/editor';
7+
import * as path from 'path';
8+
import {PythonSettings} from '../common/configSettings';
9+
10+
const pythonSettings = PythonSettings.getInstance();
11+
const EXTENSION_DIR = path.join(__dirname, '..', '..', '..');
12+
interface RenameResponse {
13+
results: [{ diff: string }];
14+
}
915

1016
export class PythonRenameProvider implements vscode.RenameProvider {
11-
private jediProxyHandler: proxy.JediProxyHandler<proxy.IReferenceResult, vscode.WorkspaceEdit>;
12-
public get JediProxy(): proxy.JediProxy {
13-
return this.jediProxyHandler.JediProxy;
14-
}
15-
public constructor(context: vscode.ExtensionContext) {
16-
this.jediProxyHandler = new proxy.JediProxyHandler(context, null, PythonRenameProvider.parseData);
17-
}
18-
private static parseData(data: proxy.IReferenceResult): vscode.WorkspaceEdit {
19-
if (data && data.references.length > 0) {
20-
var references = data.references.filter(ref => {
21-
var relPath = vscode.workspace.asRelativePath(ref.fileName);
22-
return !relPath.startsWith("..");
23-
});
24-
25-
var workSpaceEdit = new vscode.WorkspaceEdit();
26-
references.forEach(ref => {
27-
var uri = vscode.Uri.file(ref.fileName);
28-
var range = new vscode.Range(ref.lineIndex, ref.columnIndex, ref.lineIndex, ref.columnIndex + _oldName.length);
29-
workSpaceEdit.replace(uri, range, _newName);
30-
});
31-
return workSpaceEdit;
32-
}
33-
return;
34-
}
3517
public provideRenameEdits(document: vscode.TextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Thenable<vscode.WorkspaceEdit> {
3618
return vscode.workspace.saveAll(false).then(() => {
3719
return this.doRename(document, position, newName, token);
3820
});
3921
}
4022

4123
private doRename(document: vscode.TextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Thenable<vscode.WorkspaceEdit> {
42-
return new Promise<vscode.WorkspaceEdit>((resolve, reject) => {
43-
var filename = document.fileName;
44-
if (document.lineAt(position.line).text.match(/^\s*\/\//)) {
45-
return resolve();
46-
}
47-
if (position.character <= 0) {
48-
return resolve();
49-
}
50-
51-
var source = document.getText();
52-
var range = document.getWordRangeAtPosition(position);
53-
if (range == undefined || range == null || range.isEmpty) {
54-
return resolve();
55-
}
56-
_oldName = document.getText(range);
57-
_newName = newName;
58-
if (_oldName === newName) {
59-
return resolve();
60-
}
24+
var filename = document.fileName;
25+
if (document.lineAt(position.line).text.match(/^\s*\/\//)) {
26+
return;
27+
}
28+
if (position.character <= 0) {
29+
return;
30+
}
6131

62-
var columnIndex = range.isEmpty ? position.character : range.end.character;
63-
var cmd: proxy.ICommand<proxy.IReferenceResult> = {
64-
telemetryEvent: telemetryContracts.IDE.Rename,
65-
command: proxy.CommandType.Usages,
66-
fileName: filename,
67-
columnIndex: columnIndex,
68-
lineIndex: position.line,
69-
source: source
70-
};
32+
var source = document.getText();
33+
var range = document.getWordRangeAtPosition(position);
34+
if (range == undefined || range == null || range.isEmpty) {
35+
return;
36+
}
37+
const oldName = document.getText(range);
38+
if (oldName === newName) {
39+
return;
40+
}
7141

72-
this.jediProxyHandler.sendCommand(cmd, resolve, token);
42+
let proxy = new RefactorProxy(EXTENSION_DIR, pythonSettings, vscode.workspace.rootPath);
43+
return new Promise<vscode.WorkspaceEdit>(resolve => {
44+
proxy.rename<RenameResponse>(document, newName, document.uri.fsPath, range).then(response => {
45+
//return response.results[0].diff;
46+
const workspaceEdit = getWorkspaceEditsFromPatch(response.results.map(fileChanges => fileChanges.diff));
47+
resolve(workspaceEdit);
48+
});
7349
});
7450
}
7551
}

src/client/refactor/proxy.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ export class RefactorProxy extends vscode.Disposable {
3535
}
3636
this._process = null;
3737
}
38+
rename<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range): Promise<T> {
39+
let command = { "lookup": "rename", "file": filePath, "start": document.offsetAt(range.start).toString(), "id": "1", "name": name };
40+
return this.sendCommand<T>(JSON.stringify(command), REFACTOR.Rename);
41+
}
3842
extractVariable<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range): Promise<T> {
3943
let command = { "lookup": "extract_variable", "file": filePath, "start": document.offsetAt(range.start).toString(), "end": document.offsetAt(range.end).toString(), "id": "1", "name": name };
4044
return this.sendCommand<T>(JSON.stringify(command), REFACTOR.ExtractVariable);

0 commit comments

Comments
 (0)