Skip to content

Commit f074517

Browse files
committed
re-factoring using rope #220
1 parent e93c275 commit f074517

1 file changed

Lines changed: 167 additions & 0 deletions

File tree

src/client/refactor/proxy.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
'use strict';
2+
3+
import * as vscode from 'vscode';
4+
import * as path from 'path';
5+
import * as fs from 'fs';
6+
import * as child_process from 'child_process';
7+
import {ExtractResult, RefactorType} from './contracts';
8+
import {execPythonFile} from '../common/utils';
9+
import {PythonSettings} from '../common/configSettings';
10+
11+
const ROPE_PYTHON_VERSION = 'Refactor requires Python 2.x. Set \'python.pythonRopePath\' in Settings.json';
12+
13+
export class RefactorProxy extends vscode.Disposable {
14+
private _process: child_process.ChildProcess;
15+
private _extensionDir: string;
16+
private _previousOutData: string = '';
17+
private _startedSuccessfully: boolean = false;
18+
private _commandResolve: (value?: any | PromiseLike<any>) => void;
19+
private _commandReject: (reason?: any) => void;
20+
private _initializeReject: (reason?: any) => void;
21+
static pythonPath: string;
22+
private _settings: PythonSettings;
23+
constructor(context: vscode.ExtensionContext) {
24+
super(() => { });
25+
this._extensionDir = context.extensionPath;
26+
this._settings = PythonSettings.getInstance();
27+
vscode.workspace.onDidChangeConfiguration(() => {
28+
RefactorProxy.pythonPath = '';
29+
});
30+
}
31+
32+
dispose() {
33+
try {
34+
this._process.kill();
35+
}
36+
catch (ex) {
37+
}
38+
this._process = null;
39+
}
40+
extractVariable<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range): Promise<T> {
41+
let command = `{"lookup":"extract_variable", "file":"${filePath}", "start":"${document.offsetAt(range.start)}", "end":"${document.offsetAt(range.end)}", "id":"1", "name":"${name}"}`;
42+
return this.sendCommand<T>(command);
43+
}
44+
extractMethod<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range): Promise<T> {
45+
let command = `{"lookup":"extract_method", "file":"${filePath}", "start":"${document.offsetAt(range.start)}", "end":"${document.offsetAt(range.end)}", "id":"1","name":"${name}"}`;
46+
return this.sendCommand<T>(command);
47+
}
48+
private sendCommand<T>(command: string): Promise<T> {
49+
return this.pickValidPythonPath().then(pythonPath => {
50+
return this.initialize(pythonPath);
51+
}).then(() => {
52+
return new Promise<T>((resolve, reject) => {
53+
this._commandResolve = resolve;
54+
this._commandReject = reject;
55+
this._process.stdin.write(command + '\n');
56+
});
57+
});
58+
}
59+
60+
private pickValidPythonPath(): Promise<string> {
61+
if (RefactorProxy.pythonPath && RefactorProxy.pythonPath.length > 0) {
62+
return Promise.resolve(RefactorProxy.pythonPath);
63+
}
64+
65+
if (this._settings.pythonPath === this._settings.python2Path) {
66+
// First try what ever path we have in pythonRopePath
67+
return this.checkIfPythonVersionIs3(this._settings.python2Path).then(() => {
68+
return this._settings.python2Path;
69+
});
70+
}
71+
72+
// First try what ever path we have in pythonRopePath
73+
return this.checkIfPythonVersionIs3(this._settings.python2Path).then(() => {
74+
return this._settings.python2Path;
75+
}).catch(() => {
76+
// Now the path in pythonPath
77+
return this.checkIfPythonVersionIs3(this._settings.pythonPath).then(() => {
78+
return this._settings.pythonPath;
79+
});
80+
});
81+
}
82+
83+
private checkIfPythonVersionIs3(pythonPath: string): Promise<boolean> {
84+
return new Promise<boolean>((resolve, reject) => {
85+
child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.version)'], null, (error, stdout, stderr) => {
86+
if (stdout.indexOf('3.') === 0) {
87+
reject(new Error(ROPE_PYTHON_VERSION));
88+
}
89+
resolve(true);
90+
});
91+
});
92+
}
93+
private initialize(pythonPath: string): Promise<string> {
94+
return new Promise<any>((resolve, reject) => {
95+
this._initializeReject = reject;
96+
this._process = child_process.spawn(pythonPath, ['-u', 'refactor.py', vscode.workspace.rootPath, path.join(vscode.workspace.rootPath, '.vscode', 'rope')],
97+
{
98+
cwd: path.join(this._extensionDir, 'pythonFiles')
99+
});
100+
this._process.stderr.on('data', this.handleStdError.bind(this));
101+
this._process.on('error', this.handleError.bind(this));
102+
103+
let that = this;
104+
this._process.stdout.on('data', data => {
105+
let dataStr: string = data + '';
106+
if (!that._startedSuccessfully && dataStr.startsWith('STARTED')) {
107+
that._startedSuccessfully = true;
108+
// We know this works, hence keep tarck of this python path
109+
RefactorProxy.pythonPath = pythonPath;
110+
return resolve();
111+
}
112+
that.onData(data);
113+
});
114+
});
115+
}
116+
private handleStdError(data: string) {
117+
let dataStr = this._previousOutData = this._previousOutData + data + '';
118+
if (this._startedSuccessfully) {
119+
let lengthOfHeader = dataStr.indexOf(':') + 1;
120+
let lengthOfMessage = parseInt(dataStr.substring(0, lengthOfHeader - 1));
121+
if (dataStr.length === lengthOfMessage + lengthOfHeader) {
122+
this._previousOutData = '';
123+
this.dispose();
124+
125+
let errorLines = dataStr.substring(lengthOfHeader + 1).split(/\r?\n/g);
126+
let hasErrorMessage = errorLines[0].trim().length > 0;
127+
errorLines = errorLines.filter(line => line.length > 0);
128+
let errorMessage = errorLines.join('\n');
129+
130+
// If there is no error message take the last line from the error (stack)
131+
// As this generally contains the actual error
132+
if (!hasErrorMessage) {
133+
errorMessage = errorLines[errorLines.length - 1].trim() + '\n' + errorMessage;
134+
}
135+
136+
this._commandReject(`Refactor failed. ${errorMessage}`);
137+
}
138+
}
139+
else {
140+
this._initializeReject(`Refactor failed. ${dataStr}`);
141+
}
142+
}
143+
private handleError(error: Error) {
144+
if (this._startedSuccessfully) {
145+
return this._commandReject(error);
146+
}
147+
this._initializeReject(error);
148+
}
149+
private onData(data: string) {
150+
if (!this._commandResolve) { return; }
151+
// Possible there was an exception in parsing the data returned
152+
// So append the data then parse it
153+
let dataStr = this._previousOutData = this._previousOutData + data + '';
154+
let response: any;
155+
try {
156+
response = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp));
157+
this._previousOutData = '';
158+
}
159+
catch (ex) {
160+
// Possible we've only received part of the data, hence don't clear previousData
161+
return;
162+
}
163+
this.dispose();
164+
this._commandResolve(response[0]);
165+
this._commandResolve = null;
166+
}
167+
}

0 commit comments

Comments
 (0)