Skip to content

Commit 660c3c5

Browse files
authored
Make use of new execution layer instead of spawning processes (#425)
Fixes #353 * use new exec engine instead of spawning manually * fixed code review comments
1 parent 68f6626 commit 660c3c5

File tree

5 files changed

+182
-171
lines changed

5 files changed

+182
-171
lines changed

src/client/providers/renameProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class PythonRenameProvider implements vscode.RenameProvider {
5151
const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname;
5252
const pythonSettings = PythonSettings.getInstance(workspaceFolder ? workspaceFolder.uri : undefined);
5353

54-
const proxy = new RefactorProxy(EXTENSION_DIR, pythonSettings, workspaceRoot);
54+
const proxy = new RefactorProxy(EXTENSION_DIR, pythonSettings, workspaceRoot, this.serviceContainer);
5555
return proxy.rename<RenameResponse>(document, newName, document.uri.fsPath, range).then(response => {
5656
const fileDiffs = response.results.map(fileChanges => fileChanges.diff);
5757
return getWorkspaceEditsFromPatch(fileDiffs, workspaceRoot);

src/client/providers/simpleRefactorProvider.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function activateSimplePythonRefactorProvider(context: vscode.ExtensionCo
2222
vscode.window.activeTextEditor!,
2323
vscode.window.activeTextEditor!.selection,
2424
// tslint:disable-next-line:no-empty
25-
outputChannel).catch(() => { });
25+
outputChannel, serviceContainer).catch(() => { });
2626
sendTelemetryWhenDone(REFACTOR_EXTRACT_VAR, promise, stopWatch);
2727
});
2828
context.subscriptions.push(disposable);
@@ -33,7 +33,7 @@ export function activateSimplePythonRefactorProvider(context: vscode.ExtensionCo
3333
vscode.window.activeTextEditor!,
3434
vscode.window.activeTextEditor!.selection,
3535
// tslint:disable-next-line:no-empty
36-
outputChannel).catch(() => { });
36+
outputChannel, serviceContainer).catch(() => { });
3737
sendTelemetryWhenDone(REFACTOR_EXTRACT_FUNCTION, promise, stopWatch);
3838
});
3939
context.subscriptions.push(disposable);
@@ -42,7 +42,7 @@ export function activateSimplePythonRefactorProvider(context: vscode.ExtensionCo
4242
// Exported for unit testing
4343
export function extractVariable(extensionDir: string, textEditor: vscode.TextEditor, range: vscode.Range,
4444
// tslint:disable-next-line:no-any
45-
outputChannel: vscode.OutputChannel): Promise<any> {
45+
outputChannel: vscode.OutputChannel, serviceContainer: IServiceContainer): Promise<any> {
4646

4747
let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri);
4848
if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) {
@@ -53,7 +53,7 @@ export function extractVariable(extensionDir: string, textEditor: vscode.TextEdi
5353

5454
return validateDocumentForRefactor(textEditor).then(() => {
5555
const newName = `newvariable${new Date().getMilliseconds().toString()}`;
56-
const proxy = new RefactorProxy(extensionDir, pythonSettings, workspaceRoot);
56+
const proxy = new RefactorProxy(extensionDir, pythonSettings, workspaceRoot, serviceContainer);
5757
const rename = proxy.extractVariable<RenameResponse>(textEditor.document, newName, textEditor.document.uri.fsPath, range, textEditor.options).then(response => {
5858
return response.results[0].diff;
5959
});
@@ -65,7 +65,7 @@ export function extractVariable(extensionDir: string, textEditor: vscode.TextEdi
6565
// Exported for unit testing
6666
export function extractMethod(extensionDir: string, textEditor: vscode.TextEditor, range: vscode.Range,
6767
// tslint:disable-next-line:no-any
68-
outputChannel: vscode.OutputChannel): Promise<any> {
68+
outputChannel: vscode.OutputChannel, serviceContainer: IServiceContainer): Promise<any> {
6969

7070
let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri);
7171
if (!workspaceFolder && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) {
@@ -76,7 +76,7 @@ export function extractMethod(extensionDir: string, textEditor: vscode.TextEdito
7676

7777
return validateDocumentForRefactor(textEditor).then(() => {
7878
const newName = `newmethod${new Date().getMilliseconds().toString()}`;
79-
const proxy = new RefactorProxy(extensionDir, pythonSettings, workspaceRoot);
79+
const proxy = new RefactorProxy(extensionDir, pythonSettings, workspaceRoot, serviceContainer);
8080
const rename = proxy.extractMethod<RenameResponse>(textEditor.document, newName, textEditor.document.uri.fsPath, range, textEditor.options).then(response => {
8181
return response.results[0].diff;
8282
});

src/client/refactor/proxy.ts

Lines changed: 73 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
1-
'use strict';
1+
// tslint:disable:no-any no-empty member-ordering prefer-const prefer-template no-var-self
22

33
import * as child_process from 'child_process';
44
import * as path from 'path';
55
import * as vscode from 'vscode';
6+
import { Uri } from 'vscode';
67
import { IPythonSettings } from '../common/configSettings';
7-
import { mergeEnvVariables } from '../common/envFileParser';
8-
import { getCustomEnvVarsSync, getWindowsLineEndingCount, IS_WINDOWS } from '../common/utils';
8+
import '../common/extensions';
9+
import { createDeferred, Deferred } from '../common/helpers';
10+
import { IPythonExecutionFactory } from '../common/process/types';
11+
import { getWindowsLineEndingCount, IS_WINDOWS } from '../common/utils';
12+
import { IServiceContainer } from '../ioc/types';
913

1014
export class RefactorProxy extends vscode.Disposable {
11-
private _process: child_process.ChildProcess;
15+
private _process?: child_process.ChildProcess;
1216
private _extensionDir: string;
1317
private _previousOutData: string = '';
1418
private _previousStdErrData: string = '';
1519
private _startedSuccessfully: boolean = false;
16-
private _commandResolve: (value?: any | PromiseLike<any>) => void;
20+
private _commandResolve?: (value?: any | PromiseLike<any>) => void;
1721
private _commandReject: (reason?: any) => void;
18-
private _initializeReject: (reason?: any) => void;
19-
constructor(extensionDir: string, private pythonSettings: IPythonSettings, private workspaceRoot: string) {
22+
private initialized: Deferred<void>;
23+
constructor(extensionDir: string, private pythonSettings: IPythonSettings, private workspaceRoot: string,
24+
private serviceContainer: IServiceContainer) {
2025
super(() => { });
2126
this._extensionDir = extensionDir;
2227
}
2328

24-
dispose() {
29+
public dispose() {
2530
try {
26-
this._process.kill();
31+
this._process!.kill();
32+
} catch (ex) {
2733
}
28-
catch (ex) {
29-
}
30-
this._process = null;
34+
this._process = undefined;
3135
}
3236
private getOffsetAt(document: vscode.TextDocument, position: vscode.Position): number {
3337
if (!IS_WINDOWS) {
@@ -43,52 +47,52 @@ export class RefactorProxy extends vscode.Disposable {
4347

4448
return offset - winEols;
4549
}
46-
rename<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise<T> {
50+
public rename<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise<T> {
4751
if (!options) {
48-
options = vscode.window.activeTextEditor.options;
52+
options = vscode.window.activeTextEditor!.options;
4953
}
50-
let command = {
51-
"lookup": "rename",
52-
"file": filePath,
53-
"start": this.getOffsetAt(document, range.start).toString(),
54-
"id": "1",
55-
"name": name,
56-
"indent_size": options.tabSize
54+
const command = {
55+
lookup: 'rename',
56+
file: filePath,
57+
start: this.getOffsetAt(document, range.start).toString(),
58+
id: '1',
59+
name: name,
60+
indent_size: options.tabSize
5761
};
5862

5963
return this.sendCommand<T>(JSON.stringify(command));
6064
}
61-
extractVariable<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise<T> {
65+
public extractVariable<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise<T> {
6266
if (!options) {
63-
options = vscode.window.activeTextEditor.options;
67+
options = vscode.window.activeTextEditor!.options;
6468
}
65-
let command = {
66-
"lookup": "extract_variable",
67-
"file": filePath,
68-
"start": this.getOffsetAt(document, range.start).toString(),
69-
"end": this.getOffsetAt(document, range.end).toString(),
70-
"id": "1",
71-
"name": name,
72-
"indent_size": options.tabSize
69+
const command = {
70+
lookup: 'extract_variable',
71+
file: filePath,
72+
start: this.getOffsetAt(document, range.start).toString(),
73+
end: this.getOffsetAt(document, range.end).toString(),
74+
id: '1',
75+
name: name,
76+
indent_size: options.tabSize
7377
};
7478
return this.sendCommand<T>(JSON.stringify(command));
7579
}
76-
extractMethod<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise<T> {
80+
public extractMethod<T>(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise<T> {
7781
if (!options) {
78-
options = vscode.window.activeTextEditor.options;
82+
options = vscode.window.activeTextEditor!.options;
7983
}
8084
// Ensure last line is an empty line
8185
if (!document.lineAt(document.lineCount - 1).isEmptyOrWhitespace && range.start.line === document.lineCount - 1) {
8286
return Promise.reject<T>('Missing blank line at the end of document (PEP8).');
8387
}
84-
let command = {
85-
"lookup": "extract_method",
86-
"file": filePath,
87-
"start": this.getOffsetAt(document, range.start).toString(),
88-
"end": this.getOffsetAt(document, range.end).toString(),
89-
"id": "1",
90-
"name": name,
91-
"indent_size": options.tabSize
88+
const command = {
89+
lookup: 'extract_method',
90+
file: filePath,
91+
start: this.getOffsetAt(document, range.start).toString(),
92+
end: this.getOffsetAt(document, range.end).toString(),
93+
id: '1',
94+
name: name,
95+
indent_size: options.tabSize
9296
};
9397
return this.sendCommand<T>(JSON.stringify(command));
9498
}
@@ -98,39 +102,30 @@ export class RefactorProxy extends vscode.Disposable {
98102
return new Promise<T>((resolve, reject) => {
99103
this._commandResolve = resolve;
100104
this._commandReject = reject;
101-
this._process.stdin.write(command + '\n');
105+
this._process!.stdin.write(command + '\n');
102106
});
103107
});
104108
}
105-
private initialize(pythonPath: string): Promise<string> {
106-
return new Promise<any>((resolve, reject) => {
107-
this._initializeReject = reject;
108-
let environmentVariables: Object & { [key: string]: string } = { 'PYTHONUNBUFFERED': '1' };
109-
let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.workspaceRoot));
110-
if (customEnvironmentVars) {
111-
environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars);
109+
private async initialize(pythonPath: string): Promise<void> {
110+
const pythonProc = await this.serviceContainer.get<IPythonExecutionFactory>(IPythonExecutionFactory).create(Uri.file(this.workspaceRoot));
111+
this.initialized = createDeferred<void>();
112+
const args = ['refactor.py', this.workspaceRoot];
113+
const cwd = path.join(this._extensionDir, 'pythonFiles');
114+
const result = pythonProc.execObservable(args, { cwd });
115+
this._process = result.proc;
116+
result.out.subscribe(output => {
117+
if (output.source === 'stdout') {
118+
if (!this._startedSuccessfully && output.out.startsWith('STARTED')) {
119+
this._startedSuccessfully = true;
120+
return this.initialized.resolve();
121+
}
122+
this.onData(output.out);
123+
} else {
124+
this.handleStdError(output.out);
112125
}
113-
environmentVariables = mergeEnvVariables(environmentVariables);
114-
this._process = child_process.spawn(pythonPath, ['refactor.py', this.workspaceRoot],
115-
{
116-
cwd: path.join(this._extensionDir, 'pythonFiles'),
117-
env: environmentVariables
118-
});
119-
this._process.stderr.setEncoding('utf8');
120-
this._process.stderr.on('data', this.handleStdError.bind(this));
121-
this._process.on('error', this.handleError.bind(this));
126+
}, error => this.handleError(error));
122127

123-
let that = this;
124-
this._process.stdout.setEncoding('utf8');
125-
this._process.stdout.on('data', (data: string) => {
126-
let dataStr: string = data + '';
127-
if (!that._startedSuccessfully && dataStr.startsWith('STARTED')) {
128-
that._startedSuccessfully = true;
129-
return resolve();
130-
}
131-
that.onData(data);
132-
});
133-
});
128+
return this.initialized.promise;
134129
}
135130
private handleStdError(data: string) {
136131
// Possible there was an exception in parsing the data returned
@@ -140,34 +135,32 @@ export class RefactorProxy extends vscode.Disposable {
140135
try {
141136
errorResponse = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp));
142137
this._previousStdErrData = '';
143-
}
144-
catch (ex) {
138+
} catch (ex) {
145139
console.error(ex);
146140
// Possible we've only received part of the data, hence don't clear previousData
147141
return;
148142
}
149143
if (typeof errorResponse[0].message !== 'string' || errorResponse[0].message.length === 0) {
150-
errorResponse[0].message = errorResponse[0].traceback.split(/\r?\n/g).pop();
144+
errorResponse[0].message = errorResponse[0].traceback.splitLines().pop()!;
151145
}
152146
let errorMessage = errorResponse[0].message + '\n' + errorResponse[0].traceback;
153147

154148
if (this._startedSuccessfully) {
155149
this._commandReject(`Refactor failed. ${errorMessage}`);
156-
}
157-
else {
150+
} else {
158151
if (typeof errorResponse[0].type === 'string' && errorResponse[0].type === 'ModuleNotFoundError') {
159-
this._initializeReject('Not installed');
152+
this.initialized.reject('Not installed');
160153
return;
161154
}
162155

163-
this._initializeReject(`Refactor failed. ${errorMessage}`);
156+
this.initialized.reject(`Refactor failed. ${errorMessage}`);
164157
}
165158
}
166159
private handleError(error: Error) {
167160
if (this._startedSuccessfully) {
168161
return this._commandReject(error);
169162
}
170-
this._initializeReject(error);
163+
this.initialized.reject(error);
171164
}
172165
private onData(data: string) {
173166
if (!this._commandResolve) { return; }
@@ -179,13 +172,12 @@ export class RefactorProxy extends vscode.Disposable {
179172
try {
180173
response = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp));
181174
this._previousOutData = '';
182-
}
183-
catch (ex) {
175+
} catch (ex) {
184176
// Possible we've only received part of the data, hence don't clear previousData
185177
return;
186178
}
187179
this.dispose();
188-
this._commandResolve(response[0]);
189-
this._commandResolve = null;
180+
this._commandResolve!(response[0]);
181+
this._commandResolve = undefined;
190182
}
191183
}

0 commit comments

Comments
 (0)