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