@@ -7,6 +7,7 @@ import * as path from 'path';
7
7
import * as vscode from 'vscode' ;
8
8
import { Uri } from 'vscode' ;
9
9
import { PythonSettings } from '../common/configSettings' ;
10
+ import { debounce , swallowExceptions } from '../common/decorators' ;
10
11
import '../common/extensions' ;
11
12
import { createDeferred , Deferred } from '../common/helpers' ;
12
13
import { IPythonExecutionFactory } from '../common/process/types' ;
@@ -129,25 +130,24 @@ export class JediProxy implements vscode.Disposable {
129
130
private proc : child_process . ChildProcess | null ;
130
131
private pythonSettings : PythonSettings ;
131
132
private cmdId : number = 0 ;
132
- private pythonProcessCWD = '' ;
133
133
private lastKnownPythonInterpreter : string ;
134
134
private previousData = '' ;
135
135
private commands = new Map < number , IExecutionCommand < ICommandResult > > ( ) ;
136
136
private commandQueue : number [ ] = [ ] ;
137
137
private spawnRetryAttempts = 0 ;
138
- private lastKnownPythonPath : string ;
139
- private additionalAutoCopletePaths : string [ ] = [ ] ;
138
+ private additionalAutoCompletePaths : string [ ] = [ ] ;
140
139
private workspacePath : string ;
140
+ private languageServerStarted : Deferred < void > ;
141
141
private initialized : Deferred < void > ;
142
-
143
- public constructor ( extensionRootDir : string , workspacePath : string , private serviceContainer : IServiceContainer ) {
142
+ private environmentVariablesProvider : IEnvironmentVariablesProvider ;
143
+ public constructor ( private extensionRootDir : string , workspacePath : string , private serviceContainer : IServiceContainer ) {
144
144
this . workspacePath = workspacePath ;
145
145
this . pythonSettings = PythonSettings . getInstance ( vscode . Uri . file ( workspacePath ) ) ;
146
146
this . lastKnownPythonInterpreter = this . pythonSettings . pythonPath ;
147
- this . pythonSettings . on ( 'change' , this . onPythonSettingsChanged . bind ( this ) ) ;
148
- vscode . workspace . onDidChangeConfiguration ( this . onConfigChanged . bind ( this ) ) ;
149
- this . onConfigChanged ( ) ;
150
- this . initialize ( extensionRootDir ) ;
147
+ this . pythonSettings . on ( 'change' , ( ) => this . pythonSettingsChangeHandler ( ) ) ;
148
+ this . initialized = createDeferred < void > ( ) ;
149
+ // tslint:disable-next-line:no-empty
150
+ this . startLanguageServer ( ) . catch ( ( ) => { } ) . then ( ( ) => this . initialized . resolve ( ) ) ;
151
151
}
152
152
153
153
private static getProperty < T > ( o : object , name : string ) : T {
@@ -166,15 +166,13 @@ export class JediProxy implements vscode.Disposable {
166
166
167
167
public async sendCommand < T extends ICommandResult > ( cmd : ICommand < T > ) : Promise < T > {
168
168
await this . initialized . promise ;
169
+ await this . languageServerStarted . promise ;
169
170
if ( ! this . proc ) {
170
171
return Promise . reject ( new Error ( 'Python proc not initialized' ) ) ;
171
172
}
172
173
const executionCmd = < IExecutionCommand < T > > cmd ;
173
174
const payload = this . createPayload ( executionCmd ) ;
174
175
executionCmd . deferred = createDeferred < T > ( ) ;
175
- // if (typeof executionCmd.telemetryEvent === 'string') {
176
- // executionCmd.delays = new telemetryHelper.Delays();
177
- // }
178
176
try {
179
177
this . proc . stdin . write ( `${ JSON . stringify ( payload ) } \n` ) ;
180
178
this . commands . set ( executionCmd . id , executionCmd ) ;
@@ -193,25 +191,43 @@ export class JediProxy implements vscode.Disposable {
193
191
}
194
192
195
193
// keep track of the directory so we can re-spawn the process.
196
- private initialize ( dir : string ) {
197
- this . pythonProcessCWD = dir ;
198
- this . spawnProcess ( path . join ( dir , 'pythonFiles' ) )
194
+ private initialize ( ) {
195
+ this . spawnProcess ( path . join ( this . extensionRootDir , 'pythonFiles' ) )
199
196
. catch ( ex => {
200
- if ( this . initialized ) {
201
- this . initialized . reject ( ex ) ;
197
+ if ( this . languageServerStarted ) {
198
+ this . languageServerStarted . reject ( ex ) ;
202
199
}
203
200
this . handleError ( 'spawnProcess' , ex ) ;
204
201
} ) ;
205
202
}
206
-
207
- // Check if settings changes.
208
- private onPythonSettingsChanged ( ) {
203
+ @swallowExceptions ( 'JediProxy' )
204
+ private async pythonSettingsChangeHandler ( ) {
209
205
if ( this . lastKnownPythonInterpreter === this . pythonSettings . pythonPath ) {
210
206
return ;
211
207
}
208
+ this . lastKnownPythonInterpreter = this . pythonSettings . pythonPath ;
209
+ this . additionalAutoCompletePaths = await this . buildAutoCompletePaths ( ) ;
210
+ this . restartLanguageServer ( ) ;
211
+ }
212
+ @debounce ( 1500 )
213
+ @swallowExceptions ( 'JediProxy' )
214
+ private async environmentVariablesChangeHandler ( ) {
215
+ const newAutoComletePaths = await this . buildAutoCompletePaths ( ) ;
216
+ if ( this . additionalAutoCompletePaths . join ( ',' ) !== newAutoComletePaths . join ( ',' ) ) {
217
+ this . additionalAutoCompletePaths = newAutoComletePaths ;
218
+ this . restartLanguageServer ( ) ;
219
+ }
220
+ }
221
+ @swallowExceptions ( 'JediProxy' )
222
+ private async startLanguageServer ( ) {
223
+ const newAutoComletePaths = await this . buildAutoCompletePaths ( ) ;
224
+ this . additionalAutoCompletePaths = newAutoComletePaths ;
225
+ this . restartLanguageServer ( ) ;
226
+ }
227
+ private restartLanguageServer ( ) {
212
228
this . killProcess ( ) ;
213
229
this . clearPendingRequests ( ) ;
214
- this . initialize ( this . pythonProcessCWD ) ;
230
+ this . initialize ( ) ;
215
231
}
216
232
217
233
private clearPendingRequests ( ) {
@@ -240,7 +256,10 @@ export class JediProxy implements vscode.Disposable {
240
256
241
257
// tslint:disable-next-line:max-func-body-length
242
258
private async spawnProcess ( cwd : string ) {
243
- this . initialized = createDeferred < void > ( ) ;
259
+ if ( this . languageServerStarted && ! this . languageServerStarted . completed ) {
260
+ this . languageServerStarted . reject ( ) ;
261
+ }
262
+ this . languageServerStarted = createDeferred < void > ( ) ;
244
263
const pythonProcess = await this . serviceContainer . get < IPythonExecutionFactory > ( IPythonExecutionFactory ) . create ( Uri . file ( this . workspacePath ) ) ;
245
264
const args = [ 'completion.py' ] ;
246
265
if ( typeof this . pythonSettings . jediPath !== 'string' || this . pythonSettings . jediPath . length === 0 ) {
@@ -263,7 +282,7 @@ export class JediProxy implements vscode.Disposable {
263
282
}
264
283
const result = pythonProcess . execObservable ( args , { cwd } ) ;
265
284
this . proc = result . proc ;
266
- this . initialized . resolve ( ) ;
285
+ this . languageServerStarted . resolve ( ) ;
267
286
this . proc . on ( 'end' , ( end ) => {
268
287
logger . error ( 'spawnProcess.end' , `End - ${ end } ` ) ;
269
288
} ) ;
@@ -274,8 +293,8 @@ export class JediProxy implements vscode.Disposable {
274
293
error . message . indexOf ( 'This socket has been ended by the other party' ) >= 0 ) {
275
294
this . spawnProcess ( cwd )
276
295
. catch ( ex => {
277
- if ( this . initialized ) {
278
- this . initialized . reject ( ex ) ;
296
+ if ( this . languageServerStarted ) {
297
+ this . languageServerStarted . reject ( ex ) ;
279
298
}
280
299
this . handleError ( 'spawnProcess' , ex ) ;
281
300
} ) ;
@@ -533,14 +552,7 @@ export class JediProxy implements vscode.Disposable {
533
552
return '' ;
534
553
}
535
554
}
536
-
537
- private async onConfigChanged ( ) {
538
- // We're only interested in changes to the python path.
539
- if ( this . lastKnownPythonPath === this . pythonSettings . pythonPath ) {
540
- return ;
541
- }
542
-
543
- this . lastKnownPythonPath = this . pythonSettings . pythonPath ;
555
+ private async buildAutoCompletePaths ( ) : Promise < string [ ] > {
544
556
const filePathPromises = [
545
557
// Sysprefix.
546
558
this . getPathFromPythonCommand ( [ '-c' , 'import sys;print(sys.prefix)' ] ) . catch ( ( ) => '' ) ,
@@ -561,19 +573,27 @@ export class JediProxy implements vscode.Disposable {
561
573
] ;
562
574
563
575
try {
564
- const environmentVariablesProvider = this . serviceContainer . get < IEnvironmentVariablesProvider > ( IEnvironmentVariablesProvider ) ;
565
- const pythonPaths = await environmentVariablesProvider . getEnvironmentVariables ( Uri . file ( this . workspacePath ) )
576
+ const pythonPaths = await this . getEnvironmentVariablesProvider ( ) . getEnvironmentVariables ( Uri . file ( this . workspacePath ) )
566
577
. then ( customEnvironmentVars => customEnvironmentVars ? JediProxy . getProperty < string > ( customEnvironmentVars , 'PYTHONPATH' ) : '' )
567
578
. then ( pythonPath => ( typeof pythonPath === 'string' && pythonPath . trim ( ) . length > 0 ) ? pythonPath . trim ( ) : '' )
568
579
. then ( pythonPath => pythonPath . split ( path . delimiter ) . filter ( item => item . trim ( ) . length > 0 ) ) ;
569
-
580
+ const resolvedPaths = pythonPaths
581
+ . filter ( pythonPath => ! path . isAbsolute ( pythonPath ) )
582
+ . map ( pythonPath => path . resolve ( this . workspacePath , pythonPath ) ) ;
570
583
const filePaths = await Promise . all ( filePathPromises ) ;
571
- this . additionalAutoCopletePaths = filePaths . concat ( pythonPaths ) . filter ( p => p . length > 0 ) ;
584
+ return filePaths . concat ( ... pythonPaths , ... resolvedPaths ) . filter ( p => p . length > 0 ) ;
572
585
} catch ( ex ) {
573
586
console . error ( 'Python Extension: jediProxy.filePaths' , ex ) ;
587
+ return [ ] ;
574
588
}
575
589
}
576
-
590
+ private getEnvironmentVariablesProvider ( ) {
591
+ if ( ! this . environmentVariablesProvider ) {
592
+ this . environmentVariablesProvider = this . serviceContainer . get < IEnvironmentVariablesProvider > ( IEnvironmentVariablesProvider ) ;
593
+ this . environmentVariablesProvider . onDidEnvironmentVariablesChange ( this . environmentVariablesChangeHandler . bind ( this ) ) ;
594
+ }
595
+ return this . environmentVariablesProvider ;
596
+ }
577
597
private getConfig ( ) {
578
598
// Add support for paths relative to workspace.
579
599
const extraPaths = this . pythonSettings . autoComplete . extraPaths . map ( extraPath => {
@@ -591,7 +611,7 @@ export class JediProxy implements vscode.Disposable {
591
611
extraPaths . unshift ( this . workspacePath ) ;
592
612
}
593
613
594
- const distinctExtraPaths = extraPaths . concat ( this . additionalAutoCopletePaths )
614
+ const distinctExtraPaths = extraPaths . concat ( this . additionalAutoCompletePaths )
595
615
. filter ( value => value . length > 0 )
596
616
. filter ( ( value , index , self ) => self . indexOf ( value ) === index ) ;
597
617
0 commit comments